JPA简介及其使用详解

一、Spring data JPA简介

Spring data JPA是Spring在ORM框架,以及JPA规范的基础上,封装的一套JPA应用框架,并提供了一整套的数据访问层解决方案。

二、Spring data JPA的功能

Spring data JPA的功能非常的强大,这里我们先跳过环境搭建这一步,来一睹Spring data JPA的“芳容”。

Spring data JPA提供给用户使用的,主要有以下几个接口:

  1. Repository:仅仅是一个标识,表明任何继承它的均为仓库接口类,方便Spring自动扫描识别
  2. CrudRepository:继承Repository,实现了一组CRUD相关的方法
  3. PagingAndSortingRepository:继承CrudRepository,实现了一组分页排序相关的方法
  4. JpaRepository:继承PagingAndSortingRepository,实现一组JPA规范相关的方法
  5. JpaSpecificationExecutor:比较特殊,不属于Repository体系,实现一组JPA Criteria查询相关的方法。

三、Spring data JPA的接口

1、CrudRepository接口

该接口的定义如下,总共提供了11个方法,基本上可以满足简单的CRUD操作以及批量操作:

@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {

  <S extends T> S save(S entity);//保存

  <S extends T> Iterable<S> save(Iterable<S> entities);//批量保存

  T findOne(ID id);//根据id查询一个对象

  boolean exists(ID id);//判断对象是否存在

  Iterable<T> findAll();//查询所有的对象

  Iterable<T> findAll(Iterable<ID> ids);//根据id列表查询所有的对象

  long count();//计算对象的总个数

  void delete(ID id);//根据id删除

  void delete(T entity);//删除对象

  void delete(Iterable<? extends T> entities);//批量删除

  void deleteAll();//删除所有

}

2、PagingAndSortingRepository接口

PagingAndSortingRepository接口继承了CrudRepository接口。

只要继承了这个接口,Spring data JPA就已经为你提供了分页和排序的功能了。该接口的定义如下,主要提供了两个方法,供使用,其中T是要操作的实体类,ID是实体类主键的类型:

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);// 不带分页的排序

  Page<T> findAll(Pageable pageable);// 带分页的排序

}

3、JpaRepository接口

如果业务需要即提供CRUD操作,又需要提供分页以及排序功能,那么就可以直接继承这个接口。该接口继承了PagingAndSortingRepository接口。

接口定义如下:

public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {

  List<T> findAll();//查询所有对象,不排序

  List<T> findAll(Sort sort);//查询所有对象,并排序

  <S extends T> List<S> save(Iterable<S> entities);//批量保存

  void flush();//强制缓存与数据库同步

  T saveAndFlush(T entity);//保存并强制同步

  void deleteInBatch(Iterable<T> entities);//批量删除

  void deleteAllInBatch();//删除所有

}

4、JpaSpecificationExecutor接口

该接口提供了对JPA Criteria查询的支持。注意,这个接口很特殊,不属于Repository体系,而Spring data JPA不会自动扫描识别,所以会报找不到对应的Bean,我们只需要继承任意一个继承了Repository的子接口或直接继承Repository接口,Spring data JPA就会自动扫描识别,进行统一的管理。

编写接口如下:

public interface SpecificationExecutorRepository extends CrudRepository<User, Integer>,

    JpaSpecificationExecutor<User> { 

}

Service类:

@Service

public class SpecificationExecutorRepositoryManager {

  @Autowired
  private SpecificationExecutorRepository dao;

