工作日志4-28

工作日志4-28

 

了解Spring Data JPA,先从 JPA 开始,简单介绍一个 JPA 示例;接着重构该示例,并引入 Spring 框架。

示例主要涉及七个文件,但是很清晰:业务层包含一个接口和一个实现;持久层包含一个接口、一个实现、一个实体类;另外加上一个 JPA 配置文件和一个测试类。相关类 / 接口代码如下:


实体类 AccountInfo.java
				
 @Entity 
 @Table(name = "t_accountinfo") 
 public class AccountInfo implements Serializable { 
 private Long accountId; 
 private Integer balance; 

 // 此处省略 getter 和 setter 方法。
 } 


业务层接口 UserService.java
				
 public interface UserService { 
 public AccountInfo createNewAccount(String user, String pwd, Integer init); 
 } 


业务层的实现类 UserServiceImpl.java
				
 public class UserServiceImpl implements UserService { 

 private UserDao userDao = new UserDaoImpl(); 

 public AccountInfo createNewAccount(String user, String pwd, Integer init){ 
 // 封装域对象
 AccountInfo accountInfo = new AccountInfo(); 
 UserInfo userInfo = new UserInfo(); 
 userInfo.setUsername(username); 
 userInfo.setPassword(password); 
 accountInfo.setBalance(initBalance); 
 accountInfo.setUserInfo(userInfo); 
 // 调用持久层,完成数据的保存
 return userDao.save(accountInfo); 
    } 
 } 


持久层接口
				
 public interface UserDao { 
 public AccountInfo save(AccountInfo accountInfo); 
 } 


持久层的实现类
				
 public class UserDaoImpl implements UserDao { 
 public AccountInfo save(AccountInfo accountInfo) { 
 EntityManagerFactory emf = 
 Persistence.createEntityManagerFactory("SimplePU"); 
 EntityManager em = emf.createEntityManager(); 
 em.getTransaction().begin(); 
 em.persist(accountInfo); 
 em.getTransaction().commit(); 
 emf.close(); 
 return accountInfo; 
    } 
 } 


JPA 标准配置文件 persistence.xml
				
 <?xml version="1.0" encoding="UTF-8"?> 
 <persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0"> 
 <persistence-unit name="SimplePU" transaction-type="RESOURCE_LOCAL"> 
 <provider>org.hibernate.ejb.HibernatePersistence</provider> 
 <class>footmark.springdata.jpa.domain.UserInfo</class> 
 <class>footmark.springdata.jpa.domain.AccountInfo</class> 
 <properties> 
 <property name="hibernate.connection.driver_class"
 value="com.mysql.jdbc.Driver"/> 
 <property name="hibernate.connection.url" 
 value="jdbc:mysql://10.40.74.197:3306/zhangjp"/> 
 <property name="hibernate.connection.username" value="root"/> 
 <property name="hibernate.connection.password" value="root"/> 
 <property name="hibernate.dialect"
 value="org.hibernate.dialect.MySQL5Dialect"/> 
 <property name="hibernate.show_sql" value="true"/> 
 <property name="hibernate.format_sql" value="true"/> 
 <property name="hibernate.use_sql_comments" value="false"/> 
 <property name="hibernate.hbm2ddl.auto" value="update"/> 
 </properties> 
 </persistence-unit> 
 </persistence> 


测试
				
 public class SimpleSpringJpaDemo { 
    public static void main(String[] args) { 
        new UserServiceImpl().createNewAccount("aa", "123456", 1); 
    } 
 } 
 

接下来我们引入 Spring,以展示 Spring 框架对 JPA 的支持。业务层接口 UserService 保持不变,UserServiceImpl 中增加了三个注解,以让 Spring 完成依赖注入,因此不再需要使用 new 操作符创建 UserDaoImpl 对象了。同时我们还使用了 Spring 的声明式事务:


配置为 Spring Bean 的业务层实现
				
 @Service("userService") 
 public class UserServiceImpl implements UserService { 
 @Autowired 
 private UserDao userDao; 

 @Transactional 
 public AccountInfo createNewAccount( 
 String name, String pwd, Integer init) { …… } 
 } 

对于持久层,UserDao 接口也不需要修改,只需修改 UserDaoImpl 实现,修改后的代码如下:


配置为 Spring Bean 的持久层实现
				
 @Repository("userDao") 
 public class UserDaoImpl implements UserDao { 

 @PersistenceContext 
 private EntityManager em; 

 @Transactional 
   public Long save(AccountInfo accountInfo) { 
 em.persist(accountInfo); 
 return accountInfo.getAccountId(); 
 } 
 } 


