Spring Data

To Do

  • JDBC与JPA的区别
    • JDBC是数据库访问的标准;JPA是ORM的标准
    • JPA和JDBC之间的主要区别是抽象层次:JDBC是与数据库交互的低级标准,JPA是同一目的的较高标准
    • JDBC是JPA的前身
  • Hibernate与Mybatis与Spring Data
    • Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装【开发难度大】
    • MyBatis 本是apache的一个开源项目iBatis,着力于POJO与SQL之间的映射关系
    • Spring Data是一个用于简化数据库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷,并支持map-reduce框架和云计算数据服务【SQL的自由度低】
  • Predicate接口中的CriteriaQuery与CriteriaBuilder
    • CriteriaQuery:表达式查询语句
    • CriteriaBuilder:面向对象查询语句(.equal(),.and(),.or(),.like())
  • JDBC事务和JTA事务
    • JDBC的一切行为包括事务是基于一个Connection的(在JDBC中是通过Connection对象进行事务管理(常用的和事务相关的方法是: setAutoCommit、commit、rollback等)
    • Java Transaction API是一个Java企业版 的应用程序接口,在Java环境中,允许完成跨越多个XA资源的分布式事务【处理分布式事务】
  • 关于关联对象的懒加载处理

Question:

  • test中的数据库操作,会报tansactionrequired异常,事务会自动回滚roll back
    • 在test方法上手动设置@Transactional可以声明事务
    • 在test方法上手动设置@Rollback(false),可以禁止回滚
  • 进行save(Object)操作时,如果对象中有主键信息,会自动切换为修改操作(因此:增、改均可以使用同一个方法save)
    【小tips:可以在页面设置隐藏td用于存放id,可以根据实际情况进行自动判断,进行增加或者修改】

一. 概述

为了简化并统一持久层的各种实现技术的API,Spring Data提供了一套标准的API,整合不同持久层

  • spring-data-commons:一套标准API
  • spring-data-jpa 基于整合JPA实现

1. 简介

SpringData支持的持久层技术:

  • NoSQL 存储:MongoDB (文档数据库)Neo4j(图形数据库)Redis(键/值存储)Hbase(列族数据库)
  • 关系数据存储技术:JDBCJPA
    • JDBC:(Java Data Base Connectivity),用于直接调用SQL命令,面向数据库
    • JPA:(Java Persistence API),操作实体对象,免除编写繁琐的SQL,面向对象
      HIbernate即JPA的一种实现

2. 操作步骤

  1. 配置 Spring 整合 JPA
  2. 在 Spring 配置文件中配置 Spring Data,让 Spring 为声明的接口创建代理对象。
  3. 声明持久层的接口,该接口继承自Repository接口(开发中一般继承其子接口),并注入实体类及其主键类型
    spring容器实际通过AOP为我们提供的是SimpleJpaRepository(JpaRepository的实现类)的对象
  4. 在接口中声明需要的Method
  5. 使用Spring为这些接口创建代理实例(也可以通过JavaConfig,或者XML配置)
    <jpa:repositories base-package="com.acme.repositories"/> (XML配置语句)
  6. 获取Repository实例注入并使用它
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 1.声明一个继承自Repository的接口
interface PersonRepository extends Repository<User, Long> {
// 声明要查询的方法
List<Person> findByLastname(String lastname);
}

// 2.通过config为自定义接口创建代理对象
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EnableJpaRepositories
class Config {}

// 3. 注入Repository实例,并使用
public class SomeClient {
@Autowired
private PersonRepository repository;
public void doSomething() {
List<Person> persons = repository.findByLastname("Matthews");
}
}

3. Repository接口

Spring-data-commons中提供

Repository接口是 Spring Data 的一个核心接口,但是并不提供任何method,为一个名义接口

  • 开发者在自定义子接口中定义method
  • Spring Data让我们可以只定义接口,只要遵循 Spring Data的规范,就无需写实现类

4. Repository子接口

标准接口位于Spring-data-commons
扩展接口位于Spring-data-jpa

  • CrudRepository 标准接口 (提供了最基本的对实体类的CRUD操作)
    • 增删改:save、delete、deleteAll
    • 查:findAll、findOne、count
  • PagingAndSortingRepository 标准接口(继承自CrudRepository,提供了分页与排序功能)
    • 排序Iterable<T> findAll(Sort sort);
    • 分页查询(含排序功能)Page<T> findAll(Pageable pageable);
  • JpaRepository 扩展接口(该接口提供了JPA的相关功能)

|method|Description|
|:-|:-|:-|
|List<T> findAll() |查找所有实体|
|List<T> findAll(Sort sort) |排序、查找所有实体|
|List<T> save(Iterable<? extends T> entities)|保存集合|

  • JpaSpecificationExecutor 扩展接口(不属于Repository体系,实现一组JPA Criteria查询相关的方法)
  • Specification:Spring Data JPA提供的查询规范,用于复杂的查询(类似于QBC查询)
  • 自定义Repository子接口

二.操作流程

在Spring Data repository 抽象的接口中心是仓库(Repository).

1. 定义repository接口

自定义一个接口,继承某个Repository的子接口(需要泛型注入Entity类和一个主键类型)

  • 子接口举隅
    • 如果对象要实现CRUD操作,需要继承CrudRepository接口
    • 如果还要在此基础上实现分页查询,需要继承PagingAndSortingRepository接口
  • repository调整
    • 除了继承Spring Data接口,也能通过@RepositoryDefinition声明自定义的Repository接口
    • 在自定义接口中,可以选择性地暴露某些method,从而实现定义自己的抽象
      【暴露的接口,会通过动态代理获取实现类(如SimpleJapRepository)的method】
1
2
3
4
5
6
7
8
9
10
// 定义一个基础接口,供所有的domain接口使用(暴露findOne,save方法)
@NoRepositoryBean
interface MyBaseRepository<T ID extends Serializable> extends Repository<T,ID>{
T findOne(ID id);
T save(T entity);
}
// 继承基础接口(暴露通过email查询对象的方法)
interface UserRepository extends MyBaseRepository<User,Long>{
User findByEmailAddress(EmailAddress emailAddress);
}

2. 定义查询method

Repository代理提供两种方式获取特定查询
依据方法名直接导出查询 + 手动定义查询

2.1 查找策略

可用于仓库基础结构来解决查询

  • 实现方式:
    • xml配置(query-lookup-strategy属性)
    • 注解声明(Enable${store}Repositories中声明queryLookupStrategy属性)
  • 策略分类
    • CREATE:通过查询方法名构建一个特别的数据查询
    • USE_DECLARED_QUERY:尝试找到一个声明查询并在找不到的时候抛出一个异常
    • CREATE_IF_NOT_FOUND:综合上述两者的功能

2.2 创建查询

内置的查询生成器会剔除方法名中的关键字(诸如find/read/query/count/get...by),并解析剩下的内容

  • Distinct标志:
  • by:后面一般为查询条件,多个条件以and/or连接
  • 属性表达式你也可以使用可支持的操作比如Between(区间查询),LessThan,GreaterThan,Like(模糊查询)
  • 方法解析支持为某些属性设置一个IgnoreCase标志
  • 通过增加一个OrderBy字段实现按照属性排序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface PersonRepository extends Repository<user,Long>{
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

//Enables the distinct flag for the query
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname,String firstname);

List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

// Enabling ignoring case for an individual property
List<Person> findByLastnameIgnoreCase(String lastname);
// Enabling ignoring case for all suitable properties
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

// Enabling static ORDER BY for a query
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

2.3 属性表达式

用于自定义条件查询

属性表达式只能引用实体类的直接属性
内置查询器,依据固定规律,解析method名中的attribute作为查询条件

1
2
3
4
5
// 解析:1. 创建属性遍历x.address.zipCode
// 2. 如果上述解析不成功:则会按照驼峰法则从右开始拆分:AddressZip + Code --> Address + ZipCode
List<Person> findByAddressZipCode(ZipCode zipCode);
// 利用下划线(_)可以解决上述2中的歧义 【前提:Java属性命名中严格规范,不要使用下划线】
List<Person> findByAddress_ZipCode(ZipCode zipCode);

2.4 特殊参数处理

  • Pageable:
  • Slice:
  • Sort:
1
2
3
4
5
6
7
// 利用Spring框架提供的Pageable实例,来动态地实现添加分页
Page<User> findByLastname(String lastname, Pageable pageable);
// Slice仅仅知道是否有下一个Slice可用(消耗更小)
Slice<User> findByLastname(String lastname, Pageable pageable);
// 利用...Sort实例实现排序
List<User> findByLastname(String lastname, Sort sort);k
List<User> findByLastname(String lastname, Pageable pageable);

2.5 限制查询

关键字:firsttop (用于限制结果数)

  • 默认值为1,可以交替使用
  • 可以使用distinct
1
2
3
4
5
6
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);

