spring data jpa使用详解(推荐)

使用Spring data JPA开发已经有一段时间了,这期间学习了一些东西,也遇到了一些问题,在这里和大家分享一下。

前言:

Spring data简介:

Spring Data是一个用于简化数据库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷,并支持map-reduce框架和云计算数据服务。 Spring Data 包含多个子项目:

Commons - 提供共享的基础框架,适合各个子项目使用,支持跨数据库持久化

JPA - 简化创建 JPA 数据访问层和跨存储的持久层功能

Hadoop - 基于 Spring 的 Hadoop 作业配置和一个 POJO 编程模型的 MapReduce 作业

Key-Value  - 集成了 Redis 和 Riak ,提供多个常用场景下的简单封装

Document - 集成文档数据库:CouchDB 和 MongoDB 并提供基本的配置映射和资料库支持

Graph - 集成 Neo4j 提供强大的基于 POJO 的编程模型

Graph Roo AddOn - Roo support for Neo4j

JDBC Extensions - 支持 Oracle RAD、高级队列和高级数据类型

Mapping - 基于 Grails 的提供对象映射框架,支持不同的数据库

Examples - 示例程序、文档和图数据库

Guidance - 高级文档

一、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接口

建立一个Entity类:

@Entity
@Table(name="USER")
public class User {
  @Id
  @GeneratedValue
  private Integer id;
  //账号
  private String account;
  //姓名
  private String name;
  //密码
  private String password;
  // 邮箱
  private String email;
}

编写接口,并继承CrudRepository接口:

public interface UserRepository extends CrudRepository<User, Integer> {
}

编写测试类(为了更直观的看到效果,所有测试类都没有使用断言,直接使用的打印语句):

public class UserRepositoryTest {
  @Autowired
  private UserRepository dao; 

  @Test//保存
  public void testSave(){
    User user = new User();
    user.setName("chhliu");
    user.setAccount("10000");
    user.setEmail("chhliu@.com");
    user.setPassword("123456");
    dao.save(user);
  } 

  @Test//批量保存
  public void testSave1(){
    List<User> users = new ArrayList<User>();
    User user = new User();
    user.setName("tanjie");
    user.setAccount("10000");
    user.setEmail("tanjie@.com");
    user.setPassword("123456");
    users.add(user);
    user = new User();
    user.setName("esdong");
    user.setAccount("10000");
    user.setEmail("esdong@.com");
    user.setPassword("123456");
    users.add(user);
    user = new User();
    user.setName("qinhongfei");
    user.setAccount("10000");
    user.setEmail("qinhongfei@.com");
    user.setPassword("123456");
    users.add(user);
    user = new User();
    user.setName("huizhang");
    user.setAccount("10000");
    user.setEmail("huizhang@.com");
    user.setPassword("123456");
    users.add(user);
    user = new User();
    user.setName("caican");
    user.setAccount("10000");
    user.setEmail("caican@.com");
    user.setPassword("123456");
    users.add(user);
    dao.save(users);
  } 

  @Test//更新
  public void testUpdate(){
    User user = dao.findOne(1);
    user.setPassword("123890");// 要想这样实现更新的功能,需要在service层加上@Transaction事物注解
  } 

  @Test//删除
  public void testDelete(){
    dao.delete(2);
  } 

  @Test//查询所有
  public void testFindAll(){
    List<User> users = (List<User>) dao.findAll();
    System.out.println(JSON.toJSONString(users));
  } 

  @Test//判断指定的id对象是否存在
  public void testIsExist(){
    boolean isExist = dao.exists(8);
    System.out.println(isExist);
  } 

  @Test//通过id列表来查询
  public void testFindUserByIds(){
    List<Integer> listIds = new ArrayList<Integer>();
    listIds.add(2);
    listIds.add(4);
    listIds.add(7);
    List<User> users = (List<User>) dao.findAll(listIds);
    System.out.println(JSON.toJSONString(users));
  }
}

大家可以看出,到这里,我就只写了一个接口类,并没有实现这个接口类,就可以完成基本的CRUD操作。因为这个接口会自动为域对象创建增删改查方法,供业务层直接使用。

该接口的定义如下,总共提供了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接口。

