使用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提供给用户使用的,主要有以下几个接口:
Repository:仅仅是一个标识,表明任何继承它的均为仓库接口类,方便Spring自动扫描识别
CrudRepository:继承Repository,实现了一组CRUD相关的方法
PagingAndSortingRepository:继承CrudRepository,实现了一组分页排序相关的方法
JpaRepository:继承PagingAndSortingRepository,实现一组JPA规范相关的方法
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); }
@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 指定事务属性,这主要针对一个业务层方法多次调用持久层方法的情况。持久层的事务会根据设置的事务传播行为来决定是挂起业务层事务还是加入业务层的事务。
结尾:如果有需要使用示例源码的同事,可以与我联系924580006@qq.com