使用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类:

[java] 
1. @Entity  
2. @Table(name="USER")  
3. public class User {  
4. @Id  
5. @GeneratedValue  
6. private Integer id;  
7.       
8. //账号  
9. private String account;  
10.       
11. //姓名  
12. private String name;  
13.       
14. //密码  
15. private String password;  
16.       
17. // 邮箱  
18. private String email;  
19. }

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

[java] 
1. public interface UserRepository extends CrudRepository<User, Integer> {  
2.       
3. }

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

[java] 
1. public class UserRepositoryTest {  
2. @Autowired  
3. private UserRepository dao;  
4.       
5. @Test//保存  
6. public void testSave(){  
7. new User();  
8. "chhliu");  
9. "10000");  
10. "chhliu@.com");  
11. "123456");  
12.         dao.save(user);  
13.     }  
14.       
15. @Test//批量保存  
16. public void testSave1(){  
17. new ArrayList<User>();  
18. new User();  
19. "tanjie");  
20. "10000");  
21. "tanjie@.com");  
22. "123456");  
23.         users.add(user);  
24. new User();  
25. "esdong");  
26. "10000");  
27. "esdong@.com");  
28. "123456");  
29.         users.add(user);  
30. new User();  
31. "qinhongfei");  
32. "10000");  
33. "qinhongfei@.com");  
34. "123456");  
35.         users.add(user);  
36. new User();  
37. "huizhang");  
38. "10000");  
39. "huizhang@.com");  
40. "123456");  
41.         users.add(user);  
42. new User();  
43. "caican");  
44. "10000");  
45. "caican@.com");  
46. "123456");  
47.         users.add(user);  
48.         dao.save(users);  
49.     }  
50.       
51. @Test//更新  
52. public void testUpdate(){  
53. 1);  
54. "123890");// 要想这样实现更新的功能,需要在service层加上@Transaction事物注解  
55.     }  
56.       
57. @Test//删除  
58. public void testDelete(){  
59. 2);  
60.     }  
61.       
62. @Test//查询所有  
63. public void testFindAll(){  
64.         List<User> users = (List<User>) dao.findAll();  
65.         System.out.println(JSON.toJSONString(users));  
66.     }  
67.       
68. @Test//判断指定的id对象是否存在  
69. public void testIsExist(){  
70. boolean isExist = dao.exists(8);  
71.         System.out.println(isExist);  
72.     }  
73.       
74. @Test//通过id列表来查询  
75. public void testFindUserByIds(){  
76. new ArrayList<Integer>();  
77. 2);  
78. 4);  
79. 7);  
80.         List<User> users = (List<User>) dao.findAll(listIds);  
81.         System.out.println(JSON.toJSONString(users));  
82.     }  
83. }

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

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