编写接口,并继承PagingAndSortingRepository接口

public interface UserRepositoryWithOrder extends
    PagingAndSortingRepository<User, Integer> {
}

编写测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext-config.xml" })
@TransactionConfiguration(defaultRollback = false)
@Transactional
public class UserRepositoryWithOrderTest {
  @Autowired
  private UserRepositoryWithOrder dao; 

  @Test
  public void testOrder(){
    Sort sort = new Sort(Direction.DESC, "id");
    Pageable pageable = new PageRequest(0, 5, sort);
    Page<User> page = dao.findAll(pageable);
    System.out.println(JSON.toJSONString(page));
    System.out.println(page.getSize());
  }
}

只要继承了这个接口,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();
      }
    });
  }
}

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext-config.xml" })
@TransactionConfiguration(defaultRollback = false)
@Transactional
public class SpecificationExecutorRepositoryManagerTest {
  @Autowired
  private SpecificationExecutorRepositoryManager manager;
  @Test
  public void testFindUserByName(){
    User user = manager.findUserByName("chhliu");
    System.out.println(JSON.toJSONString(user));
  } 

  @Test
  public void testFindUserByNameAndEmail(){
    User user = manager.findUserByNameAndEmail("chhliu", "chhliu@.com");
    System.out.println(JSON.toJSONString(user));
  } 

  @Test
  public void testFindUserByUserVo(){
    User user = new User();
    user.setName("chhliu");
    user.setEmail("chhliu@.com");
    User u = manager.findUserByUser(user);
    System.out.println(JSON.toJSONString(u));
  } 

  @Test
  public void testFindUserByIds(){
    List<User> users = manager.findUserByIds(new ArrayList<Integer>(Arrays.asList(1,3,5,6)));
    System.out.println(JSON.toJSONString(users));
  } 

  @Test
  public void testFindUserByGtId(){
    List<User> users = manager.findUserByGtId(5);
    System.out.println(JSON.toJSONString(users));
  } 

  @Test
  public void testFindUserByLtId(){
    List<User> users = manager.findUserByLtId(5);
    System.out.println(JSON.toJSONString(users));
  } 

  @Test
  public void testFindUserBetweenId(){
    List<User> users = manager.findUserBetweenId(4, 9);
    System.out.println(JSON.toJSONString(users));
  } 

  @Test
  public void testFindUserAndOrder(){
    Page<User> users = manager.findUserAndOrder(1);
    System.out.println(JSON.toJSONString(users));
  } 

  @Test
  public void testFindUserAndOrderSecondMethod(){
    List<User> users = manager.findUserAndOrderSecondMethod(1);
    System.out.println(JSON.toJSONString(users));
  }
}

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
   */
  @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的一级缓存了, 不然你在同一接口中,更新一个对象,接着查询这个对象,那么你查出来的这个对象还是之前的没有更新之前的状态

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext-config.xml" })
@TransactionConfiguration(defaultRollback = false)
@Transactional
public class UserDefineBySelfTest {
  @Autowired
  private UserDefineBySelf dao; 

  @Test
  public void testFindUserByName(){
    User user = dao.findUserByName("chhliu");
    Assert.assertEquals("chhliu", user.getName());
    System.out.println(user.getName());
  } 

  @Test
  public void testFindUserByEmail(){
    User user = dao.findUserByEmail("chhliu@.com");
    Assert.assertEquals("chhliu", user.getName());
    System.out.println(user.getName());
  } 

  @Test
  public void testUpdateUserById(){
    dao.updateUserById("tanjie", 4);
  }
}

从测试代码可以看出,我们同样只定义了接口,没有任何的实现类,但是却实现了我们所需要的功能。

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

测试类(注释部分为实际发送的sql语句):

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:applicationContext-config.xml" })
@TransactionConfiguration(defaultRollback = false)
@Transactional
public class SimpleConditionQueryRepositoryTest {
  @Autowired
  private SimpleConditionQueryRepository dao; 

