学习Spring Data JPA之路

时间:2022-06-23 16:10:15

1。核心概念

Spring Data存储库抽象中的中心接口是Repository它需要管理域类以及域类的ID类型作为类型参数。该接口主要作为标记接口来捕获要使用的类型,并帮助您发现扩展该接口的接口。CrudRepository规定对于正在管理的实体类复杂的CRUD功能

通常情况下,你的资料库接口扩展RepositoryCrudRepositoryPagingAndSortingRepository

Repository:不公开,

CrudRepository:如果您想公开该域类型的CRUD方法,请扩展CrudRepository而不是Repository。也就是在CrudRepository都是通用的方法,其它也可以使用。

PagingAndSortingRepository:分页查询,可以集成Repository也可以继承CrudRepository,如果继承Repository说明是私有的,如果继承CrudRepository说明是通用的。

例1.  CrudRepository 接口
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> { //保存给定的实体。 <S extends T> S save(S entity);  //返回由给定ID标识的实体。 Optional<T> findById(ID primaryKey);  //返回所有实体。 Iterable<T> findAll();  //返回实体的数量。 long count();  //删除给定的实体。 void delete(T entity);  //指示是否存在具有给定ID的实体。 boolean existsById(ID primaryKey);  //下面还有很多方法,比如派生查询数量,派生删除等。。。 // … more functionality omitted. }
 

除此之外CrudRepository,还有一个PagingAndSortingRepository抽象增加了其他方法来简化对实体的分页访问:

示例2.  PagingAndSortingRepository 接口集成上面通用接口CrudRepository实现分页查询
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { Iterable<T> findAll(Sort sort); Page<T> findAll(Pageable pageable); }

要访问User页面大小为20 的第二页,您可以执行以下操作:

PagingAndSortingRepository<User, Long> repository = // … get access to a bean Page<User> users = repository.findAll(new PageRequest(1, 20));

Page<User> findByLastname(String lastname, Pageable pageable); Slice<User> findByLastname(String lastname, Pageable pageable); List<User> findByLastname(String lastname, Sort sort); List<User> findByLastname(String lastname, Pageable pageable);

User findFirstByOrderByLastnameAsc(); User findTopByOrderByAgeDesc(); Page<User> queryFirst10ByLastname(String lastname, Pageable pageable); Slice<User> findTop3ByLastname(String lastname, Pageable pageable); List<User> findFirst10ByLastname(String lastname, Sort sort); List<User> findTop10ByLastname(String lastname, Pageable pageable);

除了查询方法外,count和delete查询的查询派生都是可用的。以下列表显示派生计数查询的接口定义:

示例3.派生计数查询,根据姓名统计数量,需要集成CrudRepository然后扩展其方法
interface UserRepository extends CrudRepository<User, Long> { long countByLastname(String lastname); }

以下列表显示派生删除查询的接口定义:

示例4.派生删除查询
interface UserRepository extends CrudRepository<User, Long> { long deleteByLastname(String lastname); List<User> removeByLastname(String lastname); }

4.2。查询方法


标准CRUD功能存储库通常会在底层数据存储上进行查询。使用Spring Data,声明这些查询变成了一个四步过程:

  1. 声明一个扩展Repository或其子接口的接口,如果您想公开该域类型的CRUD方法,请扩展CrudRepository而不是Repository并将其键入它应该处理的域类和ID类型,在接口上声明查询方法,如以下示例所示:

  2. interface PersonRepository extends Repository<Person, Long> { List<Person> findByLastname(String lastname); }
  3. 设置Spring以使用JavaConfigXML配置为这些接口创建代理实例

    1. 要使用Java配置,请创建类似于以下的类:

      import org.springframework.data.jpa.repository.config.EnableJpaRepositories; @EnableJpaRepositories class Config {}

中间存储库接口用注释@NoRepositoryBean。确保将该注释添加到Spring Data不应在运行时为其创建实例的所有存储库接口。




  • @NonNullApi:在包级别上用于声明参数和返回值的默认行为是不接受或生成null值。

  • @NonNull:用于参数或返回值,不能是null (参数不需要,返回值@NonNullApi适用)。

  • @Nullable:用于可以是的参数或返回值null

3.定义方法时的关键字说明

表3.方法名称中支持的关键字
关键词 样品 JPQL片段

And

findByLastnameAndFirstname

… where x.lastname = ?1 and x.firstname = ?2

Or

findByLastnameOrFirstname

… where x.lastname = ?1 or x.firstname = ?2

Is,Equals

findByFirstnamefindByFirstnameIsfindByFirstnameEquals

… 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(参数绑定附加%

EndingWith

findByFirstnameEndingWith

… where x.firstname like ?1(参数与预先绑定%

Containing

findByFirstnameContaining

… where x.firstname like ?1(参数绑定%

OrderBy

findByAgeOrderByLastnameDesc

… where x.age = ?1 order by x.lastname desc

Not

findByLastnameNot

… where x.lastname <> ?1

In

findByAgeIn(Collection<Age> ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection<Age> ages)

… 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)


常用注解

@MappedSuperclass:

@NoRepositoryBean:选择性的暴露库

@NamedQuery注解,大致意思就是让我们在Repository接口中定义的findByName方法不使用默认的查询实现,取而代之的是使用这条自定义的查询语句去查询,如果这里没有标注的话,会使用默认实现的。


总结性思考:

1.EntityManager

我们都知道,在使用持久化工具的时候,一般都有一个对象来操作数据库,在原生的Hibernate中叫做Session,在JPA中叫做EntityManager,在MyBatis中叫做SqlSession,通过这个对象来操作数据库。

我们一般按照三层结构来看的话,Service层做业务逻辑处理,Dao层和数据库打交道,在Dao中,就存在着上面的对象。那么ORM框架本身提供的功能有什么呢?答案是基本的CRUD,使用Spring-data-jpa进行开发的过程中,常用的功能,我们几乎不需要写一条sql语句。

2.Entity其实就是实体管理器,注意每一个实体类必须要有主见id,需要加入@Id的注解,否则就会报错说实体类没有定义。

3.dao

数据库访问对象,在jpa当中,有一个词语叫Repository,这里我们一般就用Repository结尾来表示这个dao,比如UserDao,这里我们使用UserRepository,同理,在mybatis中我们一般也不叫dao,mybatis由于使用xml映射文件,我们一般使用mapper结尾,比如我们也不叫UserDao,而叫UserMapper。

  首先base-package属性,代表你的Repository接口的位置,repository-impl-postfix属性代表接口的实现类的后缀结尾字符,比如我们的UserRepository,那么他的实现类就叫做UserRepositoryImpl,和我们平时的使用习惯完全一致。

  比如:我们的UserRepository和UserRepositoryImpl这两个类就像下面这样来写。

public interface UserRepository extends JpaRepository<User, Integer>{}
public class UserRepositoryImpl {}

  那么这里为什么要这么做呢?原因是:spring-data-jpa提供基础的CRUD工作,同时也提供业务逻辑的功能,所以我们的Repository接口要做两项工作,继承spring-data-jpa提供的基础CRUD功能的接口,比如JpaRepository接口,同时自己还需要在UserRepository这个接口中定义自己的方法,那么导致的结局就是UserRepository这个接口中有很多的方法,那么如果我们的UserRepositoryImpl实现了UserRepository接口,导致的后果就是我们势必需要重写里面的所有方法,这是Java语法的规定,如此一来,悲剧就产生了,UserRepositoryImpl里面我们有很多的@Override方法,这显然是不行的,结论就是,这里我们不用去写implements部分。原因是:这个过程中cglib发挥了杰出的作用,在spring-data-jpa内部,有一个类,叫做

public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>,
        JpaSpecificationExecutor<T>

我们可以看到这个类是实现了JpaRepository接口的,事实上如果我们按照上面的配置,在同一个包下面有UserRepository,但是没有UserRepositoryImpl这个类的话,在运行时期UserRepository这个接口的实现就是上面的SimpleJpaRepository这个接口。而如果有UserRepositoryImpl这个文件的话,那么UserRepository的实现类就是UserRepositoryImpl,而UserRepositoryImpl这个类又是SimpleJpaRepository的子类,如此一来就很好的解决了上面的这个不用写implements的问题。我们通过阅读这个类的源代码可以发现,里面包装了entityManager,底层的调用关系还是entityManager在进行CRUD。

4.相应的Repository的说明

JpaRepository实现了PagingAndSortingRepository接口,

PagingAndSortingRepository接口实现了CrudRepository接口,

CrudRepository接口实现了Repository接口;

Repository接口是一个标识接口,里面是空的;

Repository接口

这个接口是最基础的接口,只是一个标志性的接口,没有定义任何的方法,那这个接口有什么用了?既然Spring data JPA提供了这个接口,自然是有它的用处,例如,我们有一部分方法是不想对外提供的,比如我们只想提供增加和修改方法,不提供删除方法,那么前面的几个接口都是做不到的,这个时候,我们就可以继承这个接口,然后将CrudRepository接口里面相应的方法拷贝到Repository接口就可以了。

总结:上述五个接口,开发者到底该如何选择?其实依据很简单,根据具体的业务需求,选择其中之一。因为各个接口之间并不存在功能强弱的问题。

CrudRepository接口定义了增删改查方法;

  1. @NoRepositoryBean  
  2. public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {  
  3.     <S extends T> S save(S entity);//保存  
  4.     <S extends T> Iterable<S> save(Iterable<S> entities);//批量保存  
  5.     T findOne(ID id);//根据id查询一个对象  
  6.     boolean exists(ID id);//判断对象是否存在  
  7.     Iterable<T> findAll();//查询所有的对象  
  8.     Iterable<T> findAll(Iterable<ID> ids);//根据id列表查询所有的对象  
  9.     long count();//计算对象的总个数  
  10.     void delete(ID id);//根据id删除  
  11.     void delete(T entity);//删除对象  
  12.     void delete(Iterable<? extends T> entities);//批量删除  
  13.     void deleteAll();//删除所有  
  14. }  

PagingAndSortingRepository接口用于分页和排序;

  1. @NoRepositoryBean  
  2. public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {  
  3.     Iterable<T> findAll(Sort sort);// 不带分页的排序  
  4.     Page<T> findAll(Pageable pageable);// 带分页的排序  
  5. }  

        //分页时,需要传入排序字段,可以多个,可以单个,第一个参数是正序,第二个参数是需要排序的字段。         Sort sort = new Sort(Direction.DESC, "id");          //第几页(默认从0开始),每页数量大小,排序字段(上面创建的sort)。         Pageable pageable = new PageRequest(05, sort);           Page<User> page = dao.findAll(pageable);           System.out.println(JSON.toJSONString(page));           System.out.println(page.getSize());  

JpaRepository接口继承了以上所有接口,所以拥有它们声明的所有方法;

  1. public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {  
  2.     List<T> findAll();//查询所有对象,不排序  
  3.     List<T> findAll(Sort sort);//查询所有对象,并排序  
  4.     <S extends T> List<S> save(Iterable<S> entities);//批量保存  
  5.     void flush();//强制缓存与数据库同步  
  6.     T saveAndFlush(T entity);//保存并强制同步  
  7.     void deleteInBatch(Iterable<T> entities);//批量删除  
  8.     void deleteAllInBatch();//删除所有  
  9. }  

JpaSpecificationExecutor接口,不属于Repository体系,实现一组JPA Criteria查询相关的方法。


[java]  view plain  copy
  1. public interface JpaSpecificationExecutor<T> {
        Optional<T> findOne(@Nullable Specification<T> var1);
        List<T> findAll(@Nullable Specification<T> var1);
        Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
        List<T> findAll(@Nullable Specification<T> var1, Sort var2);
        long count(@Nullable Specification<T> var1);
    }

该接口提供了对JPA Criteria(标准)查询的支持。Spring data JPA不会自动扫描识别,所以会报找不到对应的Bean,我们只需要继承任意一个继承了Repository的子接口或直接继承Repository接口,Spring data JPA就会自动扫描识别,进行统一的管理。

编写接口如下:

[java]  view plain  copy
  1. public interface SpecificationExecutorRepository extends CrudRepository<User, Integer>,  JpaSpecificationExecutor<User> {  
  2.   
  3. }  

Service 类:
[java]  view plain  copy
  1. @Service  
  2. public class SpecificationExecutorRepositoryManager {  
  3.     @Autowired  
  4.     private SpecificationExecutorRepository dao;  
  5.     /** 
  6.      * 描述:根据name来查询用户 
  7.      */  
  8.     public User findUserByName(final String name){  
  9.         return dao.findOne(new Specification<User>() {  
  10.               
  11.             @Override  
  12.             public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query,  CriteriaBuilder cb) {  
  13.                 Predicate predicate = cb.equal(root.get("name"), name);  
  14.                 return predicate;  
  15.             }  
  16.         });  
  17.     }  
  18.       
  19.     /** 
  20.      * 描述:根据name和email来查询用户 
  21.      */  
  22.     public User findUserByNameAndEmail(final String name, final String email){  
  23.         return dao.findOne(new Specification<User>() {  
  24.               
  25.             @Override  
  26.             public Predicate toPredicate(Root<User> root,  
  27.                     CriteriaQuery<?> query, CriteriaBuilder cb) {  
  28.                 List<Predicate> list = new ArrayList<Predicate>();  
  29.                 Predicate predicate1 = cb.equal(root.get("name"), name);  
  30.                 Predicate predicate2 = cb.equal(root.get("email"), email);  
  31.                 list.add(predicate1);  
  32.                 list.add(predicate2);  
  33.                 // 注意此处的处理  
  34.                 Predicate[] p = new Predicate[list.size()];  
  35.                 return cb.and(list.toArray(p));  
  36.             }  
  37.         });  
  38.     }  
  39.       
  40.     /** 
  41.      * 描述:组合查询 
  42.      */  
  43.     public User findUserByUser(final User userVo){  
  44.         return dao.findOne(new Specification<User>() {  
  45.               
  46.             @Override  
  47.             public Predicate toPredicate(Root<User> root,  
  48.                     CriteriaQuery<?> query, CriteriaBuilder cb) {  
  49.                 Predicate predicate = cb.equal(root.get("name"), userVo.getName());  
  50.                 cb.and(predicate, cb.equal(root.get("email"), userVo.getEmail()));  
  51.                 cb.and(predicate, cb.equal(root.get("password"), userVo.getPassword()));  
  52.                 return predicate;  
  53.             }  
  54.         });  
  55.     }  
  56.       
  57.     /** 
  58.      * 描述:范围查询in方法,例如查询用户id在[2,10]中的用户 
  59.      */  
  60.     public List<User> findUserByIds(final List<Integer> ids){  
  61.         return dao.findAll(new Specification<User>() {  
  62.   
  63.             @Override  
  64.             public Predicate toPredicate(Root<User> root,  
  65.                     CriteriaQuery<?> query, CriteriaBuilder cb) {  
  66.                 return root.in(ids);  
  67.             }  
  68.         });  
  69.     }  
  70.       
  71.     /** 
  72.      * 描述:范围查询gt方法,例如查询用户id大于9的所有用户 
  73.      */  
  74.     public List<User> findUserByGtId(final int id){  
  75.         return dao.findAll(new Specification<User>() {  
  76.   
  77.             @Override  
  78.             public Predicate toPredicate(Root<User> root,  
  79.                     CriteriaQuery<?> query, CriteriaBuilder cb) {  
  80.                 return cb.gt(root.get("id").as(Integer.class), id);  
  81.             }  
  82.         });  
  83.     }  
  84.       
  85.     /** 
  86.      * 描述:范围查询lt方法,例如查询用户id小于10的用户 
  87.      */  
  88.     public List<User> findUserByLtId(final int id){  
  89.         return dao.findAll(new Specification<User>() {  
  90.   
  91.             @Override  
  92.             public Predicate toPredicate(Root<User> root,  
  93.                     CriteriaQuery<?> query, CriteriaBuilder cb) {  
  94.                 return cb.lt(root.get("id").as(Integer.class), id);  
  95.             }  
  96.         });  
  97.     }  
  98.       
  99.     /** 
  100.      * 描述:范围查询between方法,例如查询id在3和10之间的用户 
  101.      */  
  102.     public List<User> findUserBetweenId(final int start, final int end){  
  103.         return dao.findAll(new Specification<User>() {  
  104.   
  105.             @Override  
  106.             public Predicate toPredicate(Root<User> root,  
  107.                     CriteriaQuery<?> query, CriteriaBuilder cb) {  
  108.                 return cb.between(root.get("id").as(Integer.class), start, end);  
  109.             }  
  110.         });  
  111.     }  
  112.       
  113.     /** 
  114.      * 描述:排序和分页操作 
  115.      */  
  116.     public Page<User> findUserAndOrder(final int id){  
  117.         Sort sort = new Sort(Direction.DESC, "id");  
  118.         return dao.findAll(new Specification<User>() {  
  119.   
  120.             @Override  
  121.             public Predicate toPredicate(Root<User> root,  
  122.                     CriteriaQuery<?> query, CriteriaBuilder cb) {  
  123.                 return cb.gt(root.get("id").as(Integer.class), id);  
  124.             }  
  125.         }, new PageRequest(05, sort));  
  126.     }  
  127.       
  128.     /** 
  129.      * 描述:只有排序操作 
  130.      */  
  131.     public List<User> findUserAndOrderSecondMethod(final int id){  
  132.         return dao.findAll(new Specification<User>() {  
  133.   
  134.             @Override  
  135.             public Predicate toPredicate(Root<User> root,  
  136.                     CriteriaQuery<?> query, CriteriaBuilder cb) {  
  137.                 cb.gt(root.get("id").as(Integer.class), id);  
  138.                 query.orderBy(cb.desc(root.get("id").as(Integer.class)));  
  139.                 return query.getRestriction();  
  140.             }  
  141.         });  
  142.     }  
  143. }