Spring 配置文件
				
 <?xml version="1.0" encoding="UTF-8"?> 
 <beans...> 
 <context:component-scan base-package="footmark.springdata.jpa"/> 
 <tx:annotation-driven transaction-manager="transactionManager"/> 
 <bean id="transactionManager" 
 class="org.springframework.orm.jpa.JpaTransactionManager"> 
 <property name="entityManagerFactory" ref="entityManagerFactory"/> 
 </bean> 
 <bean id="entityManagerFactory" class= 
"org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    </bean> 
 </beans> 


改后的测试代码
				
 public class SimpleSpringJpaDemo{ 
 public static void main(String[] args){ 
 ClassPathXmlApplicationContext ctx = 
 new ClassPathXmlApplicationContext("spring-demo-cfg.xml"); 
 UserDao userDao = ctx.getBean("userDao", UserDao.class); 
 userDao.createNewAccount("aa", "123456", 1); 
 } 
 } 

通过对比重构前后的代码,可以发现 Spring 对 JPA 的简化已经非常出色了,我们可以大致总结一下 Spring 框架对 JPA 提供的支持主要体现在如下几个方面:

  • 首先,它使得 JPA 配置变得更加灵活。JPA 规范要求,配置文件必须命名为 persistence.xml,并存在于类路径下的 META-INF 目录中。该文件通常包含了初始化 JPA 引擎所需的全部信息。Spring 提供的 LocalContainerEntityManagerFactoryBean 提供了非常灵活的配置,persistence.xml 中的信息都可以在此以属性注入的方式提供。
  • 其次,Spring 实现了部分在 EJB 容器环境下才具有的功能,比如对 @PersistenceContext、@PersistenceUnit 的容器注入支持。
  • 第三,也是最具意义的,Spring 将 EntityManager 的创建与销毁、事务管理等代码抽取出来,并由其统一管理,开发者不需要关心这些,如前面的代码所示,业务方法中只剩下操作领域对象的代码,事务管理和 EntityManager 创建、销毁的代码都不再需要开发者关心了。

    通过前面的分析可以看出,Spring 对 JPA 的支持已经非常强大,我们只需关心核心业务逻辑的实现代码,无需过多关注 EntityManager 的创建、事务处理等 JPA 相关的处理,这基本上也是作为一个开发框架而言所能做到的极限了。

    Spring Data JPA 框架,主要针对的就是 Spring 唯一没有简化到的业务逻辑代码,至此,连仅剩的实现持久层业务逻辑的工作都省了,唯一要做的,就只是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成!Spring Data JPA 做的是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。

    接下来我们针对前面的例子进行改造,让 Spring Data JPA 来帮助我们完成业务逻辑。在着手写代码之前,需要先下载Spring Data JPA 的发布包(需要同时下载 Spring Data Commons 和 Spring Data JPA 两个发布包,Commons 是 Spring Data 的公共基础包),并把相关的依赖 JAR 文件加入到 CLASSPATH 中。

首先,让持久层接口 UserDao 继承 Repository 接口。该接口使用了泛型,需要为其提供两个类型:第一个为该接口处理的域对象类型,第二个为该域对象的主键类型。修改后的 UserDao 如下:


Spring Data JPA 风格的持久层接口
				
 public interface UserDao extends Repository<AccountInfo, Long> { 
    public AccountInfo save(AccountInfo accountInfo); 
 } 

然后删除 UserDaoImpl 类,因为我们前面说过,框架会为我们完成业务逻辑。最后,我们需要在 Spring 配置文件中增加如下配置,以使 Spring 识别出需要为其实现的持久层接口:


在 Spring 配置文件中启用扫描并自动创建代理的功能
				
 <-- 需要在 <beans> 标签中增加对 jpa 命名空间的引用 --> 
 <jpa:repositories base-package="footmark.springdata.jpa.dao"
 entity-manager-factory-ref="entityManagerFactory" 
 transaction-manager-ref="transactionManager"/> 

至此便大功告成了!执行一下测试代码,然后看一下数据库,新的数据已经如我们预期的添加到表中了。如果要再增加新的持久层业务,比如希望查询出给 ID 的 AccountInfo 对象,该怎么办呢?很简单,在 UserDao 接口中增加一行代码即可:


修改后的持久层接口,增加一个方法声明
				
 public interface UserDao extends Repository<AccountInfo, Long> { 

 public AccountInfo save(AccountInfo accountInfo); 

 // 你需要做的,仅仅是新增如下一行方法声明
 public AccountInfo findByAccountId(Long accountId); 
 } 