  /**
   * select
    user0_.id as id0_,
    user0_.account as account0_,
    user0_.email as email0_,
    user0_.name as name0_,
    user0_.password as password0_
  from
    USER user0_
  where
    user0_.name=?
    and user0_.email=? limit ?
   */
  @Test
  public void testFindUserByNameAndEmail(){
    User user = dao.findByNameAndEmail("chhliu", "chhliu@.com");
    System.out.println(JSON.toJSONString(user));
  } 

  /**
   * select
    user0_.id as id1_,
    user0_.account as account1_,
    user0_.email as email1_,
    user0_.name as name1_,
    user0_.password as password1_
  from
    USER user0_
  where
    user0_.name=?
    or user0_.password=?
   */
  @Test
  public void testFindUserByNameOrPassword(){
    List<User> users = dao.findByNameOrPassword("chhliu", "123456");
    System.out.println(JSON.toJSONString(users));
  } 

  /**
   * select
    user0_.id as id1_,
    user0_.account as account1_,
    user0_.email as email1_,
    user0_.name as name1_,
    user0_.password as password1_
  from
    USER user0_
  where
    user0_.id between ? and ?
   */
  @Test
  public void testFindByIdBetween(){
    List<User> users = dao.findByIdBetween(5, 8);
    System.out.println(JSON.toJSONString(users));
  } 

  /**
   * select
    user0_.id as id1_,
    user0_.account as account1_,
    user0_.email as email1_,
    user0_.name as name1_,
    user0_.password as password1_
  from
    USER user0_
  where
    user0_.id<?
   */
  @Test
  public void testFindByIdLessThan(){
    List<User> users = dao.findByIdLessThan(4);
    System.out.println(JSON.toJSONString(users));
  } 

  /**
   * select
    user0_.id as id0_,
    user0_.account as account0_,
    user0_.email as email0_,
    user0_.name as name0_,
    user0_.password as password0_
  from
    USER user0_
  where
    user0_.id>?
   */
  @Test
  public void testFindByIdGreaterThan(){
    List<User> users = dao.findByIdGreaterThan(6);
    System.out.println(JSON.toJSONString(users));
  } 

  /**
   * select
    user0_.id as id0_,
    user0_.account as account0_,
    user0_.email as email0_,
    user0_.name as name0_,
    user0_.password as password0_
  from
    USER user0_
  where
    user0_.name is null
   */
  @Test
  public void testFindByNameIsNull(){
    List<User> users = dao.findByNameIsNull();
    System.out.println(JSON.toJSONString(users));
  } 

  /**
   * select
    user0_.id as id1_,
    user0_.account as account1_,
    user0_.email as email1_,
    user0_.name as name1_,
    user0_.password as password1_
  from
    USER user0_
  where
    user0_.name is not null
   */
  @Test
  public void testFindByNameIsNotNull(){
    List<User> users = dao.findByNameIsNotNull();
    System.out.println(JSON.toJSONString(users));
  } 

  /**
   * select
    user0_.id as id1_,
    user0_.account as account1_,
    user0_.email as email1_,
    user0_.name as name1_,
    user0_.password as password1_
  from
    USER user0_
  where
    user0_.name like ?
   */
  @Test
  public void testFindByNameLike(){
    List<User> users = dao.findByNameLike("chhliu");
    System.out.println(JSON.toJSONString(users));
  } 

  /**
   * select
    user0_.id as id0_,
    user0_.account as account0_,
    user0_.email as email0_,
    user0_.name as name0_,
    user0_.password as password0_
  from
    USER user0_
  where
    user0_.name not like ?
   */
  @Test
  public void testFindByNameNotLike(){
    List<User> users = dao.findByNameNotLike("chhliu");
    System.out.println(JSON.toJSONString(users));
  } 

  /**
   * select
    user0_.id as id0_,
    user0_.account as account0_,
    user0_.email as email0_,
    user0_.name as name0_,
    user0_.password as password0_
  from
    USER user0_
  where
    user0_.password=?
  order by
    user0_.id desc
   */
  @Test
  public void testFindByPasswordOrderByIdDesc(){
    List<User> users = dao.findByPasswordOrderByIdDesc("123456");
    System.out.println(JSON.toJSONString(users));
  } 

