Spring Data JPA系列
2、Spring Data JPA Criteria查询、部分字段查询
3、Spring Data JPA数据批量插入、批量更新真的用对了吗
4、Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作
5、Spring Data JPA自定义Id生成策略、复合主键配置、Auditing使用
6、【源码】Spring Data JPA原理解析之Repository的自动注入(一)
7、【源码】Spring Data JPA原理解析之Repository的自动注入(二)
8、【源码】Spring Data JPA原理解析之Repository执行过程及SimpleJpaRepository源码
9、【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(一)
10、【源码】Spring Data JPA原理解析之Repository自定义方法命名规则执行原理(二)
11、【源码】Spring Data JPA原理解析之Repository自定义方法添加@Query注解的执行原理
13、【源码】Spring Data JPA原理解析之事务注册原理
14、【源码】Spring Data JPA原理解析之事务执行原理
18、【源码】Spring Data JPA原理解析之Hibernate EventListener使用及原理
19、【源码】Spring Data JPA原理解析之Auditing执行原理
20、Spring Data JPA使用及实现原理总结
前言
《 Spring Data JPA系列》博文已经写了19篇,对于Spring Data JPA,还有其他的知识点,该系列博文暂时先写这些。该系列从入门到高级、再到源码解读,分享了Spring Data JPA在开发中最常用的技术点和实现原理。本篇对《Spring Data JPA系列》做一个总结。
Spring Data JPA简介
Spring Data JPA是Spring提供的一套简化JPA开发的框架,按照约定好的方法名命规则写DAO层接口,可以在不写接口实现的情况下,实现对数据库中Table的操作,同时提供了除CRUD操作之外的许多功能,如分页、复杂查询等。
Repository接口类型
在JPA中,通过自定义Repository接口,实现对数据库的操作。Repository接口的接口方法类型为以下三类:
1)第一类Repository接口为继承JpaRepositoryImplementation接口的接口方法,该接口提供并自动实现了对实体类的CRUD等的操作;
2)通过方法命名规则,自定义接口方法,无需写SQL或HQL,实现数据库表的操作,主要针对数据库表的查询、删除操作;
方法名称规则:
操作(关键字)+ By + 属性名称(属性名称的首字母大写)+ 查询条件(首字母大写)
操作关键字包括:
find | read | get | query | search | stream | count | exists | delete | remove
查询条件包括:
And | Or | Is | Equals | Between | LessThan | Before | LessThanEqual | GreaterThan | After | GreaterThanEqual | IsNull | IsNotNull | NotNull | Like | NotLike | StartingWith | EndingWith | Containing | OrderBy | Not | In | NotIn | True | False | IgnoreCase | OrderBy
3)通过@Query注解,添加SQL或HQL,自定义接口方法,实现数据库表的操作;
在@Query注解中,支持三类查询语句的书写形式,分别如下:
a)通过@Query注解的value属性,填写HQL语句;
b)通过@Query注解的value属性,填写SQL语句,nativeQuery属性设置为true;
c)通过@Query注解的name属性,填写对应的queryName。其中:queryName为在实体类中,通过@NamedQueries、@NamedQuery注解添加的HQL语句;
EntityManager实现的数据库操作
在JPA中,除了通过Repository接口实现对数据库的操作以外,还可以通过EntityManager编码,实现数据库操作。
Java Persistence API(JPA)中的EntityManager是一个接口,在JPA规范中,EntityManager扮演着执行持久化操作的关键角色。普通Java对象只有被EntityManager持久化之后,才能转变为持久化对象,保存到数据库中。它不仅可以管理和更新Entity对象,还可以基于主键查询Entity对象,通过JPQL语句进行Entity查询,甚至通过原生SQL语句进行数据库更新及查询操作。
EntityManager提供以下功能:
1)创建、更新和删除数据:EntityManager中的persist()、merge()和remove()方法分别用于插入、更新和删除数据库记录;
2)查询数据:EntityManager的find()和createQuery()方法用于查询数据;
3)管理实体的生命周期:EntityManager的flush()方法用于将持久性上下文同步到基础数据库,进行持久化操作;
4)事务管理:EntityManager的getTransaction()方法用于获取当前事务,可以对事务进行提交或回滚;
5)执行原生SQL:EntityManager的createNativeQuery()方法用于执行原生SQL。对于原生SQL,需要考虑不同数据库的各自实现;
6)创建CriteriaBuilder:EntityManager的getCriteriaBuilder()方法用于获取CriteriaBuilder。通过CriteriaBuilder实现使用Criteria API查询数据;
Criteria查询
JPA的Repository接口操作数据库时,无法很好的支持动态条件查询,Criteria查询可以很好的解决这个问题。
Criteria查询的API主要包括:
1)CriteriaBuilder:用于构造条件查询、复合选择、表达式、谓词和排序;
2)CriteriaQuery:定义了特定于顶级查询的功能,包含了查询的各个部分。如:select结果集、where条件、group by、order by等。在CriteriaQuery指定返回值结果集;
3)Root:定义Criteria查询的根对象,Criteria查询的根定义为实体类型,它与SQL查询中的FROM子句类似,定义了查询的FROM子句中能够出现的类型。可以有多个查询根对象;
4)Expression:运算表达式。用在查询语句的select、where和having子句中,该接口有isNull、isNotNull和in方法。可通过Root.get(“实体类属性”)获得Expression对象;
5)Predicate:过滤条件。通过CriteriaQuery.where()方法,将Predicate或Expression实例作为条件应用到Criteria查询中;
Predicate实例可以通过CriteriaBuilder的条件方法(equal、notEqual、gt、ge、lt、le、between、like等)创建;
Predicate实例也可以通过Expression实例的isNull、isNotNull和in方法获得;
复合的Predicate语句,可以使用CriteriaBuilder的and、or、andnot方法构建;
JPA多表关系
在JPA中,支持多表关系定义及多表级联操作。
多表关系主要相关注解:
@OneToOne注解:表示一对一关系;
@OneToMany、@ManyToOne注解:表示一对多关系;
@ManyToMany注解:表示多对多关系;
@JoinColumn注解:标记实体类与数据库的对应关系,以@OneToOne、@OneToMany、@ManyToOne三个注解搭配使用;
@JoinTable注解:多对多通常使用中间表关联,通过该注解标记关联表,以@ManyToMany注解搭配使用。属性name为关联表的名称、joinColumns为关联student表的id、inverseJoinColumns为关联Course表的id;
多表级联操作是指当一个实体的状态发生改变时,关联的其他实体是否同时发生改变。简单理解:cascade用于设置当前实体是否能够操作关联的另一个实体的权限。cascade的取值在CascadeType枚举类中定义。
详见:Spring Data JPA的一对一、LazyInitializationException异常、一对多、多对多操作
实体类Id生成策略
JPA默认支持四种Id生成策略,分别为TABLE(使用数据库表格保存主键)、SEQUENCE(使用底层数据库的序列来生成主键)、IDENTITY(主键由数据库自动生成)、AUTO(主键由程序控制)。通过@GeneratedValue注解中的strategy属性指定对应生成策略。
除了以上四种默认支持的策略以外,JPA还支持自定义Id生成策略,通过实现org.hibernate.id.IdentifierGenerator接口,重写generate()方法,在方法中返回Id值。
通过@GenericGenerator注解,指定自定义Id策略的名称name及对应的策略strategy的类,并添加@GeneratedValue注解的generator属性为@GenericGenerator注解的name属性。
复合主键
在Spring Data JPA的实体类中并不支持简单的直接在多个属性中添加@Id注解实现复合主键。而是需要先创建一个复合主键类,然后在实体类中使用@IdClass注解将主键类附加在类中,然后在多个主键属性中添加@Id主键。
Auditing审计
JPA通过@CreateBy、@CreateDate、@LastModifiedBy、@LastModifiedDate四个注解,提供对数据库表中的数据记录自动添加操作人及操作时间的审计功能。
在实际开发中,可以定义一个抽象父类,专门记录操作人及操作信息。在该父类中添加@MappedSuperclass注解。
Auditing使用
1)在启动类中添加@EnableJpaAuditing注解,开启Auditing;
2)在实体类中添加监听器及操作人、操作时间的注解。或者定义一个抽象父类,专门记录操作人及操作信息。并添加@EntityListeners注解,添加AuditingEntityListener监听器;
3)自定义AuditorAware,重写getCurrentAuditor(),返回审计员主键,实现数据记录的创建人、操作人的自动填充;
Repository自动注入原理
JPA的Repository注入到Spring IOC容器的对象为代理对象,其中target为SimpleJpaRepository对象,实现了自定义的Repository接口、Repository接口以及TransactionProxy接口,主要添加了QueryExecutorMethodInterceptor、TransactionInterceptor等拦截器。
注入的主要执行流程如下:
1)在SpringBoot中引入spring-boot-starter-data-jpa依赖时,会自动注入JpaRepositoriesAutoConfiguration,从而注入JpaRepositoryConfigExtension;
2)在JpaRepositoryConfigExtension的父类RepositoryConfigurationExtensionSupport的getRepositoryConfigurations()方法中,获取所有实现Repository接口的类,封装成RepositoryConfiguration;
3)SpringBoot启动时,会执行RepositoryConfigurationDelegate的registerRepositoriesIn()方法,在该方法中,获取2)中所有实现Repository接口的类,创建对应的BeanDefinition对象,其中beanClass为"org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean"字符串。并执行registry.registerBeanDefinition(beanName, beanDefinition),将Repository类及对应的beanDefinition添加到Spring IOC容器中;
4)在Spring启动过程中,会执行AbstractBeanFactory.doGetBean()方法,先从Repository的BeanDefinition中,获取beanClass,为JpaRepositoryFactoryBean类名,然后创建JpaRepositoryFactoryBean对象。执行afterPropertiesSet()方法,完成初始化;
5)在JpaRepositoryFactoryBean的afterPropertiesSet()初始化方法中,会调用RepositoryFactorySupport.getRepository()方法。该方法通过代理的的方式,创建一个实现了自定义的Repository接口、Repository以及TransactionProxy接口,且代理的target为SimpleJpaRepository对象,代理添加了QueryExecutorMethodInterceptor、TransactionInterceptor等拦截器。代理的对象保存在repository属性中;
6)执行了JpaRepositoryFactoryBean对象的afterPropertiesSet()初始化之后,会调用AbstractBeanFactory.getObjectForBeanInstance(),从JpaRepositoryFactoryBean工厂bean中执行getObject(),返回创建的代理repository对象;
详见:
提醒:最好指定Repository所在的包,以免扫描整个SpringBoot启动类所在的包及其子包来获取Repository类
Repository接口实现原理
Repository在Spring容器中为代理对象,Repository接口的接口方法有三种类型,其实现原理分别为:
11.1)继承JpaRepositoryImplementation接口的接口方法
在Repository代理对象中,target为SimpleJpaRepository对象。该对象实现了JpaRepositoryImplementation接口。
当执行JpaRepositoryImplementation接口中的方法时,在动态代理中,会调用执行SimpleJpaRepository的对应方法。
在SimpleJpaRepository中,通过EntityManager接口及Criteria查询接口实现了对数据库的相关操作。
详见:
【源码】Spring Data JPA原理解析之Repository执行过程及SimpleJpaRepository源码
11.2)通过方法命名规则
通过方法命名规则,自定义接口方法的实现是通过Repository代理对象中的QueryExecutorMethodInterceptor拦截器中实现的。
主要执行流程如下:
1)QueryExecutorMethodInterceptor方法拦截器的构造方法中,会根据查询查找策略CreateIfNotFoundQueryLookupStrategy,获得RepositoryQuery对象,解析方法。对于按方法命名规则实现的方法,使用的RepositoryQuery对象为PartTreeJpaQuery;
2)在PartTreeJpaQuery构造方法中,创建一个PartTree对象,解析方法名称中的起始关键字【如:findBy、readBy、deleteBy等】、条件属性【实体类中的属性】、查询关键字【Between、In、Equals等】;
3)创建对应方法的countQuery和query,将解析出的查询的基础信息封装在QueryPreparer对象中,根据解析出的查询信息,创建CriteriaQuery对象;
4)解析完方法信息,保存在PartTreeJpaQuery后,保存到QueryExecutorMethodInterceptor的Map<Method, RepositoryQuery> queries中;
5)当Repository的接口被调用的时候,在ReflectiveMethodInvocation.proceed()中,先执行QueryExecutorMethodInterceptor.invoke()方法;
5.1)调用doInvoke()方法,获取数据库执行后的数据;
5.1.1)调用RepositoryQueryMethodInvoker.invoke() -> RepositoryQuery.execute() -> AbstractJpaQuery.execute() -> AbstractJpaQuery.doExecute() -> JpaQueryExecution.execute() -> JpaQueryExecution.doExecute();
5.1.2)doExecute()是一个抽象方法,针对不同的数据库查询返回值信息,使用不同的实现类。所有的实现类都会先调用AbstractJpaQuery.createQuery(),获取一个Query对象;
5.1.3)在AbstractJpaQuery.createQuery()中,调用抽象方法doCreateQuery()。对于按方法命名规则的Repository接口,实现类为PartTreeJpaQuery;
5.1.4)在PartTreeJpaQuery.coCreateQuery()方法中,通过EntityManager.createQuery(criteriaQuery)返回TypedQuery,然后执行invokeBinding(),在TypedQuery对象中,调用query.setParameter()绑定查询条件的参数值,如果有分页,设置分页信息;
5.1.5)参数完参数,在6.1.3中设置hint等。然后执行6.1.2中的具体实现类,执行数据库查询。如SingleEntityExecution实现类,执行TypeQuery.getSingleResult(),然后单个结果;
5.2)调用resultHandler.postProcessInvocationResult(),对数据库查询后的值进行返回值类型转换;
详见:
11.3)通过@Query注解
通过@Query注解,添加SQL或HQL,自定义接口方法的实现也是通过Repository代理对象中的QueryExecutorMethodInterceptor拦截器中实现的。对比方法命名规则的实现,差异在于使用不同的查询解析方法。
主要执行流程如下:
1)QueryExecutorMethodInterceptor方法拦截器的构造方法中,会根据查询查找策略CreateIfNotFoundQueryLookupStrategy,获得RepositoryQuery对象,解析方法。对于添加@Query注解的方法,使用的RepositoryQuery对象为SimpleJpaQuery和NativeJpaQuery;
2)SimpleJpaQuery和NativeJpaQuery都是继承AbstractStringBasedJpaQuery,在AbstractStringBasedJpaQuery构造方法中,解析对应的queryString和countQueryString,生成DeclaredQuery对象,实际对象为ExpressionBasedStringQuery。核心的解析过程在父类StringQuery中;
3)解析完方法信息,保存在父类AbstractStringBasedJpaQuery后,保存到QueryExecutorMethodInterceptor的Map<Method, RepositoryQuery> queries中;
4)当Repository的接口被调用的时候,在ReflectiveMethodInvocation.proceed()中,先执行QueryExecutorMethodInterceptor.invoke()方法;
4.1)调用doInvoke()方法,获取数据库执行后的数据;
4.1.1)调用RepositoryQueryMethodInvoker.invoke() -> RepositoryQuery.execute() -> AbstractJpaQuery.execute() -> AbstractJpaQuery.doExecute() -> JpaQueryExecution.execute() -> JpaQueryExecution.doExecute();
4.1.2)doExecute()是一个抽象方法,针对不同的数据库查询返回值信息,使用不同的实现类。所有的实现类都会先调用AbstractJpaQuery.createQuery(),获取一个Query对象;
4.1.3)在AbstractJpaQuery.createQuery()中,调用抽象方法doCreateQuery()。对于添加@Query注解Repository接口,实现类为SimpleJpaQuery或NativeJpaQuery,方法实现在父类AbstractStringBasedJpaQuery.doCreateQuery();
4.1.4)在AbstractStringBasedJpaQuery.doCreateQuery()方法中,通过EntityManager.createQuery(queryString)返回Query【如果是NativeJpaQuery,使用EntityManager.createNativeQuery(queryString)返回Query】,然后执行invokeBinding(),在Query对象中,调用query.setParameter()绑定查询条件的参数值,如果有分页,设置分页信息;
4.1.5)参数完参数,在6.1.3中设置hint等。然后执行6.1.2中的具体实现类,执行数据库查询。如SingleEntityExecution实现类,执行TypeQuery.getSingleResult(),然后单个结果;
4.2)调用resultHandler.postProcessInvocationResult(),对数据库查询后的值进行返回值类型转换;
Auditing审计实现原理
JPA接口中提供了持久化的监听回调注解,通过添加注解,可以监听实体的新增、修改、删除的前后状态。
主要执行流程如下:
1)JPA的提供了持久化的监听回调注解;
2)Spring解析实体类时,会解析实体类中添加的监听回调注解的监听器;或者实体类中的@EntityListeners注解中的监听器,并归类存放在FastSessionServices中,然后将FastSessionServices传给SessionImpl对象。对于Auditing,监听器为AuditingEntityListener,添加了@PrePersist和@PreUpdate,在新增和修改之前回调。
3)Auditing审计功能通过添加@EnableJpaAuditing注解,自动为AuditingEntityListener添加AuditHandler对象;
4)AuditHandler对象提供了对当前执行持久化对象的审计相关注解的属性获取、审计人获取;
5)JPA通过SessionImpl执行新增或修改操作时,会调用FastSessionServices中对应操作类型的监听器,从而执行AuditingEntityListener的方法;
6)AuditingEntityListener通过AuditHandler及当前的实体类,通过反射,为实体类的审计属性赋值;
详见:
JPA事务
12.1)事务说明
事务是指在数据库管理系统中,一系列紧密相关的操作序列,这些操作作为一个单一的工作单元执行。事务的特点是要么全部成功,要么全部失败,不会出现部分完成的情况。如果事务中的任何一个操作失败,那么整个事务都会被回滚到开始之前的状态,以确保数据库的一致性和完整性。
事务的四个特性为:原子性、一致性、隔离性和持久性。通常称为ACID。
在Spring框架中,数据操作的事务是通过添加@Transactional注解来实现的。
12.2)JPA事务实现原理
JPA的Repository注入到Spring IOC容器的对象为代理对象,其中添加了TransactionInterceptor拦截器,在该拦截器中实现了事务。
主要执行流程如下:
1)当方法调用时,执行TransactionInterceptor.invoke()方法,该方法调用父类TransactionAspectSupport.invokeWithinTransaction()方法;
2)解析原方法的@Transactional注解信息,封装为TransactionAttribute对象;
3)从Spring容器中获取JpaTransactionManager对象;
4)根据传播特性,选择性开启事务;
5)在try中执行ReflectiveMethodInvocation.proceed()方法,直至执行原方法,获取返回值;
6)在catch中捕获异常,如果出现异常,执行completeTransactionAfterThrowing(),对满足回滚规则的,执行回滚;如果不满足回滚规则,依然提交事务,并抛出异常,结束方法;
7)如果没有异常,提交事务;
详见:
【源码】Spring Data JPA原理解析之事务注册原理-CSDN博客
12.3)事务失效
事务使用不当时,可能导致事务失效。常见的事务失效场景如下:
1)访问权限问题。非public访问权限的方法,即使添加了@Transactional注解,事务无效;
2)无效异常。Spring事务回滚是通过异常捕获实现的,默认只处理RuntineException或Error异常,也可以通过@Transactional的rollbackFor参数设置回滚异常;
3)业务异常捕获。如果在业务中捕获了异常,Spring事务无法捕获到异常,回滚失效。需要通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(),设置回滚状态;
4)内部方法调用。在一个上下文没有事务的方法中,通过this调用内部添加@Transactional注解的方法时,事务无效。因为此时的this是目标对象,不是对应的代理对象;
5)传播特性使用不当。
6)异步线程调用。事务信息是通过ThreadLocal保存在本地线程变量中,对于异步线程无效;
详见:
JPA的优点与缺点
13.1)优点
1)简化数据持久化操作:JPA框架提供了一系列的API和工具,提供实体的CRUD方法,减少SQL语句的编写,开发效率高;
2)可移植性好:JPA框架是基于对象关系映射(ORM)的规范,底层采用Hibernate,提供标注的查询语言,对象化程度更好,使得开发人员可以更加方便地切换不同的数据库;
13.2)缺点
1)性能问题:通过源码篇能够了解到,JPA框架提供了较为复杂的对象关系映射机制,可能会影响系统的性能;
2)灵活性较差:JPA强调对象模型和数据库模型的映射,对于复杂的SQL操作,可能需要使用Criteria API或转为Native SQL;
结尾
以上为本篇分享的全部内容。
关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。