Repository: 是 spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法
仅仅是一个标识,表明任何继承它的均为仓库接口类,方便Spring自动扫描识别,
@Indexed public interface Repository<T, ID> { }
T :实体类名 ID : 主键类型
CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID>
Iterable<T> findAll(Sort sort); Page<T> findAll(Pageable pageable); }
使用举例:
如果要在以20为一页的结果中,获取第2页结果,则如下使用:
Page<User> users = repository.findAll(new PageRequest(1, 20));
JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法
自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。
JpaSpecificationExecutor: 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法 。
在使用SpringData时只需要定义Dao层接口及定义方法就可以操作数据库。但是,这个Dao层接口中的方法也是有定义规范的,只有按这个规范来,SpringData才能识别并实现该方法。下面来说说方法定义的规范。
(1)简单的条件查询的方法定义规范
方法定义规范如下:
- 简单条件查询:查询某一个实体或者集合
- 按照SpringData规范,查询方法于find|read|get开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:属性首字母需要大写。
- 支持属性的级联查询;若当前类有符合条件的属性, 则优先使用, 而不使用级联属性。 若需要使用级联属性, 则属性之间使用 _ 进行连接。
(2)支持的关键字
直接在接口中定义方法,如果符合规范,则不用写实现。目前支持的关键字写法如下:
(3)属性级联查询的案例
- 修改Person类,添加address属性,使Person和Address成多对一的关系,设置外键列名为address_id ,添加的代码如下图
- 在PersonDao接口中定义一个方法,代码如下:
// 级联查询,查询address的id等于条件值/ List<Person> findByAddressId(Integer addressId);
运行测试方法
/** 测试findByAddressId方法 */ @Test PersonDao personDao = ctx.getBean(PersonDao.class); // 查出地址id为1的person集合 List<Person> list = personDao.findByAddressId(1); for (Person person : list) { System.out.println(person.getName() + "---addressId=" + person.getAddress().getId()); } }
(4)查询方法解析流程
。这里再介绍下查询方法的解析的流程吧,掌握了这个流程,对于定义方法有更深的理解。
<1> 方法参数不带特殊参数的查询
假如创建如下的查询:findByUserDepUuid(),框架在解析该方法时,流程如下:
- 首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为Doc
- 先判断 userDepUuid(根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续往下走
- 从右往左截取第一个大写字母开头的字符串(此处为Uuid),然后检查剩下的字符串是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复这一步,继续从右往左截取;最后假设 user 为查询实体的一个属性
- 接着处理剩下部分(DepUuid),先判断 user 所对应的类型是否有depUuid属性,如果有,则表示该方法最终是根据 "Doc.user.depUuid" 的取值进行查询;否则继续按照步骤3的规则从右往左截取,最终表示根据 "Doc.user.dep.uuid" 的值进行查询。
可能会存在一种特殊情况,比如 Doc包含一个 user 的属性,也有一个 userDep 属性,此时会存在混淆。可以明确在级联的属性之间加上 "_" 以显式表达意图,比如 "findByUser_DepUuid()" 或者 "findByUserDep_uuid()"。
<2> 方法参数带特殊参数的查询
特殊的参数: 还可以直接在方法的参数上加入分页或排序的参数,比如:
Page<UserModel> findByName(String name, Pageable pageable)
List<UserModel> findByName(String name, Sort sort);
@Query注解
我们在dao层接口按照规则来定义方法就可以不用写方法的实现也能操作数据库。但是如果一个条件查询有多个条件时,写出来的方法名字就太长了,所以我们就想着不按规则来定义方法名。我们可以使用@Query这个注解来实现这个功能,在定义的方法上加上@Query这个注解,将查询语句声明在注解中,也可以查询到数据库的数据。
(1)使用Query结合jpql语句实现自定义查询
- 在PersonDao接口中声明方法,放上面加上Query注解,注解里面写jpql语句,代码如下:
// 自定义的查询,直接写jpql语句; 查询id<? 或者 名字 like?的person集合 @Query("from Person where id < ?1 or name like ?2") List<Person> testPerson(Integer id, String name); // 自定义查询之子查询,直接写jpql语句; 查询出id最大的person @Query("from Person where id = (select max(p.id) from Person as p)") Person testSubquery();
(2)索引参数和命名参数
在写jpql语句时,查询条件的参数的表示有以下2种方式:
- 索引参数方式如下图所示,索引值从1开始,查询中'?x'的个数要和方法的参数个数一致,且顺序也要一致
- 命名参数方式(推荐使用这种方式)如下图所示,可以用':参数名'的形式,在方法参数中使用@Param("参数名")注解,这样就可以不用按顺序来定义形参
一个特殊情况,那就是自定义的Query查询中jpql语句有like查询时,可以直接把%号写在参数的前后,这样传参数就不用把%号拼接进去了。使用案例如下,调用该方法时传递的参数直接传就ok。
(3)使用@Query来指定使用本地SQL查询
如果你不熟悉jpql语句,你也可以写sql语句查询,只需要在@Query注解中设置nativeQuery=true。直接来看案例吧
- dao层接口写法如下图所示
@Modifying注解和事务
1)@Modifying注解的使用
@Query与@Modifying这两个注解一起使用时,可实现个性化更新操作及删除操作;例如只涉及某些字段更新时最为常见。
下面演示一个案例,把id小于3的person的name都改为'admin'
- dao层代码如下所示
//可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT //在 @Query 注解中编写 JPQL 语句, 但必须使用 @Modifying 进行修饰. 以通知 SpringData, 这是一个 UPDATE 或 DELETE 操作 //UPDATE 或 DELETE 操作需要使用事务, 此时需要定义 Service 层. 在 Service 层的方法上添加事务操作. //默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作! @Modifying @Query("UPDATE Person p SET p.name = :name WHERE p.id < :id") int updatePersonById(@Param("id")Integer id, @Param("name")String updateName);
使用@Modifying+@Query时的注意事项:
- 方法返回值是int,表示影响的行数
- 在调用的地方必须加事务,没事务不执行
(2)事务
- Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务。
- 对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上注解 @Transactional 声明
- 进行多个 Repository 操作时,也应该使它们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此,需要在 Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。