Spring Data
一.概述
Spring 的一个子项目。用于简化数据库访问,支持NoSQL 和 关系数据存储。其主要目标是使数据库的访问变得方便快捷。SpringData 项目所支持
NoSQL 存储:MongoDB (文档数据库)
Neo4j(图形数据库)
Redis(键/值存储)
Hbase(列族数据库)
SpringData 项目所支持的关系数据存储技术:JDBC/JPA
JPA Spring Data : 致力于减少数据访问层 (DAO) 的开发量. 开发者唯一要做的,就
只是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成!框架怎么可能代替开发者实现业务逻辑呢?比如:当有一个 UserDao.findUserById() 这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User 对象。Spring Data JPA 做的便是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。
二.所需jar包
三.配置文件
四.Entity
表:
person(通过SpringData自动生成的表会自动生成主键和外键) address
五.Repository
5.1 如何声明一个Repository
Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法 public interface Repository<T, ID extends Serializable> { } 第一个泛型为 model的class
第二个为id的class Spring Data可以让我们只定义接口,只要遵循 Spring Data的规范,就无需写实现类。 与继承 Repository 等价的一种方式,就是在持久层接口上使用
@RepositoryDefinition 注 解,并为其指定 domainClass 和 idClass 属性。如下两种方式是完全等价的
也可集成repository的子接口
5.2 Repository查询方法的关键字
5.3 查询方法解析流程
假如创建如下的查询:findByUserDepUuid(),框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为Doc
-
先判断 userDepUuid (根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步
-
从右往左截取第一个大写字母开头的字符串(此处为Uuid),然后检查剩下的字符串是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 user 为查询实体的一个属性;
-
接着处理剩下部分(DepUuid),先判断 user 所对应的类型是否有depUuid属性,如果有,则表示该方法最终是根据 “ Doc.user.depUuid” 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 “Doc.user.dep.uuid” 的值进行查询。
-
可能会存在一种特殊情况,比如 Doc包含一个 user 的属性,也有一个 userDep 属性,此时会存在混淆。可以明确在属性之间加上 "_" 以显式表达意图,比如 "findByUser_DepUuid()" 或者 "findByUserDep_uuid()"
-
特殊的参数: 还可以直接在方法的参数上加入分页或排序的参数, 比如:Page<UserModel> findByName(String name, Pageable pageable); List<UserModel> findByName(String name, Sort sort);
5.4 查询@Query注解
这种查询可以声明在 Repository 方法中,摆脱像命名查询那样的约束,将查询直接在相应的接口方法中声明,结构更为清晰,这是 Spring data 的特有实现。
占位符绑定参数:
-
索引参数如下所示,索引值从1开始,查询中 ”?X” 个数需要与方法定义的参数个数相一致,并且顺序也要一致
-
命名参数(推荐使用这种方式):可以定义好参数名,赋值时采用@Param("参数名"),而不用管顺序。
-
如果是 @Query 中有 LIKE 关键字,后面的参数需要前面或者后面加 %,这样在传递参数值的时候就可以不加 %:
@Query("select o from UserModel o where o.name like ?1%") public List<UserModel> findByUuidOrAge(String name);
@Query("select o from UserModel o where o.name like %?1") public List<UserModel> findByUuidOrAge(String name);
@Query("select o from UserModel o where o.name like %?1%") public List<UserModel> findByUuidOrAge(String name);
-
@Query 本地查询
设置nativeQuery为true,
比如:@Query(value="select * from tbl_user where name like %?1" ,nativeQuery=true) public List<UserModel> findByUuidOrAge(String name);
5.5@Modifying注解和事务
-
@Query 与 @Modifying 这两个 annotation一起声明,可定义个性化更新操作,例如只涉及某些字段更新时最为常用,示例如下:
注意:
1.方法的返回值应该是 int,表示更新语句所影响的行数在调用的地方必须加事务,
2.可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT
3.在 @Query 注解中编写 JPQL 语句, 但必须使用 @Modifying 进行修饰. 以通知 SpringData, 这是一个 UPDATE 或 DELETE 操作
4.UPDATE 或 DELETE 操作需要使用事务, 此时需要定义 Service 层. 在 Service 层的方法上添加事务操作.
5.默认情况下, SpringData 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作!
没有事务不能正常执行
会报:
org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an
update/delete query
六.SpringData的事务
-
Spring Data 提供了默认的事务处理方式,即 所有的查询均声明为只读事务 。
-
对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上注解 @Transactional 声明
-
进行多个 Repository 操作时,也应该使它们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此, 需要在 Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。
七.Repository子类 以及其他类
7.1 CrudRepository 接口
自带一些方法
保存/删除(单个与集合)
根据id查询 查询所有 查询条数 是否存在该id....
注:保存时id可以不填写 如果id有注解(
@Id
@GeneratedValue
)
7.2PagingAndSortingRepository接口
实现了分页和排序的功能
是
CrudRepository 接口的子接口
分页使用:
PageRequest 类即可
测试代码:
分页
@Test
public void
getByPage
(){
//pageNo
从
0
开始
.
int
pageNo =
6
-
1
;
int
pageSize =
5
;
//Pageable
接口通常使用的其
PageRequest
实现类
.
其中封装了需要分页的信息
//
排序相关的
. Sort
封装了排序的信息
//Order
是具体针对于某一个属性进行升序还是降序
.
PageRequest pageable =
new
PageRequest(pageNo
,
pageSize)
;
Page<Person> page =
personRepository
.findAll(pageable)
;
System.
out
.println(
"
总记录数
: "
+ page.getTotalElements())
;
System.
out
.println(
"
当前第几页
: "
+ (page.getNumber() +
1
))
;
System.
out
.println(
"
总页数
: "
+ page.getTotalPages())
;
System.
out
.println(
"
当前页面的
List: "
+ page.getContent())
;
System.
out
.println(
"
当前页面的记录数
: "
+ page.getNumberOfElements())
;
}
分页+排序
@Test
public void
getByPageAndOrder
(){
//pageNo
从
0
开始
.
int
pageNo =
6
-
1
;
int
pageSize =
5
;
//Pageable
接口通常使用的其
PageRequest
实现类
.
其中封装了需要分页的信息
//
排序相关的
. Sort
封装了排序的信息
//Order
是具体针对于某一个属性进行升序还是降序
.
Order order1 =
new
Order(Direction.
DESC
,
"id"
)
;
Order order2 =
new
Order(Direction.
ASC
,
"email"
)
;
Sort sort =
new
Sort(order1
,
order2)
;
PageRequest pageable =
new
PageRequest(pageNo
,
pageSize
,
sort)
;
Page<Person> page =
personRepository
.findAll(pageable)
;
System.
out
.println(
"
总记录数
: "
+ page.getTotalElements())
;
System.
out
.println(
"
当前第几页
: "
+ (page.getNumber() +
1
))
;
System.
out
.println(
"
总页数
: "
+ page.getTotalPages())
;
System.
out
.println(
"
当前页面的
List: "
+ page.getContent())
;
System.
out
.println(
"
当前页面的记录数
: "
+ page.getNumberOfElements())
;
}
7.3JpaRepository接口
属于PagingAndSortingRepository接口的子类
该接口提供了JPA的相关功能
List<T> findAll(); //查找所有实体
List<T> findAll(Sort sort); //排序、查找所有实体
List<T> save(Iterable<? extends T> entities);//保存集合
void flush();//执行缓存与数据库同步 T saveAndFlush(T entity);//强制执行持久化
void deleteInBatch(Iterable<T> entities);//删除一个实体集合
@Test
public void
testJpaRepository
(){
Person person =
new
Person()
;
person.setBirth(
new
Date())
;
person.setLastName(
"xyz"
)
;
person.setId(
28
)
;
Person person2 =
personRepository
.saveAndFlush(person)
;
System.
out
.println(person == person2)
;
}
7.4JpaSpecificationExecutor接口
只需要一个泛型
JpaSpecificationExecutor<Person>
不属于Repository体系,实现一组 JPA Criteria 查询相关的方法
Specification:封装 JPA Criteria 查询条件。通常使用匿名内部类的方式来创建该接口的对象
代码示例:
/**
*
目标
:
实现带查询条件的分页
. id > 9145071
的条件
*
*
调用
JpaSpecificationExecutor
的
Page
<T>
findAll(Specification
<T>
spec, Pageable pageable);
* Specification:
封装了
JPA Criteria
查询的查询条件
* Pageable:
封装了请求分页的信息
:
例如
pageNo, pageSize, Sort
*/
@Test
public void
testJpaSpecificationExecutor
(){
int
pageNo =
3
-
1
;
int
pageSize =
5
;
PageRequest pageable =
new
PageRequest(pageNo
,
pageSize)
;
//
通常使用
Specification
的匿名内部类
Specification<Person> specification =
new
Specification<Person>() {
/**
*
@param
*root:
代表查询的实体类
.
*
@param
query
:
可以从中可到
Root
对象
,
即告知
JPA Criteria
查询要查询哪一个实体类
.
还可以
*
来添加查询条件
,
还可以结合
EntityManager
对象得到最终查询的
TypedQuery
对象
.
*
@param
*cb: CriteriaBuilder
对象
.
用于创建
Criteria
相关对象的工厂
.
当然可以从中获取到
Predicate
对象
*
@return:
*Predicate
类型
,
代表一个查询条件
.
*/
@Override
public
Predicate
toPredicate
(Root<Person> root
,
CriteriaQuery<?> query
,
CriteriaBuilder cb) {
Path path = root.get(
"id"
)
;
//gt表示大于
Predicate predicate = cb.gt(path
,
9145071
)
;
return
predicate
;
}
}
;
Page<Person> page =
personRepository
.findAll(specification
,
pageable)
;
System.
out
.println(
"
总记录数
: "
+ page.getTotalElements())
;
System.
out
.println(
"
当前第几页
: "
+ (page.getNumber() +
1
))
;
System.
out
.println(
"
总页数
: "
+ page.getTotalPages())
;
System.
out
.println(
"
当前页面的
List: "
+ page.getContent())
;
System.
out
.println(
"
当前页面的记录数
: "
+ page.getNumberOfElements())
;
}
7.5自定义Repository
①为某一个 Repository 上添加自定义方法 实现步骤
-
创建一个实现类名字为自定义Repository名字+Impl(强制,但可以通过配置修改)
-
实现类与接口中定义相同名称参数返回值的方法(一般为了强制约束一致 会新建一个新的接口并且实体类实现这个接口 原Repository继承这个接口)
类图关系如下:
实际上在使用 PersonRepository 的 test 方法时,
会调用 PersonRepositoryImpl 中 test 方法的实现
//
测试自定义
Repository
@Test
public void
testMyRepository
(){
System.
out
.println(
personRepository
.test(
9145069
))
;
}
②为所有的 Repository 都添加自实现的方法
-
创建一个 自定义的扩展接口CommonMethodTest
-
创建一个扩展接口的实现类CommonMethodTestImpl
-
创建一个 MyRepositoryFactory 与FactoryBean 这里建在一起放到CommonJpaRepositoryFactoryBean中
-
修改配置文件
<
jpa
:repositories
base-package
="com.jw.myRepository"
entity-manager-factory-ref
="entityManagerFactory"
factory-class
="com.jw.myRepository.CommonJpaRepositoryFactoryBean"
></
jpa
:repositories>
5.新创建的Repository继承自定义的 CommonMethodTest接口即可
注意事项:
@NoRepositoryBean一定要有的,还有全局的扩展实现类不要用Imp作为后缀名,不然会报异常的(目前还没搞清楚报异常的具体原因,个人猜测可能是和局部的扩展有冲突吧)
八.其他
8.1本文代码打包
8.2 推荐其他学习资料
8.3 补充
①JPA拿到EntityManage
@PersistenceContext
private
EntityManager
entityManager
;
②Entity 中忽略属性或者级联列名 等等注解 都应该加在属性的get方法上
③ SPEL表达式(使用
'#{#entityName}'值为'Book'对象对应的数据表名称(book)。
public interface BookQueryRepositoryExample extends Repository<Book, Long>{
@Query(value = "select * from #{#entityName} b where b.name=?1", nativeQuery = true)
List<Book> findByName(String name);
④ 用Reporsity 拿到的 非实体类对象 对象将是object[] 唯一数值将是 将会返回BigDecimal
⑤IN 查询可以传一个List<Long>