Spring Data JPA 介绍及使用
Spring Data JPA 介绍
概述
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!
Spring Data JPA 让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,在实际的工作工程中,推荐使用Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦
特性
SpringData Jpa 极大简化了数据库访问层代码。 如何简化的呢? 使用了SpringDataJpa,我们的dao层中只需要写接口,就自动具有了增删改查、分页查询等方法。
官网:https://docs.spring.io/spring-data/jpa/docs/2.6.0/reference/html/#preface
与 JPA和hibernate之间的关系
JPA是一套规范,内部是有接口和抽象类组成的。hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也可以称hibernate为JPA的一种实现方式,我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程)
Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案。
Spring Data JPA 使用
引入坐标
使用Spring Data JPA,需要整合Spring与Spring Data JPA,并且需要提供JPA的服务提供者hibernate,所以需要导入spring相关坐标,hibernate坐标,数据库驱动坐标等
    4.2.4.RELEASE 
    5.0.7.Final 
    1.6.6 
    1.2.12 
    0.9.1.2 
    5.1.6 
 
    
    
        junit 
        junit 
        4.9 
        test 
     
    
    
    
        org.aspectj 
        aspectjweaver 
        1.6.8 
     
    
        org.springframework 
        spring-aop 
        ${spring.version} 
     
    
        org.springframework 
        spring-context 
        ${spring.version} 
     
    
        org.springframework 
        spring-context-support 
        ${spring.version} 
     
    
        org.springframework 
        spring-orm 
        ${spring.version} 
     
    
        org.springframework 
        spring-beans 
        ${spring.version} 
     
    
        org.springframework 
        spring-core 
        ${spring.version} 
     
    
    
    
    
        org.hibernate 
        hibernate-core 
        ${hibernate.version} 
     
    
        org.hibernate 
        hibernate-entitymanager 
        ${hibernate.version} 
     
    
        org.hibernate 
        hibernate-validator 
        5.2.1.Final 
     
    
    
    
        c3p0 
        c3p0 
        ${c3p0.version} 
     
    
    
    
        log4j 
        log4j 
        ${log4j.version} 
     
    
        org.slf4j 
        slf4j-api 
        ${slf4j.version} 
     
    
        org.slf4j 
        slf4j-log4j12 
        ${slf4j.version} 
     
    
    
    
        mysql 
        mysql-connector-java 
        ${mysql.version} 
     
    
        org.springframework.data 
        spring-data-jpa 
        1.9.0.RELEASE 
     
    
        org.springframework 
        spring-test 
        4.2.4.RELEASE 
     
    
    
      
        javax.el   
        javax.el-api   
        2.2.4   
       
      
      
        org.glassfish.web   
        javax.el   
        2.2.4   
      
    
 
与Spring整合
<?xml version="1.0" encoding="UTF-8"?>
  
  
  
    
       
    
    
      
        
         
     
    
    
       
   
  
  
     
  
  
     
  
  
    
       
   
  
  
     
  
  
   
使用JPA注解配置映射关系
@Entity
	作用:指定当前类是实体类。
@Table
	作用:指定实体类和表之间的对应关系。
@Id
	作用:指定当前字段是主键。
@GeneratedValue
	作用:指定主键的生成方式。。
@Column
	作用:指定实体类属性和数据库表之间的对应关系
@OneToMany:
   	作用:建立一对多的关系映射
    属性:
    	targetEntityClass:指定多的多方的类的字节码
    	mappedBy:指定从表实体类中引用主表对象的名称。
    	cascade:指定要使用的级联操作
    	fetch:指定是否采用延迟加载
    	orphanRemoval:是否使用孤儿删除
@ManyToOne
    作用:建立多对一的关系
    属性:
    	targetEntityClass:指定一的一方实体类字节码
    	cascade:指定要使用的级联操作
    	fetch:指定是否采用延迟加载
    	optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
@JoinColumn
     作用:用于定义主键字段和外键字段的对应关系。
     属性:
    	name:指定外键字段的名称
    	referencedColumnName:指定引用主表的主键字段名称
    	unique:是否唯一。默认值不唯一
    	nullable:是否允许为空。默认值允许。
    	insertable:是否允许插入。默认值允许。
    	updatable:是否允许更新。默认值允许。
    	columnDefinition:列的定义信息。
@ManyToMany
	作用:用于映射多对多关系
	属性:
		cascade:配置级联操作。
		fetch:配置是否采用延迟加载。
    	targetEntity:配置目标的实体类。映射多对多的时候不用写。
@JoinTable
    作用:针对中间表的配置
    属性:
    	nam:配置中间表的名称
    	joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段			  			
    	inverseJoinColumn:中间表的外键字段关联对方表的主键字段