[java]
1. @NoRepositoryBean  
2. public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {  
3. extends T> S save(S entity);//保存  
4. extends T> Iterable<S> save(Iterable<S> entities);//批量保存  
5. //根据id查询一个对象  
6. boolean exists(ID id);//判断对象是否存在  
7. //查询所有的对象  
8. //根据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. }

2、PagingAndSortingRepository接口

PagingAndSortingRepository接口继承了CrudRepository接口。

编写接口,并继承PagingAndSortingRepository接口

[java] 
1. public interface UserRepositoryWithOrder extends  
2.         PagingAndSortingRepository<User, Integer> {  
3.   
4. }

编写测试类:

[java] 
1. @RunWith(SpringJUnit4ClassRunner.class)  
2. @ContextConfiguration(locations = { "classpath:applicationContext-config.xml" })  
3. @TransactionConfiguration(defaultRollback = false)  
4. @Transactional  
5. public class UserRepositoryWithOrderTest {  
6. @Autowired  
7. private UserRepositoryWithOrder dao;  
8.       
9. @Test  
10. public void testOrder(){  
11. new Sort(Direction.DESC, "id");  
12. new PageRequest(0, 5, sort);  
13.         Page<User> page = dao.findAll(pageable);  
14.         System.out.println(JSON.toJSONString(page));  
15.         System.out.println(page.getSize());  
16.     }  
17. }

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

[java] 
1. @NoRepositoryBean  
2. public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {  
3. // 不带分页的排序  
4. // 带分页的排序  
5. }

3、JpaRepository接口

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

接口定义如下:

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

4、JpaSpecificationExecutor接口

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

编写接口如下:

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

Service类:

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


测试类:

[java] 
1. @RunWith(SpringJUnit4ClassRunner.class)  
2. @ContextConfiguration(locations = { "classpath:applicationContext-config.xml" })  
3. @TransactionConfiguration(defaultRollback = false)  
4. @Transactional  
5. public class SpecificationExecutorRepositoryManagerTest {  
6. @Autowired  
7. private SpecificationExecutorRepositoryManager manager;  
8. @Test  
9. public void testFindUserByName(){  
10. "chhliu");  
11.         System.out.println(JSON.toJSONString(user));  
12.     }  
13.       
14. @Test  
15. public void testFindUserByNameAndEmail(){  
16. "chhliu", "chhliu@.com");  
17.         System.out.println(JSON.toJSONString(user));  
18.     }  
19.       
20. @Test  
21. public void testFindUserByUserVo(){  
22. new User();  
23. "chhliu");  
24. "chhliu@.com");  
25.         User u = manager.findUserByUser(user);  
26.         System.out.println(JSON.toJSONString(u));  
27.     }  
28.       
29. @Test  
30. public void testFindUserByIds(){  
31. new ArrayList<Integer>(Arrays.asList(1,3,5,6)));  
32.         System.out.println(JSON.toJSONString(users));  
33.     }  
34.       
35. @Test  
36. public void testFindUserByGtId(){  
37. 5);  
38.         System.out.println(JSON.toJSONString(users));  
39.     }  
40.       
41. @Test  
42. public void testFindUserByLtId(){  
43. 5);  
44.         System.out.println(JSON.toJSONString(users));  
45.     }  
46.       
47. @Test  
48. public void testFindUserBetweenId(){  
49. 4, 9);  
50.         System.out.println(JSON.toJSONString(users));  
51.     }  
52.       
53. @Test  
54. public void testFindUserAndOrder(){  
55. 1);  
56.         System.out.println(JSON.toJSONString(users));  
57.     }  
58.       
59. @Test  
60. public void testFindUserAndOrderSecondMethod(){  
61. 1);  
62.         System.out.println(JSON.toJSONString(users));  
63.     }  
64. }

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 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询操作。

编写接口,如下:

[java] 
1. /** 
2.  * 描述:自定义查询,当Spring Data JPA无法提供时,需要自定义接口,此时可以使用这种方式 
3.  */  
4. public interface UserDefineBySelf extends JpaRepository<User, Integer> {  
5. /** 
6.      * 命名参数 
7.      * 描述:推荐使用这种方法,可以不用管参数的位置 
8.      */  
9. @Query("select u from User u where u.name = :name")  
10. @Param("name") String name);  
11.       
12. /** 
13.      * 索引参数 
14.      * 描述:使用?占位符 
15.      */  
16. @Query("select u from User u where u.email = ?1")// 1表示第一个参数  
17.     User findUserByEmail(String email);  
18.       
19. /** 
20.      * 描述:可以通过@Modifying和@Query来实现更新 
21.      * 注意:Modifying queries的返回值只能为void或者是int/Integer 
22.      */  
23. @Modifying  
24. @Query("update User u set u.name = :name where u.id = :id")  
25. int updateUserById(@Param("name") String name, @Param("id") int id);  
26. }

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

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

测试类:

[java] 
1. @RunWith(SpringJUnit4ClassRunner.class)  
2. @ContextConfiguration(locations = { "classpath:applicationContext-config.xml" })  
3. @TransactionConfiguration(defaultRollback = false)  
4. @Transactional  
5. public class UserDefineBySelfTest {  
6. @Autowired  
7. private UserDefineBySelf dao;  
8.       
9. @Test  
10. public void testFindUserByName(){  
11. "chhliu");  
12. "chhliu", user.getName());  
13.         System.out.println(user.getName());  
14.     }  
15.       
16. @Test  
17. public void testFindUserByEmail(){  
18. "chhliu@.com");  
19. "chhliu", user.getName());  
20.         System.out.println(user.getName());  
21.     }  
22.       
23. @Test  
24. public void testUpdateUserById(){  
25. "tanjie", 4);  
26.     }  
27. }

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

2、使用@NamedQueries创建查询

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

编写接口:

[java] 
1. public interface FindUserByNamedQueryRepository extends JpaRepository<User, Integer> {  
2. @Param("name") String name);  
3. }

编写类:

[java] 
1. @Entity  
2. @NamedQueries(value={  
3. @NamedQuery(name="User.findUserWithName",query="select u from User u where u.name = :name")  
4. })  
5. // 注意:此处如果是多个方法,那么需要使用@NamedQueries,如果只有一个方法,则可以使用@NamedQuery,写法如下:@NamedQuery(name="User.findUserWithName",query="select u from User u where u.name = :name")  
6. public class FindUserByNamedQuery {  
7. /** 
8.      * 注意:此处必须要给这个实体类定义一个唯一标识,否则会报异常 
9.      */  
10. @Id  
11. @GeneratedValue  
12. private Integer id;  
13. }

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

测试类:

[java]
1. @RunWith(SpringJUnit4ClassRunner.class)  
2. @ContextConfiguration(locations = { "classpath:applicationContext-config.xml" })  
3. @TransactionConfiguration(defaultRollback = false)  
4. @Transactional  
5. public class FindUserByNamedQueryRepositoryTest {  
6. @Autowired  
7. private FindUserByNamedQueryRepository dao;  
8.       
9. @Test  
10. public void testFindUserByName(){  
11. "caican");  
12.         System.out.println(JSON.toJSONString(user));  
13.     }  
14. }

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

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

编写接口:

[java] 
1. public interface SimpleConditionQueryRepository extends JpaRepository<User, Integer> {  
2. /** 
3.      * 说明:按照Spring data 定义的规则,查询方法以find|read|get开头 
4.      * 涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写 
5.      */  
6.       
7.       
8.       
9. /** 
10.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name = :name and u.email = :email 
11.      * 参数名大写,条件名首字母大写,并且接口名中参数出现的顺序必须和参数列表中的参数顺序一致 
12.      */  
13.     User findByNameAndEmail(String name, String email);  
14.       
15. /** 
16.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name = ?1 or u.password = ?2 
17.      */  
18.     List<User> findByNameOrPassword(String name, String password);  
19.       
20. /** 
21.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.id between ?1 and ?2 
22.      */  
23.     List<User> findByIdBetween(Integer start, Integer end);  
24.       
25. /** 
26.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.id < ?1 
27.      */  
28.     List<User> findByIdLessThan(Integer end);  
29.       
30. /** 
31.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.id > ?1 
32.      */  
33.     List<User> findByIdGreaterThan(Integer start);  
34.       
35. /** 
36.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name is null 
37.      */  
38.     List<User> findByNameIsNull();  
39.       
40. /** 
41.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name is not null 
42.      */  
43.     List<User> findByNameIsNotNull();  
44.       
45. /** 
46.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name like ?1 
47.      */  
48.     List<User> findByNameLike(String name);  
49.       
50. /** 
51.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name not like ?1 
52.      */  
53.     List<User> findByNameNotLike(String name);  
54.       
55. /** 
56.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.password = ?1 order by u.id desc 
57.      */  
58.     List<User> findByPasswordOrderByIdDesc(String password);  
59.       
60. /** 
61.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.name <> ?1 
62.      */  
63.     List<User> findByNameNot(String name);  
64.       
65. /** 
66.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.id in ?1 
67.      */  
68.     List<User> findByIdIn(List<Integer> ids);  
69.       
70. /** 
71.      * 注:此处这个接口相当于发送了一条SQL:select u from User u where u.id not in ?1 
72.      */  
73.     List<User> findByIdNotIn(List<Integer> ids);  
74. }
  1.  

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