  /**
   * 描述:根据name来查询用户
   */
  public User findUserByName(final String name){
    return dao.findOne(new Specification<User>() {
      @Override
      public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query,
          CriteriaBuilder cb) {
        Predicate predicate = cb.equal(root.get("name"), name);
        return predicate;
      }
    });
  }

    
  /**
   * 描述:根据name和email来查询用户
   */
  public User findUserByNameAndEmail(final String name, final String email){
    return dao.findOne(new Specification<User>() {
      @Override
      public Predicate toPredicate(Root<User> root,
          CriteriaQuery<?> query, CriteriaBuilder cb) {
        List<Predicate> list = new ArrayList<Predicate>();
        Predicate predicate1 = cb.equal(root.get("name"), name);
        Predicate predicate2 = cb.equal(root.get("email"), email);
        list.add(predicate1);
        list.add(predicate2);
        // 注意此处的处理
        Predicate[] p = new Predicate[list.size()];
        return cb.and(list.toArray(p));
      }
    });
  }
    

  /**
   * 描述:组合查询
   */
  public User findUserByUser(final User userVo){
    return dao.findOne(new Specification<User>() {
      @Override
      public Predicate toPredicate(Root<User> root,
          CriteriaQuery<?> query, CriteriaBuilder cb) {
        Predicate predicate = cb.equal(root.get("name"), userVo.getName());
        cb.and(predicate, cb.equal(root.get("email"), userVo.getEmail()));
        cb.and(predicate, cb.equal(root.get("password"), userVo.getPassword()));
        return predicate;
      }
    });
  }

    
  /**
   * 描述:范围查询in方法,例如查询用户id在[2,10]中的用户
   */
  public List<User> findUserByIds(final List<Integer> ids){
    return dao.findAll(new Specification<User>() {
      @Override
      public Predicate toPredicate(Root<User> root,
          CriteriaQuery<?> query, CriteriaBuilder cb) {
        return root.in(ids);
      }
    });
  }

   
  /**
   * 描述:范围查询gt方法,例如查询用户id大于9的所有用户
   */
  public List<User> findUserByGtId(final int id){
    return dao.findAll(new Specification<User>() {
      @Override
      public Predicate toPredicate(Root<User> root,
          CriteriaQuery<?> query, CriteriaBuilder cb) {
        return cb.gt(root.get("id").as(Integer.class), id);
      }
    });
  }

    
  /**
   * 描述:范围查询lt方法,例如查询用户id小于10的用户
   */
  public List<User> findUserByLtId(final int id){
    return dao.findAll(new Specification<User>() {
      @Override
      public Predicate toPredicate(Root<User> root,
          CriteriaQuery<?> query, CriteriaBuilder cb) {
        return cb.lt(root.get("id").as(Integer.class), id);
      }

    });

  }

    
  /**
   * 描述:范围查询between方法,例如查询id在3和10之间的用户
   */
  public List<User> findUserBetweenId(final int start, final int end){
    return dao.findAll(new Specification<User>() {
      @Override
      public Predicate toPredicate(Root<User> root,
          CriteriaQuery<?> query, CriteriaBuilder cb) {
        return cb.between(root.get("id").as(Integer.class), start, end);
      }
    });
  }
    

  /**
   * 描述:排序和分页操作
   */
  public Page<User> findUserAndOrder(final int id){
    Sort sort = new Sort(Direction.DESC, "id");
    return dao.findAll(new Specification<User>() {
      @Override
      public Predicate toPredicate(Root<User> root,
          CriteriaQuery<?> query, CriteriaBuilder cb) {
        return cb.gt(root.get("id").as(Integer.class), id);
      }
    }, new PageRequest(0, 5, sort));
  }

    

  /**
   * 描述:只有排序操作
   */
  public List<User> findUserAndOrderSecondMethod(final int id){
    return dao.findAll(new Specification<User>() {
      @Override
      public Predicate toPredicate(Root<User> root,
          CriteriaQuery<?> query, CriteriaBuilder cb) {
        cb.gt(root.get("id").as(Integer.class), id);
        query.orderBy(cb.desc(root.get("id").as(Integer.class)));
        return query.getRestriction();
      }
    });
  }

}

5、Repository接口

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

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

四、Spring data JPA的查询

1、使用 @Query 创建查询