下面总结一下使用 Spring Data JPA 进行持久层开发大致需要的三个步骤:

  1. 声明持久层的接口,该接口继承 Repository,Repository 是一个标记型接口,它不包含任何方法,当然如果有需要,Spring Data 也提供了若干 Repository 子接口,其中定义了一些常用的增删改查,以及分页相关的方法。
  2. 在接口中声明需要的业务方法。Spring Data 将根据给定的策略来为其生成实现代码。
  3. 在 Spring 配置文件中增加一行声明,让 Spring 为声明的接口创建代理对象。配置了 <jpa:repositories> 后,Spring 初始化容器时将会扫描 base-package 指定的包目录及其子目录,为继承 Repository 或其子接口的接口创建代理对象,并将代理对象注册为 Spring Bean,业务层便可以通过 Spring 自动封装的特性来直接使用该对象。

    此外,<jpa:repository> 还提供了一些属性和子标签,便于做更细粒度的控制。可以在 <jpa:repository> 内部使用 <context:include-filter>、<context:exclude-filter> 来过滤掉一些不希望被扫描到的接口。

前面提到,持久层接口继承 Repository 并不是唯一选择。Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,需要在自己定义的接口中声明需要的方法。与继承 Repository 等价的一种方式,就是在持久层接口上使用 @RepositoryDefinition 注解,并为其指定 domainClass 和 idClass 属性。如下两种方式是完全等价的:


两种等价的继承接口方式示例
				
 public interface UserDao extends Repository<AccountInfo, Long> { …… } 

 @RepositoryDefinition(domainClass = AccountInfo.class, idClass = Long.class) 
 public interface UserDao { …… } 

    如果持久层接口较多,且每一个接口都需要声明相似的增删改查方法,直接继承 Repository 就显得有些啰嗦,这时可以继承 CrudRepository,它会自动为域对象创建增删改查方法,供业务层直接使用。开发者只是多写了 "Crud" 四个字母,即刻便为域对象提供了开箱即用的十个增删改查方法。

    但是,使用 CrudRepository 也有副作用,它可能暴露了你不希望暴露给业务层的方法。比如某些接口你只希望提供增加的操作而不希望提供删除的方法。针对这种情况,开发者只能退回到 Repository 接口,然后到 CrudRepository 中把希望保留的方法声明复制到自定义的接口中即可。

    分页查询和排序是持久层常用的功能,Spring Data 为此提供了 PagingAndSortingRepository 接口,它继承自 CrudRepository 接口,在 CrudRepository 基础上新增了两个与分页有关的方法。但是,我们很少会将自定义的持久层接口直接继承自 PagingAndSortingRepository,而是在继承 Repository 或 CrudRepository 的基础上,在自己声明的方法参数列表最后增加一个 Pageable 或 Sort 类型的参数,用于指定分页或排序信息即可,这比直接使用 PagingAndSortingRepository 提供了更大的灵活性。

    JpaRepository 是继承自 PagingAndSortingRepository 的针对 JPA 技术提供的接口,它在父接口的基础上,提供了其他一些方法,比如 flush(),saveAndFlush(),deleteInBatch() 等。如果有这样的需求,则可以继承该接口。

    上述四个接口,到底该如何选择?其实依据很简单,根据具体的业务需求,选择其中之一。在通常情况下优先选择 Repository 接口。因为 Repository 接口已经能满足日常需求,其他接口能做到的在 Repository 中也能做到,彼此之间并不存在功能强弱的问题。只是 Repository 需要显示声明需要的方法,而其他则可能已经提供了相关的方法,不需要再显式声明,但如果对 Spring Data JPA 不熟悉,别人在检视代码或者接手相关代码时会有疑惑,他们不明白为什么明明在持久层接口中声明了三个方法,而在业务层使用该接口时,却发现有七八个方法可用,从这个角度而言,应该优先考虑使用 Repository 接口。

    前面提到,Spring Data JPA 在后台为持久层接口创建代理对象时,会解析方法名字,并实现相应的功能。除了通过方法名字以外,它还可以通过如下两种方式指定查询语句:

  1. Spring Data JPA 可以访问 JPA 命名查询语句。只需要在定义命名查询语句时,为其指定一个符合给定格式的名字,Spring Data JPA 便会在创建代理对象时,使用该命名查询语句来实现其功能。
  2. 还可以直接在声明的方法上面使用 @Query 注解,并提供一个查询语句作为参数,Spring Data JPA 在创建代理对象时,便以提供的查询语句来实现其功能。

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值