  /**
   * select
    user0_.id as id1_,
    user0_.account as account1_,
    user0_.email as email1_,
    user0_.name as name1_,
    user0_.password as password1_
  from
    USER user0_
  where
    user0_.name<>?
   */
  @Test
  public void testFindByNameNot(){
    List<User> users = dao.findByNameNot("chhliu");
    System.out.println(JSON.toJSONString(users));
  } 

  /**
   * select
    user0_.id as id1_,
    user0_.account as account1_,
    user0_.email as email1_,
    user0_.name as name1_,
    user0_.password as password1_
  from
    USER user0_
  where
    user0_.id in (
      ? , ? , ? , ?
    )
   */
  @Test
  public void testFindByIdIn(){
    List<User> users = dao.findByIdIn(new ArrayList<Integer>(Arrays.asList(3,4,6,8)));
    System.out.println(JSON.toJSONString(users));
  } 

  /**
   * select
    user0_.id as id0_,
    user0_.account as account0_,
    user0_.email as email0_,
    user0_.name as name0_,
    user0_.password as password0_
  from
    USER user0_
  where
    user0_.id not in (
      ? , ? , ? , ?
    )
   */
  @Test
  public void testFindByIdNotIn(){
    List<User> users = dao.findByIdNotIn(new ArrayList<Integer>(Arrays.asList(3,4,6,8)));
    System.out.println(JSON.toJSONString(users));
  }
}

这里,我们只定义了一个接口,接口里面只有方法,但是没有任何的实现,却完成了各种操作。

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

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

And --- 等价于 SQL 中的 and 关键字,比如 findByUsernameAndPassword(String user, Striang pwd)

Or --- 等价于 SQL 中的 or 关键字,比如 findByUsernameOrAddress(String user, String addr)

Between --- 等价于 SQL 中的 between 关键字,比如 findBySalaryBetween(int max, int min)

LessThan --- 等价于 SQL 中的 "<",比如 findBySalaryLessThan(int max)

GreaterThan --- 等价于 SQL 中的">",比如 findBySalaryGreaterThan(int min)

IsNull --- 等价于 SQL 中的 "is null",比如 findByUsernameIsNull()

IsNotNull --- 等价于 SQL 中的 "is not null",比如 findByUsernameIsNotNull()

NotNull --- 与 IsNotNull 等价

Like --- 等价于 SQL 中的 "like",比如 findByUsernameLike(String user)

NotLike --- 等价于 SQL 中的 "not like",比如 findByUsernameNotLike(String user)

OrderBy ---等价于 SQL 中的 "order by",比如 findByUsernameOrderBySalaryAsc(String user)

Not --- 等价于 SQL 中的 "! =",比如 findByUsernameNot(String user)

In --- 等价于 SQL 中的 "in",比如 findByUsernameIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数

NotIn --- 等价于 SQL 中的 "not in",比如 findByUsernameNotIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数

五、创建查询的顺序

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的时候,只是定义了接口,在使用的时候,直接注入就可以了,并没有做与事物相关的任何处理,但实际上,事物已经起到效果了,这又是为什么了?

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

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

您可能感兴趣的文章:

  • Spring Data JPA实现动态查询的两种方法
  • Spring Data JPA实现分页Pageable的实例代码
  • Spring Data JPA使用Sort进行排序(Using Sort)
  • Spring Data JPA 复杂/多条件组合分页查询
  • Spring Data JPA实现动态条件与范围查询实例代码
  • Spring Data JPA调用存储过程实例代码
  • SpringData JPA实现查询分页demo
  • Spring Data JPA中的动态查询实例
(0)