@Query 注解的使用非常简单,只需在声明的方法上面标注该注解,同时提供一个 JP QL 查询语句即可。很多开发者在创建 JP QL 时喜欢使用命名参数来代替位置编号,@Query 也对此提供了支持。JP QL 语句中通过": 变量"的格式来指定参数,同时在方法的参数前面使用 @Param 将方法参数与 JP QL 中的命名参数对应。此外,开发者也可以通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询操作。

编写接口,如下:

/**
 * 描述:自定义查询,当Spring Data JPA无法提供时,需要自定义接口,此时可以使用这种方式
 */
public interface UserDefineBySelf extends JpaRepository<User, Integer> {

  /**
   * 命名参数
   * 描述:推荐使用这种方法,可以不用管参数的位置
   */
  @Query("select u from User u where u.name = :name")
  User findUserByName(@Param("name") String name);

    
  /**
   * 索引参数
   * 描述:使用?占位符
   */
  @Query("select u from User u where u.email = ?1")// 1表示第一个参数
  User findUserByEmail(String email);
    
  /**
   * 描述:可以通过@Modifying和@Query来实现更新
   * 注意:Modifying queries的返回值只能为void或者是int/Integer
   * 新版本update/delete必须使用@Transactional注解
   */
  @Transactional
  @Modifying
  @Query("update User u set u.name = :name where u.id = :id")
  int updateUserById(@Param("name") String name, @Param("id") int id);

}

注:@Modifying注解里面有一个配置clearAutomatically

它说的是可以清除底层持久化上下文,就是entityManager这个类,我们知道jpa底层实现会有二级缓存,也就是在更新完数据库后,如果后面去用这个对象,你再去查这个对象,这个对象是在一级缓存,但是并没有跟数据库同步,这个时候用clearAutomatically=true,就会刷新hibernate的一级缓存了, 不然你在同一接口中,更新一个对象,接着查询这个对象,那么你查出来的这个对象还是之前的没有更新之前的状态

2、使用@NamedQueries创建查询

命名查询是 JPA 提供的一种将查询语句从方法体中独立出来,以供多个方法共用的功能。Spring Data JPA 对命名查询也提供了很好的支持。用户只需要按照 JPA 规范在 orm.xml 文件或者在代码中使用 @NamedQuery(或 @NamedNativeQuery)定义好查询语句,唯一要做的就是为该语句命名时,需要满足”DomainClass.methodName()”的 命名规则。

编写接口:

public interface FindUserByNamedQueryRepository extends JpaRepository<User, Integer> {

  User findUserWithName(@Param("name") String name);

}

编写类:

@Entity
@NamedQueries(value={
    @NamedQuery(name="User.findUserWithName",query="select u from User u where u.name = :name")
})

// 注意:此处如果是多个方法,那么需要使用@NamedQueries,如果只有一个方法,则可以使用@NamedQuery,写法如下:@NamedQuery(name="User.findUserWithName",query="select u from User u where u.name = :name")
public class FindUserByNamedQuery {

  /**
   * 注意:此处必须要给这个实体类定义一个唯一标识,否则会报异常
   */
  @Id
  @GeneratedValue
  private Integer id;

}

注意:文中标记为红色的部分,需要一一对应,否则不满足JPA 的规范。

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext-config.xml" })
@TransactionConfiguration(defaultRollback = false)
@Transactional
public class FindUserByNamedQueryRepositoryTest {

  @Autowired
  private FindUserByNamedQueryRepository dao;

    
  @Test
  public void testFindUserByName(){
    User user = dao.findUserWithName("caican");
    System.out.println(JSON.toJSONString(user));
  }

}

3、通过解析方法名创建查询

顾名思义,就是根据方法的名字,就能创建查询,也许初听起来,感觉很不可思议,等测试后才发现,原来一切皆有可能。

编写接口:

public interface SimpleConditionQueryRepository extends JpaRepository<User, Integer> {