[java] 
1. @RunWith(SpringJUnit4ClassRunner.class)  
2. @ContextConfiguration(locations = { "classpath:applicationContext-config.xml" })  
3. @TransactionConfiguration(defaultRollback = false)  
4. @Transactional  
5. public class SimpleConditionQueryRepositoryTest {  
6. @Autowired  
7. private SimpleConditionQueryRepository dao;  
8.       
9. /** 
10.      * select 
11.         user0_.id as id0_, 
12.         user0_.account as account0_, 
13.         user0_.email as email0_, 
14.         user0_.name as name0_, 
15.         user0_.password as password0_  
16.     from 
17.         USER user0_  
18.     where 
19.         user0_.name=?  
20.         and user0_.email=? limit ? 
21.      */  
22. @Test  
23. public void testFindUserByNameAndEmail(){  
24. "chhliu", "chhliu@.com");  
25.         System.out.println(JSON.toJSONString(user));  
26.     }  
27.       
28. /** 
29.      * select 
30.         user0_.id as id1_, 
31.         user0_.account as account1_, 
32.         user0_.email as email1_, 
33.         user0_.name as name1_, 
34.         user0_.password as password1_  
35.     from 
36.         USER user0_  
37.     where 
38.         user0_.name=?  
39.         or user0_.password=? 
40.      */  
41. @Test  
42. public void testFindUserByNameOrPassword(){  
43. "chhliu", "123456");  
44.         System.out.println(JSON.toJSONString(users));  
45.     }  
46.       
47. /** 
48.      * select 
49.         user0_.id as id1_, 
50.         user0_.account as account1_, 
51.         user0_.email as email1_, 
52.         user0_.name as name1_, 
53.         user0_.password as password1_  
54.     from 
55.         USER user0_  
56.     where 
57.         user0_.id between ? and ? 
58.      */  
59. @Test  
60. public void testFindByIdBetween(){  
61. 5, 8);  
62.         System.out.println(JSON.toJSONString(users));  
63.     }  
64.       
65. /** 
66.      * select 
67.         user0_.id as id1_, 
68.         user0_.account as account1_, 
69.         user0_.email as email1_, 
70.         user0_.name as name1_, 
71.         user0_.password as password1_  
72.     from 
73.         USER user0_  
74.     where 
75.         user0_.id<? 
76.      */  
77. @Test  
78. public void testFindByIdLessThan(){  
79. 4);  
80.         System.out.println(JSON.toJSONString(users));  
81.     }  
82.       
83. /** 
84.      * select 
85.         user0_.id as id0_, 
86.         user0_.account as account0_, 
87.         user0_.email as email0_, 
88.         user0_.name as name0_, 
89.         user0_.password as password0_  
90.     from 
91.         USER user0_  
92.     where 
93.         user0_.id>? 
94.      */  
95. @Test  
96. public void testFindByIdGreaterThan(){  
97. 6);  
98.         System.out.println(JSON.toJSONString(users));  
99.     }  
100.       
101. /** 
102.      * select 
103.         user0_.id as id0_, 
104.         user0_.account as account0_, 
105.         user0_.email as email0_, 
106.         user0_.name as name0_, 
107.         user0_.password as password0_  
108.     from 
109.         USER user0_  
110.     where 
111.         user0_.name is null 
112.      */  
113. @Test  
114. public void testFindByNameIsNull(){  
115.         List<User> users = dao.findByNameIsNull();  
116.         System.out.println(JSON.toJSONString(users));  
117.     }  
118.       
119. /** 
120.      * select 
121.         user0_.id as id1_, 
122.         user0_.account as account1_, 
123.         user0_.email as email1_, 
124.         user0_.name as name1_, 
125.         user0_.password as password1_  
126.     from 
127.         USER user0_  
128.     where 
129.         user0_.name is not null 
130.      */  
131. @Test  
132. public void testFindByNameIsNotNull(){  
133.         List<User> users = dao.findByNameIsNotNull();  
134.         System.out.println(JSON.toJSONString(users));  
135.     }  
136.       
137. /** 
138.      * select 
139.         user0_.id as id1_, 
140.         user0_.account as account1_, 
141.         user0_.email as email1_, 
142.         user0_.name as name1_, 
143.         user0_.password as password1_  
144.     from 
145.         USER user0_  
146.     where 
147.         user0_.name like ? 
148.      */  
149. @Test  
150. public void testFindByNameLike(){  
151. "chhliu");  
152.         System.out.println(JSON.toJSONString(users));  
153.     }  
154.       
155. /** 
156.      * select 
157.         user0_.id as id0_, 
158.         user0_.account as account0_, 
159.         user0_.email as email0_, 
160.         user0_.name as name0_, 
161.         user0_.password as password0_  
162.     from 
163.         USER user0_  
164.     where 
165.         user0_.name not like ? 
166.      */  
167. @Test  
168. public void testFindByNameNotLike(){  
169. "chhliu");  
170.         System.out.println(JSON.toJSONString(users));  
171.     }  
172.       
173. /** 
174.      * select 
175.         user0_.id as id0_, 
176.         user0_.account as account0_, 
177.         user0_.email as email0_, 
178.         user0_.name as name0_, 
179.         user0_.password as password0_  
180.     from 
181.         USER user0_  
182.     where 
183.         user0_.password=?  
184.     order by 
185.         user0_.id desc 
186.      */  
187. @Test  
188. public void testFindByPasswordOrderByIdDesc(){  
189. "123456");  
190.         System.out.println(JSON.toJSONString(users));  
191.     }  
192.       
193. /** 
194.      * select 
195.         user0_.id as id1_, 
196.         user0_.account as account1_, 
197.         user0_.email as email1_, 
198.         user0_.name as name1_, 
199.         user0_.password as password1_  
200.     from 
201.         USER user0_  
202.     where 
203.         user0_.name<>? 
204.      */  
205. @Test  
206. public void testFindByNameNot(){  
207. "chhliu");  
208.         System.out.println(JSON.toJSONString(users));  
209.     }  
210.       
211. /** 
212.      * select 
213.         user0_.id as id1_, 
214.         user0_.account as account1_, 
215.         user0_.email as email1_, 
216.         user0_.name as name1_, 
217.         user0_.password as password1_  
218.     from 
219.         USER user0_  
220.     where 
221.         user0_.id in ( 
222.             ? , ? , ? , ? 
223.         ) 
224.      */  
225. @Test  
226. public void testFindByIdIn(){  
227. new ArrayList<Integer>(Arrays.asList(3,4,6,8)));  
228.         System.out.println(JSON.toJSONString(users));  
229.     }  
230.       
231. /** 
232.      * select 
233.         user0_.id as id0_, 
234.         user0_.account as account0_, 
235.         user0_.email as email0_, 
236.         user0_.name as name0_, 
237.         user0_.password as password0_  
238.     from 
239.         USER user0_  
240.     where 
241.         user0_.id not in  ( 
242.             ? , ? , ? , ? 
243.         ) 
244.      */  
245. @Test  
246. public void testFindByIdNotIn(){  
247. new ArrayList<Integer>(Arrays.asList(3,4,6,8)));  
248.         System.out.println(JSON.toJSONString(users));  
249.     }  
250. }

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

看到这里,估计很多人都会问,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 指定事务属性,这主要针对一个业务层方法多次调用持久层方法的情况。持久层的事务会根据设置的事务传播行为来决定是挂起业务层事务还是加入业务层的事务。