2.6 流查询

Stream

查询方法能对以JAVA 8的Stream为返回的结果进行逐步处理
一个数据流可能包裹底层数据存储特定资源,因此在使用后必须关闭。 你也可以使用close()方法或者JAVA 7 try-with-resources区块手动关闭数据流

1
2
3
4
5
6
7
@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();

Stream<User> readAllByFirstnameNotNull();

@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

3. 创建repository实例

使用namespace,创建repository接口的bean实例

  • XML配置

    每一个Spring Data模块都包含repositories元素,因此简单的基于base-package定义即可进行Spring扫描

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<repositories base-package="com.acme.repositories" />
</beans:beans>
  • JavaConfig

    在一个JavaConfig类中使用@Enable${store}Repositories声明来触发repository的构建

1
2
3
4
5
6
7
8
9
// JPA声明
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
public EntityManagerFactory entityManagerFactory() {
// …
}
}
  • 独立使用

    可以在spring容器外使用repository组件

1
2
RepositoryFactorySupport factory = … // Instantiate factory here
UserRepository repository = factory.getRepository(UserRepository.class);

4. 自定义实现repositories

允许实现自定义的功能(包括CRUD和查询功能

4.1 针对单一repositories

自定义接口,并实现接口的方法,使用自定义的

1
2
3
4
5
6
7
8
9
10
11
12
13
interface UserRepositoryCustom {
public void someCustomMethod(User user);
}

class UserRepositoryImpl implements UserRepositoryCustom {
public void someCustomMethod(User user) {
// Your custom implementation
}
}

interface UserRepository extends CrudRepository<User, Long>, UserRepositoryCustom {
// Declare query methods here
}

4.2 针对所有repositories

添加自定义行为到所有的repository中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@NoRepositoryBean
public interface MyRepository<T, ID extends Serializable>
extends PagingAndSortingRepository<T, ID> {
void sharedCustomMethod(ID id);
}

public class MyRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {
private final EntityManager entityManager;
public MyRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
// Keep the EntityManager around to used from the newly introduced methods.
this.entityManager = entityManager;
}

public void sharedCustomMethod(ID id) {
// implementation goes here
}
}