相关推荐

  • Spring Data JPA实现动态查询的两种方法

    前言 一般在写业务接口的过程中,很有可能需要实现可以动态组合各种查询条件的接口.如果我们根据一种查询条件组合一个方法的做法来写,那么将会有大量方法存在,繁琐,维护起来相当困难.想要实现动态查询,其实就是要实现拼接SQL语句.无论实现如何复杂,基本都是包括select的字段,from或者join的表,where或者having的条件.在Spring Data JPA有两种方法可以实现查询条件的动态查询,两种方法都用到了Criteria API. Criteria API 这套API可用于构建对数据

  • SpringData JPA实现查询分页demo

    SpringData JPA 的 PagingAndSortingRepository接口已经提供了对分页的支持,查询的时候我们只需要传入一个 org.springframework.data.domain.Pageable 接口的实现类,指定PageNumber和pageSize即可 springData包中的 PageRequest类已经实现了Pageable接口,我们可以直接使用下边是部分代码: DAO: package com.jiaoyiping.jdjy.sourcecode.dao

  • Spring Data JPA实现动态条件与范围查询实例代码

    Spring Data JPA为我们提供了Query With Example来实现动态条件查询,当查询条件为空的时候,我们不用做大量的条件判断.但是Query With Example却不支持范围查询(包括日期范围,数值范围查询),本文通过Specification实现了既支持动态条件查询又支持范围查询的方法. 1 实现方式 1.1 范围对象Range定义 import java.io.Serializable; public class Range<E> implements Serial

  • Spring Data JPA中的动态查询实例

    spring Data JPA大大的简化了我们持久层的开发,但是实际应用中,我们还是需要动态查询的. 比如,前端有多个条件,这些条件很多都是可选的,那么后端的SQL,就应该是可以定制的,在使用hibernate的时候,可以通过判断条件来拼接SQL(HQL),当然,Spring Data JPA在简化我们开发的同时,也是提供了支持的. 通过实现Criteria二实现的动态查询,需要我们的Repo接口继承JpaSpecificationExecutor接口,这是个泛型接口. 然后查询的时候,传入动态

  • Spring Data JPA调用存储过程实例代码

    JPA连接到数据库,调用存储过程,这样的需求很常见.本文就针对这一点,讲述如何使用spring Data JPA调用存储过程的方法. 1.存储过程 假设存储过程如下: CREATE OR REPLACE PACKAGE test_pkg AS PROCEDURE in_only_test (inParam1 IN VARCHAR2); PROCEDURE in_and_out_test (inParam1 IN VARCHAR2, outParam1 OUT VARCHAR2); END tes

  • Spring Data JPA 复杂/多条件组合分页查询

    话不多说,请看代码: public Map<String, Object> getWeeklyBySearch(final Map<String, String> serArgs, String pageNum, String pageSize) throws Exception { // TODO Auto-generated method stub Map<String,Object> resultMap=new HashMap<String, Object&

  • Spring Data JPA实现分页Pageable的实例代码

    在JPA中提供了很方便的分页功能,那就是Pageable(org.springframework.data.domain.Pageable)以及它的实现类PageRequest(org.springframework.data.domain.PageRequest),详细的可以见示例代码. 1.改变CustomerRepository方法​ /** * 一个参数,匹配两个字段 * @param name2 * @Param pageable 分页参数 * @return * 这里Param的值和

  • Spring Data JPA使用Sort进行排序(Using Sort)

    通过上一节的学习,我们知道了如何用@Query注解来实现灵活的查询.在上一节的示例中,我也尝试给出简单的排序,通过JPQL语句以及原生SQL来实现的.这样的实现,虽然在一定程度上可以应用,但是灵活度不够,因此结合@Query注解,我们可以使用Sort来对结果进行排序. 1.在CustomerRepository内添加方法 /** * 一个参数,匹配两个字段 * @param name2 * @param sort 指定排序的参数,可以根据需要进行调整 * @return * 这里Param的值和

  • spring data jpa使用详解(推荐)

    使用Spring data JPA开发已经有一段时间了,这期间学习了一些东西,也遇到了一些问题,在这里和大家分享一下. 前言: Spring data简介: Spring Data是一个用于简化数据库访问,并支持云服务的开源框架.其主要目标是使得对数据的访问变得方便快捷,并支持map-reduce框架和云计算数据服务. Spring Data 包含多个子项目: Commons - 提供共享的基础框架,适合各个子项目使用,支持跨数据库持久化 JPA - 简化创建 JPA 数据访问层和跨存储的持久层

  • Springboot集成spring data elasticsearch过程详解

    版本对照 各版本的文档说明:https://docs.spring.io/spring-data/elasticsearch/docs/ 1.在application.yml中添加配置 spring: data: elasticsearch: repositories: enabled: true #多实例集群扩展时需要配置以下两个参数 #cluster-name: datab-search #cluster-nodes: 127.0.0.1:9300,127.0.0.1:9301 2.添加 M

  • 详谈hibernate,jpa与spring data jpa三者之间的关系

    目录 前提 文字说明 CRUD操作 前提 其实很多框架都是对另一个框架的封装,我们在学习类似的框架的时候,难免会进入误区,所以我们就应该对其进行总结归纳,对比.本文就是对hibernate,jpa,spring data jpa三者之间进行文字对比,以及对其三者分别进行CRUD操作. 文字说明 Hibernate Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,是一个全自动的orm框架,hibernate可以自动生

  • Spring Data JPA 简单查询--方法定义规则(详解)

    一.常用规则速查 1 And 并且 2 Or   或 3 Is,Equals 等于 4 Between   两者之间 5 LessThan 小于 6 LessThanEqual   小于等于 7 GreaterThan 大于 8 GreaterThanEqual   大于等于 9 After 之后(时间) > 10 Before 之前(时间) < 11 IsNull 等于Null 12 IsNotNull,NotNull 不等于Null 13 Like 模糊查询.查询件中需要自己加 % 14

  • Spring Data Jpa的四种查询方式详解

    这篇文章主要介绍了Spring Data Jpa的四种查询方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.调用接口的方式 1.基本介绍 通过调用接口里的方法查询,需要我们自定义的接口继承Spring Data Jpa规定的接口 public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> 使用这

  • Spring data jpa的使用与详解(复杂动态查询及分页,排序)

    一. 使用Specification实现复杂查询 (1) 什么是Specification Specification是springDateJpa中的一个接口,他是用于当jpa的一些基本CRUD操作的扩展,可以把他理解成一个spring jpa的复杂查询接口.其次我们需要了解Criteria 查询,这是是一种类型安全和更面向对象的查询.而Spring Data JPA支持JPA2.0的Criteria查询,相应的接口是JpaSpecificationExecutor. 而JpaSpecifica

  • Spring Data JPA框架的Repository自定义实现详解

    目录 1. Spring Data Repository自定义实现 1.1 自定义特殊repository 1.2 配置类 1.3 解决歧义 1.4 手动装配 1.5 自定义Base Repository 1. Spring Data Repository自定义实现 Spring Data提供了各种选项来创建查询方法,只需少量编码.但是当这些选项不能满足你的需求时,你也可以为资源库方法提供你自己的自定义实现.本节主要介绍如何做到这一点. 1.1 自定义特殊repository 要用自定义的功能实

  • 详解Spring Data JPA中Repository的接口查询方法

    目录 1.查询方法定义详解 2.搜索查询策略 3.查询创建 4.属性表达式 5.特殊参数处理 6.限制查询结果 7. repository方法返回Collections or Iterables 8.repository方法处理Null 9.查询结果流 10.异步查询结果 1.查询方法定义详解 repository代理有两种方式从方法名中派生出特定存储查询. 通过直接从方法名派生查询. 通过使用一个手动定义的查询. 可用的选项取决于实际的商店.然而,必须有一个策略来决定创建什么实际的查询. 2.

  • Spring Data JPA注解Entity使用示例详解

    目录 1.JPA协议中关于Entity的相关规定 需要注意的是: 2.常用注解 2.1 JPA支持的注解 2.2 常用注解 3.联合主键 3.1 @IdClass 3.2 @Embeddable与@EmbeddedId注解使用 3.3 两者的区别是什么? 1.JPA协议中关于Entity的相关规定 (1)实体是直接进行数据库持久化操作的领域对象(即一个简单的POJO),必须通过@Entity注解进行标示. (2)实体必须有一个 public 或者 projected的无参数构造方法. (3)持久

  • Spring Data JPA 注解Entity关联关系使用详解

    目录 1.OneToOne关联关系 1.1 解读OneToOne源码 1.2 mappedBy 注意事项 1.3 CascadeType 用法 1.4 orphanRemoval属性用法 1.5 orphanRemoval 和 CascadeType.REMOVE的区别 2.@JoinColumns & @JoinColumn 3.@ManyToOne & @OneToMany 3.1 Lazy机制 4.ManyToMany 4.1 利用@ManyToOne 和 @OneToMany表达多

随机推荐