spring学习:spring data jpa
简介
在前一篇文章中,我们讨论了spring和jpa的集成实现。jpa本身是一个数据访问的规范,针对它有很多具体的实现。在这里,重点针对前面工程设计中一些地方进行改进。通过讨论这些值得改进的地方引入spring data jpa。在实际中我们会发现引入的东西居然和我们前面改进的思路是暗合的。
工程改进
在前一篇文章中,我们讨论过对工程的改进。在最开始的思路里,我们是定义了ContactService接口,在具体的ContactServiceImpl里直接引用sessionFactory或者EntityManager来操作数据。在这种情况下,我们的实际业务逻辑却直接和数据操作部分耦合起来了。于是,为了使得这部分不是紧密耦合的。我们定义了dao包,将真正数据访问的部分放在dao的实现里。
另外,考虑到对数据的基本增删查改算是最常用的操作。几乎每个对象都需要,我们完全可以定义一个通用的给其他dao实现来集成。因为要足够通用,我们采用泛型的参数接口。于是就得出了一个如下图的设计思路:
在这种实现里,我们定义了dao的抽象接口。理论上它可以进行CRUD操作的数据可以是继承自Object的任何对象。然后我们需要继承dao接口来定义自己的特定dao操作部分。而针对dao本身这些基础的crud操作就都由AbstractHbnDao实现了。我们定义自己的实现时只需要继承AbstractHbnDao,然后实现自定义的那部分接口就可以了 。
这种方式带来了不少的便利。当然,也带来了一个可以改进的地方。既然这个dao是用来针对通用的对象操作的。而且AbstractHbnDao也是定义的一个通用操作。那么可不可以直接将它们实现好作为一个通用的类库呢?这样以后我们就连这部分的定义都省了。真是基于这个观点,我们找到了spring data jpa。
引入spring data jpa
spring data jpa是一个通用的DAO框架。它除了支持基础的crud操作之外,还分页、排序和批处理操作。我们原来很多需要手工定义的dao类它都通过动态代理生成了。
spring data jpa的类结构图如下:
我们在工程中使用它们就比较简单了。首先定义一个自定义的dao接口。比如本例中的ContactDao:
package com.yunzero.dao; import java.util.List; import org.springframework.data.repository.CrudRepository; import com.yunzero.model.Contact; public interface ContactDao extends CrudRepository<Contact, Long> { List<Contact> findByEmailLike(String email); }
在这里我们可以看到,我们自定义的方法findByEmailLike。按照以往的思路,既然这是我们定义的方法,总该要我们自己去实现它吧?可是在这里,居然不用自己去实现它。我们看后续的代码:
ContactService:
package com.yunzero.service; import java.util.List; import com.yunzero.model.Contact; public interface ContactService { void createContact(Contact contact); List<Contact> getContacts(); List<Contact> getContactsByEmail(String email); Contact getContact(Long id); void updateContact(Contact contact); void deleteContact(Long id); }
真正重点的是ContactServiceImpl:
package com.yunzero.service.impl; import static org.springframework.util.Assert.notNull; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import javax.inject.Inject; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.yunzero.dao.ContactDao; import com.yunzero.model.Contact; import com.yunzero.service.ContactService; @Service @Transactional public class ContactServiceImpl implements ContactService { @Inject private ContactDao contactDao; @Override public void createContact(Contact contact) { notNull(contact, "contact can't be null"); contactDao.save(contact); } @Override public List<Contact> getContacts() { Iterable<Contact> iterable = contactDao.findAll(); Iterator<Contact> iterator = iterable.iterator(); List<Contact> contacts = new ArrayList<Contact>(); while (iterator.hasNext()) { contacts.add(iterator.next()); } return contacts; } @Override public List<Contact> getContactsByEmail(String email) { notNull(email, "email can't be null"); return contactDao.findByEmailLike("%" + email + "%"); } @Override public Contact getContact(Long id) { notNull(id, "id can't be null"); return contactDao.findOne(id); } @Override public void updateContact(Contact contact) { notNull(contact, "contact can't be null"); contactDao.save(contact); } @Override public void deleteContact(Long id) { notNull(id, "id can't be null"); contactDao.delete(id); } }
这里最玄乎的就是contactDao有了save, delete等方法了。而实际上ContactDao继承的不是接口吗?而且更猛的是,我在ContactDao里定义的方法findByEmailLike没有定义实现居然可以拿来直接用!这一切看起来像魔法一样,实在是太不可思议了。
实际上,这一切就是spring data jpa的动态代理方法带来的好处。我们定义的方法只要按照它定义的规则来命名,它就可以通过反射将名字和各种操作方法和映射起来。有点convention over configuration的感觉哈。对于具体spring data jpa是怎么实现这些的,这里不再赘述,在后面的文章中会详细讨论。
配置
上面就是所有的代码了。除了前面代码部分,还要一个需要注意的就是配置。这里一个典型的配置如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.8.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd"> <context:property-placeholder location="classpath:/environment.properties" /> <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close" p:driverClassName="${dataSource.driverClassName}" p:url="${dataSource.url}" p:username="${dataSource.username}" p:password="${dataSource.password}" /> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" p:dataSource-ref="dataSource" p:packagesToScan="com.yunzero.model"> <property name="persistenceProvider"> <bean class="org.hibernate.jpa.HibernatePersistenceProvider" /> </property> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop> <prop key="hibernate.show_sql">false</prop> </props> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" p:entityManagerFactory-ref="entityManagerFactory" /> <tx:annotation-driven /> <jpa:repositories base-package="com.yunzero.dao" /> <context:component-scan base-package="com.yunzero.service.impl" /> </beans>
看起来和前面的配置没什么差别。唯一的一点就是引入了jpa的命名空间,并引入了这么一个配置。<jpa:repositories base-package="com.yunzero.dao" />
当然,在我们依赖的包里也需要引入spring-data-jpa,如下是一个典型的引用内容:
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.8.0.RELEASE</version> </dependency>
就只要这么几步,整个数据操作的部分就完成了。详细的实现可以参考后面的附件。
总结
spring data jpa是对dao方式操作数据的进一步优化。它带来的不仅仅是一个抽象的通用数据访问接口,更多的是一个按照命名规范自动生成的数据访问实现。这些玄妙之处值得后续好好学习。