三. 常用API(★★★)

本章节主要概述自定义数据库操作语句(即Spring框架中repository实现类中未定义的sql

1. 条件查询

1.1 根据method名自动生成

即利用属性表达式(Property expressions)

在接口中定义方法(利用关键字 + 按照固定格式),无需实现,内置查询器会自动生成并在调用方法时注入相应的查询语句

  • 精确查询一列:findBy列名
  • 模糊查询一列:findBy列名Like
  • 精确查询多列:findBy列名And列名Like 【按照驼峰命名】

1.2 配置@Query (★)

当方法名不按命名规则写的查询方法,可以配置@Query 绑定JPAL语句或者SQL语句

  • 使用JPAL语句,设置属性nativeQuery=false
    实际上,JPAL语句,在语法上完全同HQL,只是称谓不同
  • 使用SQL语句,设置属性nativeQuery=true

1.3 实体类上配置@NamedQuery

在接口的方法上配置@Query,并在实体类上定义@NamedQuery(name="方法名",query="语句")

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface StandardRepository extends JpaRepository<Standard, Integer> {
// 1.1 根据方法名自动生成
public List<Standard> findByName(String name);

// 1.2 配置@Query
@Query(value="from Standard where name = ?" ,nativeQuery=false)
// nativeQuery 为 false 配置JPQL 、 为true 配置SQL
public List<Standard> queryName(String name);

// 1.3 配置@Query不写语句【然后在实体类中通过@NamedQuery定义】
@Query
public List<Standard> queryName2(String name);
}
////// 1.3 实体类中的定义【声明在类上】
@NamedQueries({
@NamedQuery(name="Standard.queryName2",query="from Standard where name=?")})

2. 带条件修改

使用@Query注解完成 , 搭配使用@Modifying标记修改、删除操作

  • 在测试test中进行增删改操作时,需要在test方法上设置事务,并手动定义不rollback
1
2
3
4
5
6
7
8
9
10
11
12
// 定义修改的方法
@Query(value="update Standard set minLength=?2 where id =?1")
@Modifying
public void updateMinLength(Integer id , Integer minLength);

// 在test中进行测试
@Test
@Transactional // 声明事务
@Rollback(false) // 声明不回滚(默认在test中操作结束,自动回滚)
public void testUpdate(){
standRepository.updateMinLength(1,15);
}

3. 分页查询

  • Spring Data提供的标准接口PagingAndSortingRepository中,定义了实现分页的方法
    • Page<T> findAll(Pageable pageable)
  • 参数SpringData提供了PageRequest对象,作为Pageable接口的实现类
    注意:PageRequest的page页码从0开始
    • public PageRequest(int page, int size) 【构造器】
    • public PageRequest(int page, int size, Sort sort)
  • 返回值分页的查询结果,会被自动封装为Page<T>
    • 总页数:TotalPages
    • 页面详情:TotalElements
1
2
3
4
// 实例化Pageable对象(传入参数:当前页码,当前页总记录数)
Pageable pageable = new PageRequest(page - 1, rows);//PageRequest的page页码从0开始,所以进行-1
// 调用findAll方法,返回结果自动封装为Page<T>类型
Page<Standard> pageData = standardRepository.findAll(pageable);

4. 查询关键字

在method名称中可以使用的


四. 参考文档

1. 简介

1.1 命名空间(namespace)

Spring Data的JPA模块包含一个允许定义存储库bean的自定义命名空间(包含JPA特有的某些特征和元素属性

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jpa:repositories base-package="com.acme.repositories" />
</beans>

1.2 注解配置

Spring Data JPA存储库支持不仅可以通过XML命名空间激活,还可以通过JavaConfig使用注释

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
26
27
28
29
@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
class ApplicationConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
// 重点:创建LocalContainerEntityManagerFactoryBean而不是EntityManagerFactory
// (因为前者包含了异常机制)
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.acme.domain");
factory.setDataSource(dataSource());
return factory;
}

@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
}
}

2. 持久化实体

3. 查询方法

4. 存储过程

5. 事务管理

6. 审查

坚持原创技术分享,您的支持将鼓励我继续创作!