  /**
   * 说明:按照Spring data 定义的规则,查询方法以find|read|get开头
   * 涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写
   */   
  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name = :name and u.email = :email
   * 参数名大写,条件名首字母大写,并且接口名中参数出现的顺序必须和参数列表中的参数顺序一致
   */
  User findByNameAndEmail(String name, String email);
    

  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name = ?1 or u.password = ?2
   */
  List<User> findByNameOrPassword(String name, String password);
    

  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.id between ?1 and ?2
   */
  List<User> findByIdBetween(Integer start, Integer end);

    
  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.id < ?1
   */
  List<User> findByIdLessThan(Integer end);

    
  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.id > ?1
   */
  List<User> findByIdGreaterThan(Integer start);

    
  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name is null
   */
  List<User> findByNameIsNull();

    
  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name is not null
   */
  List<User> findByNameIsNotNull();

    
  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name like ?1
   */
  List<User> findByNameLike(String name);

    
  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name not like ?1
   */
  List<User> findByNameNotLike(String name);

    
  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.password = ?1 order by u.id desc
   */
  List<User> findByPasswordOrderByIdDesc(String password);

    
  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name <> ?1
   */
  List<User> findByNameNot(String name);

    
  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.id in ?1
   */
  List<User> findByIdIn(List<Integer> ids);

    
  /**
   * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.id not in ?1
   */
  List<User> findByIdNotIn(List<Integer> ids);

}

框架在进行方法名解析时,会先把方法名多余的前缀截取掉,比如 find、findBy、read、readBy、get、getBy,然后对剩下部分进行解析。并且如果方法的最后一个参数是 Sort 或者 Pageable 类型,也会提取相关的信息,以便按规则进行排序或者分页查询。在创建查询时,我们通过在方法名中使用属性名称来表达,比如 findByIdIn()。框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析。

在查询时,通常需要同时根据多个属性进行查询,且查询的条件也格式各样(大于某个值、在某个范围等等),Spring Data JPA 为此提供了一些表达条件查询的关键字,大致如下:

Keyword

Sample

JPQL snippet

And

findByLastnameAndFirstname

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

Or

findByLastnameOrFirstname

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

Is,Equals

findByFirstname,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<Age> ages)

… where x.age in ?1

NotIn

findByAgeNotIn(Collection<Age> 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 在为接口创建代理对象时,如果发现同时存在多种上述情况可用,它该优先采用哪种策略呢?为此,<jpa:repositories> 提供了 query-lookup-strategy 属性,用以指定查找的顺序。它有如下三个取值:

create --- 通过解析方法名字来创建查询。即使有符合的命名查询,或者方法通过 @Query 指定的查询语句,都将会被忽略。

create-if-not-found --- 如果方法通过 @Query 指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则通过解析方 法名字来创建查询。这是 query-lookup-strategy 属性的默认值。

use-declared-query --- 如果方法通过 @Query 指定了查询语句,则使用该语句实现查询;如果没有,则查找是否定义了符合条件的命名查询,如果找到,则使用该命名查询;如果两者都没有找到,则抛出异常。

六、Spring Data JPA 对事务的支持

默认情况下,Spring Data JPA 实现的方法都是使用事务的。针对查询类型的方法,其等价于 @Transactional(readOnly=true);增删改类型的方法,等价于 @Transactional。可以看出,除了将查询的方法设为只读事务外,其他事务属性均采用默认值。

如果用户觉得有必要,可以在接口方法上使用 @Transactional 显式指定事务属性,该值覆盖 Spring Data JPA 提供的默认值。同时,开发者也可以在业务层方法上使用 @Transactional 指定事务属性,这主要针对一个业务层方法多次调用持久层方法的情况。持久层的事务会根据设置的事务传播行为来决定是挂起业务层事务还是加入业务层的事务。

  • 7
    点赞
  • 55
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值