编写符合其规范的Dao层接口
Spring Data JPA是spring提供的一款对于数据访问层(Dao层)的框架,使用Spring Data JPA,只需要按照框架的规范提供dao接口,不需要实现类就可以完成数据库的增删改查、分页查询等方法的定义,极大的简化了我们的开发过程。
注意:在Spring Data JPA中,对于定义符合规范的Dao层接口,我们只需要遵循以下几点就可以了:
1.创建一个Dao层接口,并实现JpaRepository和JpaSpecificationExecutor
2.提供相应的泛型
/**
 * JpaRepository<实体类类型,主键类型>:用来完成基本CRUD操作
 * JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
 */
public interface CustomerDao extends JpaRepository, JpaSpecificationExecutor {
   
}
  Spring Data JPA的内部原理剖析
常用接口分析
对于我们自定义的Dao接口,由于继承了JpaRepository和JpaSpecificationExecutor,所以我们可以使用这两个接口的所有方法。
在使用Spring Data JPA时,一般实现JpaRepository和JpaSpecificationExecutor接口,这样就可以使用这些接口中定义的方法,但是这些方法都只是一些声明,没有具体的实现方式,那么在 Spring Data JPA中它又是怎么实现的呢?
实现过程
通过对客户案例,以debug断点调试的方式,通过分析Spring Data JPA的原理来分析程序的执行过程
我们以findOne方法为例进行分析:
代理子类的实现过程
断点执行到方法上时,我们可以发现注入的customerDao对象,本质上是通过JdkDynamicAopProxy生成的一个代理对象.
代理对象中方法调用的分析
当程序执行的时候,会通过JdkDynamicAopProxy的invoke方法,对customerDao对象生成动态代理对象。根据对Spring Data JPA介绍而知,要想进行findOne查询方法,最终还是会出现JPA规范的API完成操作,那么这些底层代码存在于何处呢?答案很简单,都隐藏在通过JdkDynamicAopProxy生成的动态代理对象当中,而这个动态代理对象就是SimpleJpaRepository
通过SimpleJpaRepository的源码分析,定位到了findOne方法,在此方法中,返回em.find()的返回结果,那么em又是什么呢?
带着问题继续查找em对象,我们发现em就是EntityManager对象,而他是JPA原生的实现方式,所以我们得到结论Spring Data JPA只是对标准JPA操作进行了进一步封装,简化了Dao层代码的开发
完整的调用过程分析
Spring Data JPA的查询方式
使用中接口定义的方法
在继承JpaRepository,和JpaRepository接口后,我们就可以使用接口中定义的方法进行查询
使用JPQL的方式查询
使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询
@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可
使用SQL语句查询
Spring Data JPA同样也支持sql语句的查询,如下:
/**
 * nativeQuery : 使用本地sql的方式查询
 */
@Query(value="select * from cst_customer",nativeQuery=true)
public void findSql();
方法命名规则查询
方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询。
按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
| Keyword | Sample | JPQL | 
|---|---|---|
| And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 | 
| Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 | 
| Is,Equals | findByFirstnameIs,findByFirstnameEquals | … where x.firstname = ?1 | 
| Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 | 
| LessThan | findByAgeLessThan | … where x.age < ?1 | 
| LessThanEqual | findByAgeLessThanEqual | … where x.age ? ?1 | 
| GreaterThan | findByAgeGreaterThan | … where x.age > ?1 | 
| GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 | 
| After | findByStartDateAfter | … where x.startDate > ?1 | 
| Before | findByStartDateBefore | … where x.startDate < ?1 | 
| IsNull | findByAgeIsNull | … where x.age is null | 
| IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null | 
| Like | findByFirstnameLike | … where x.firstname like ?1 | 
| NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 | 
| StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) | 
| EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) | 
| Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) | 
| OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc | 
| Not | findByLastnameNot | … where x.lastname <> ?1 | 
| In | findByAgeIn(Collection ages) | … where x.age in ?1 | 
| NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 | 
| TRUE | findByActiveTrue() | … where x.active = true | 
| FALSE | findByActiveFalse() | … where x.active = false | 
| IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) | 
Spring Data JPA中的多表查询
对象导航查询
对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。
@Autowired
private CustomerDao customerDao;
@Test
//由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中
@Transactional 
public void testFind() {
	Customer customer = customerDao.findOne(5l);
	Set linkMans = customer.getLinkMans();//对象导航查询
	for(LinkMan linkMan : linkMans) {
		System.out.println(linkMan);
	}
}
 使用Specification查询
/**
 * Specification的多表查询
 */
@Test
public void testFind() {
	Specification spec = new Specification() {
		public Predicate toPredicate(Root root, CriteriaQuery<?> query, CriteriaBuilder cb) {
			//Join代表链接查询,通过root对象获取
			//创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
			//JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接
			Join join = root.join("customer",JoinType.INNER);
			return cb.like(join.get("custName").as(String.class),"阿冰");
		}
	};
	List list = linkManDao.findAll(spec);
	for (LinkMan linkMan : list) {
		System.out.println(linkMan);
	}
}