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);
	}
}