Spring之声明式事务控制(十)
文章目录(一)改造:基于XML的AOP实现事务控制(二)改造:基于注解的AOP实现事务控制(上)(三)改造:基于注解的AOP实现事务控制(下)(四)spring中事务控制的一组API(五)spring事务控制的代码准备(六)spring基于XML的声明式事务控制(七)spring基于注解的声明式事务控制(八)spring基于纯注解的声明式事务控制(九)spring编程式事务控制(...
文章目录
(一)改造:基于XML的AOP实现事务控制
(二)改造:基于注解的AOP实现事务控制(上)
(三)改造:基于注解的AOP实现事务控制(下)
(四)spring中事务控制的一组API
(五)spring事务控制的代码准备
(六)spring基于XML的声明式事务控制
(七)spring基于注解的声明式事务控制
(八)spring基于纯注解的声明式事务控制
(九)spring编程式事务控制(上)
(十)spring编程式事务控制(下)
(十一)spring5新特性的介绍
(一)改造:基于XML的AOP实现事务控制
创建一个普通的maven工程,并且拷贝之前 银行转账案例 的代码,如下:
并且在pom.xml里导入AOP相关的坐标(Aspectj用于解析切入点表达式),如下:
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
同时把我们自己写的跟动态代理相关的代码删掉
同时在bean.xml文件中删掉相关的bean标签配置,如下:
接着导入AOP约束,如下:
粘贴进去
在bean.xml文件进行AOP相关的配置,把通知类(TransactionManager)交给spring管理,如下:
<!-- 配置AOP-->
<aop:config>
<!-- 配置通用的切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.zzq.service.impl.*.*(..))"/>
<aop:aspect id="txAdvice" ref="tsManager">
<!-- 配置前置通知:开启事务-->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!-- 配置后置通知:提交事务-->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!-- 配置异常通知:回滚事务-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!-- 配置最终通知:释放连接-->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
运行测试类的转账方法试试,如下:
转帐前
转账中
转账后
我们把异常注释掉,再试试,如下:
可以看到依然可以实现事务控制
(二)改造:基于注解的AOP实现事务控制(上)
创建一个普通的maven工程,把上一个基于XML的AOP实现事务控制项目的代码拷贝过来,如下:
在bean.xml里导入注解相关的约束,如下:
配置spring创建容器时要扫描的包,如下:
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.zzq"></context:component-scan>
配置业务层实现类的注解
配置持久层实现类的注解
对于QueryRunner涉及到纯注解配置的问题,我们不管它
而事务管理器需要被后面的AOP配置使用到,我们待会儿再配置注解
但是ConnectionUtils我们可以使用注解配置(具体配置步骤省略,跟前面相似)
接下来配置事务管理器,如下:
把bean.xml中跟事务管理器和AOP配置相关的代码删掉,并且开启注解AOP支持,如下:
<!-- 开启spring对注解AOP的支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(三)改造:基于注解的AOP实现事务控制(下)
我们运行测试类肯定会报错的,原因如下:
这也是我们之前提到过的spring的一个BUG,用注解配置四种通知类型的时候,会先执行最终通知,再执行后置通知。执行完最终通知的时候连接对象已经被关闭(解绑)了,自然也就无法执行后置通知,无法提交事务,操作无效。
我们具体分析一下问题,如下:
执行完最终通知之后conn显然为null
此时会重新获取连接对象,但是因为此前已经执行过前置通知,前置通知如下:
我们新获取的conn是不会再执行前置通知的了,也就是默认setAutoCommit(true)
那我们能不能再这里加多一行代码,让它强制setAutoCommit(false)
呢?尝试一下
断点调试试试,如下:
执行流程如下:
第一次肯定是没有的,需要获取
虽然设置了两次setAutoCommit(false)
,但是无伤大雅,继续执行
执行方法
先执行了最终通知
之后才执行后置通知
因为之前已经关闭(解绑)了连接对象,所以此时conn为null
其实最终答案是:依然无法正常执行,因为我们把执行的方法绑定在旧的连接对象,而新创建的连接对象什么都没有,就算提交了也只是空提交,所以这种方法是不可行的
注意:我们测试类运行的是转账方法,经过我测试发现此时查询方法是可以正常运行的。虽然说切入点表达式覆盖了所有方法,但是查询方法本身并不需要事务控制,所以即使事务失效了也能正常运行,这不在本篇博客的讨论范围之内
那我们要怎么处理呢?只能使用环绕通知了,我们把它改造成环绕通知,如下:
@Around("pt1()")
public Object arroundAdvice(ProceedingJoinPoint pjd) {
Object rtValue = null;
try {
//1.获取参数
Object[] args = pjd.getArgs();
//2.开启事务
this.beginTransaction();
//3.执行方法
rtValue = pjd.proceed(args);
//4.提交事务
this.commit();
//返回结果
return rtValue;
} catch (Throwable e) {
//5.回滚事务
this.rollback();
throw new RuntimeException(e);
} finally {
//6.释放资源
this.release();
}
}
(四)spring中事务控制的一组API
我们看回基于XML的案例,如下:
发现事务管理器是我们自己写的,其实spring是集成了事务管理器的
我们可以直接使用spring自带的事务管理器
同时还发现AOP配置有一些部分是多余的,比如:
- 最终通知可以内嵌入后置通知或者异常通知
- 可以在绑定线程的同时设置
setAutoCommit(false)
,这样可以省掉前置通知
- 同时后置通知和异常通知又是互斥的,或许没有配置的必要性?
接下来详细介绍spring集合的事务控制的API,我们先明确三个点:
JavaEE 体系进行分层开发,事务处理位于业务层, Spring 提供了分层设计业务层的事务处理解决方案
spring 框架为我们提供了一组事务控制的接口,这组接口是在spring-tx-5.0.2.RELEASE.jar中
spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置(XML/注解)的方式实现
我们学习的重点是使用配置的方式实现
接下来详细介绍API
我们导入pom.xml的maven坐标后,查看一下PlatformTransactionManager(spring提供的事务控制接口)的源码,如下:
可以看到只有后置通知和异常通知,跟我们之前分析的很类似
但是这只是一个接口,我们需要的是实现类,下面介绍实现类,如下:
org.springframework.jdbc.datasource.DataSourceTransactionManager
使用 SpringJDBC 或 iBatis 进行持久化数据时使用
org.springframework.orm.hibernate5.HibernateTransactionManager
使用 Hibernate 版本进行持久化数据时使用
除了PlatformTransactionManager接口以外,还有一个常用的TransactionDefinition接口,如下:
获取事务对象名称:
String getName()
获取事务隔离级别:
int getIsolationLevel()
spring提供的事务隔离级别默认跟数据库的隔离级别一致
获取事务传播行为:
int getPropagationBehavior()
传播行为是指:什么情况下必须要开启事务(增删改),什么情况下可以不开启事务(查)
获取事务超时时间:
int getTimeout()
当提交或者回滚达到多长时间就过期
获取事务是否只读:
boolean isReadOnly()
读写型事务:增加、删除和修改会开启事务
只读型事务:执行查询时,也会开启事务
-
隔离级别
(我们通常设置为ISOLATION_DEFAULT就可以了)事务隔离级别反映事务提交并发访问时的处理态度 ISOLATION_DEFAULT 默认级别,归属下列的某一种(spring的默认级别是数据库的默认级别) ISOLATION_READ_UNCOMMITTED 可以读取未提交数据 ISOLATION_READ_COMMITTED 只能读取已提交数据,解决脏读问题(Oracle的默认级别) ISOLATION_REPEATABLE_READ 是否读取其它事务提交修改后的数据,解决不可重复读问题(MySQL的默认级别) ISOLATION_SERIALIZABLE 是否读取其他事务提交添加后的数据,解决幻读问题
-
传播行为
(我们通常对增删改使用REQUIRED,对查使用SUPPORTS)REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务,就加入到该事务中(默认值) SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务) MANDATORY 使用当前事务,如果当前没有事务,就抛出异常 REQUERS_NEW 新建事务,如果当前在事务中,把当前事务挂起 NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 NEVER 以非事务方式运行,如果当前存在事务,抛出异常 NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行类似REQUIRED的操作
-
超时时间
默认值是-1,没有超时限制。如果有,以秒为单位进行设置
-
是否是只读事务
建议查询时设置为只读
除了以上两个常用的接口,还有最后一个TransactionStatus接口,用于提供事务具体的运行状态
TransactionStatus接口描述了某个时间点上事务对象的状态信息
刷新事务
void flush()
获取是否存在存储点
boolean hasSavepoint()
获取事务是否完成
boolean isCompleted()
获取事务是否为新的事务
boolean isNewTransaction()
获取事务是否回滚
boolean isRollbackOnly()
设置事务回滚
void setRollbackOnly()
(五)spring事务控制的代码准备
创建一个普通的maven工程,并且导入相关JAR包,如下:
<packaging>jar</packaging>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
导入上一篇博客的JdbcTemplate项目里面的实体类、业务层接口、业务层实现类以及bean.xlm
因为事务是写在业务层的,所以我们要创建业务层的接口和实现类,如下:
同时还要在bean.xml补全业务层的配置,如下:
<!-- 配置账户的业务层-->
<bean id="accountService" class="com.zzq.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
编写测试类,如下:
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
此时的功能是普通的转账功能,不涉及事务,可以先测试一下以保证环境搭建没问题
接下来会分别从XML和注解两个方向去改造该项目
(六)spring基于XML的声明式事务控制
创建一个普通的maven工程,拷贝上述项目的代码与配置,如下:
然后在bean.xml做一系列配置,如下:
1、配置事务管理器
2、配置事务的通知
此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系
使用AOP配置的aop:advisor的标签配置
5、配置事务的属性
是在事务的通知tx:advice标签的内部
- 第一步,配置事务管理器
<!-- 1、配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
</bean>
然后给事务管理器注入依赖
<!-- 1、配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
- 第二步,配置事务的通知,此时我们需要导入事务的约束
我们把事务和AOP的约束一起导进来,如下:
接下来配置事务的通知,如下:
<!-- 2、配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
</tx:advice>
- 第三步,配置AOP中的通用切入点表达式
<!-- 3、配置aop-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.zzq.service.impl.*.*(..))"/>
</aop:config>
- 第四步,建立事务通知和切入点表达式之间的关系,如下图:
这个操作有点类似我们之前配置的切面,或者说这也是切面的一种把
<!-- 3、配置aop-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.zzq.service.impl.*.*(..))"/>
<!-- 4、建立事务通知和切入点表达式的关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
-
第五步,配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别 propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写 timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位 rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值:表示任何异常都回滚 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值:表示任何异常都回滚
如果一个方法一个方法地配置就太过于麻烦,我们可以结合通配符,如下:
<!-- 配置事务的属性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
注意:全通配*
的优先级是低于部分通配find*
的优先级的
bean.xml的总代码如下:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置账户的业务层-->
<bean id="accountService" class="com.zzq.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置账户的持久层-->
<bean id="accountDao" class="com.zzq.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=true&serverTimezone=UTC"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- spring中基于XML的声明式事务控制配置步骤-->
<!-- 1、配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2、配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 3、配置aop-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.zzq.service.impl.*.*(..))"/>
<!-- 4、建立事务通知和切入点表达式的关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
测试类运行结果时没问题的,可以成功控制事务,这里不展示了
总结:
- 使用spring自带的事务管理器(需要AOP的支持)的好处就是不用我们手写事务管理器
- 通过简单的bean.xml配置,以后所有的业务层都会被事务控制,非常方便
(七)spring基于注解的声明式事务控制
创建一个普通的maven工程,把上面的spring基于XML的声明式事务控制项目的代码拷贝过来
首先在bean.xml导入注解的约束,如下:
先改造业务层,如下:
对于持久层需要做一些改动,如下:
我们使用注解就不能集成JdbcDaoSupport了(之前介绍过),要做一些改动,如下:
同时需要在bean.xml配置JdbcTemplate,如下:
<!-- 配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
最重要的一点:配置spring要扫描的包的路径,如下:
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.zzq"></context:component-scan>
然后只保留事务管理器,其他的都注释掉,如下:
现在开始正式配置基于注解的声明式事务控制,如下:
1、配置事务管理器
2、开启spring对注解事务的支持
3、在需要事务支持的地方使用@Transactional注解
- 第一步,配置事务管理器(跟XML的那边一样,这里不重复了,直接贴代码)
<!-- 1、配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
- 第二步,开启spring对注解事务的支持
<!-- 2、开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
- 第三步,在需要事务支持的地方使用
@Transactional
注解
我们运行测试类结果是成功的,可以控制事务
使用注解配置似乎不需要配置事务的属性?我们进去@Transactional
注解的源码看看,如下:
我们刚才什么属性都没写,用的全部都是默认值,我们可以指定属性,如下:
但是如果我们有很多个方法,比如说有10个方法,5个需要只读型,5个需要读写型
这样就要写6个注解很麻烦,所以就配置事务而言,XML更合适
(八)spring基于纯注解的声明式事务控制
我们先看看现在bean.xml还剩下多少代码
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.zzq"></context:component-scan>
<!-- 配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=true&serverTimezone=UTC"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- spring中基于注解的声明式事务控制配置步骤-->
<!-- 1、配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2、开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
创建一个普通的maven工程,把上面基于注解的声明式事务控制项目的代码及配置拷贝过来,如下:
我们先创建一个配置类(SpringConfiguration)替换以下代码
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.zzq"></context:component-scan>
<!-- 配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test?useSSL=true&serverTimezone=UTC"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
首先写一个jdbcConfig.properties配置文件,如下:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=true&serverTimezone=UTC
jdbc.username=root
jdbc.password=root
然后写一个JdbcConfig类
/**
* 和连接数据库相关的配置类
*/
@PropertySource("classpath:jdbcConfig.properties")
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建JdbcTemplate对象
*
* @param dataSource
* @return
*/
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
*
* @return
*/
@Bean(name = "dataSource")
public DataSource createDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
最后写一个SpringConfiguration类就可以了,如下:
/**
* spring的配置类,相当于bean.xml
*/
@Configuration//表明是一个配置类
@ComponentScan("com.zzq")//配置包路径
@Import(JdbcConfig.class)
public class SpringConfiguration {
}
此时还剩下两段代码,如下:
我们先用@EnableTransactionManagement
注解代替上面红框的代码,如下:
问题只剩下最后一个,如图:
(首先判断该类是用构造函数创建的,并且是带参构造,传入了DataSource类型的对象)
该类跟之前的JdbcTemplate是同一种情况,这是JDK官方提供的类,不是我们自己写的类,我们无法在类上加注解,我们解决方案一样,创建一个配置类,如下:
/**
* 和事务管理器相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器对象
*
* @param dataSource
* @return
*/
@Bean(name = "transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
最后补全注解即可,如下:
测试类也要做修改,如下:
运行测试类结果也是成功的,可以控制事务,这里不展示了
(九)spring编程式事务控制(上)
前提:spring的编程式事务控制是不常用的,了解即可
创建一个普通的maven工程,拷贝一份之前项目的代码,如下图:
(只有一对业务层代码、一对持久层代码、一个实体类和一个测试类,不需要详细展示了)
现在该案例是没有事务支持的,我们接下来介绍编程式事务
无论是声明式还是编程式事务控制都离不开提交和回滚操作,都需要配置事务管理器,如下:
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
编程式事务控制需要配置一个事务模板对象,如下:
<!-- 配置事务模板对象-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"></property>
</bean>
事务模板对象的部分源码如下:
(十)spring编程式事务控制(下)
我们只需要在业务层使用该模板对象即可,如下:
我们在需要使用事务的方法中执行模板对象的execute()
方法即可,如下:
并且把所有需要事务支持的操作放到doInTransaction()
方法中
我们要给每一个需要事务支持的方法都要在其内部实现上述的匿名内部类,所以重复性的代码很多
所以不推荐使用编程式事务控制,更推荐使用基于XML的声明式事务控制,一劳永逸
测试类的运行结果是成功的,可以控制事务,这里不再展示了
(十一)spring5新特性的介绍
与JDK相关的升级
核心容器的更新
JetBrains Kotlin 语言支持
响应式编程风格
Junit5 支持
依赖类库的更新
与JDK相关的升级
首先spring5.0是基于JDK1.8编写的
spring5.0 在 2017 年 9 月发布了它的 GA(通用)版本。该版本是基于 jdk8 编写的, 所以 jdk8 以下版本
将无法使用。 同时,可以兼容 jdk9 版本
tomcat 版本要求 8.5 及以上
注:
我们使用 jdk8 构建工程,可以降版编译。但是不能使用 jdk8 以下版本构建工程
我们先对比一下JDK1.8和JDK1.7的区别,如下:
public class Test {
//循环次数定义:10亿次
private static final int loopCnt = 1000 * 1000 * 1000;
public static void main(String[] args) throws Exception {
//输出jdk的版本
System.out.println("java.version=" + System.getProperty("java.version"));
t1();
t2();
t3();
}
// 每次重新生成对象
public static void t1() {
long s = System.currentTimeMillis();
for (int i = 0; i < loopCnt; i++) {
Person p = new Person();
p.setAge(31);
}
long e = System.currentTimeMillis();
System.out.println("循环10亿次创建对象的时间:" + (e - s));
}
// 同一个对象
public static void t2() {
long s = System.currentTimeMillis();
Person p = new Person();
for (int i = 0; i < loopCnt; i++) {
p.setAge(32);
}
long e = System.currentTimeMillis();
System.out.println("循环10亿次给同一对象赋值的时间: " + (e - s));
}
//使用反射创建对象
public static void t3() throws Exception {
long s = System.currentTimeMillis();
Class<Person> c = Person.class;
Person p = c.newInstance();
Method m = c.getMethod("setAge", Integer.class);
for (int i = 0; i < loopCnt; i++) {
m.invoke(p, 33);
}
long e = System.currentTimeMillis();
System.out.println("循环10亿次反射创建对象的时间:" + (e - s));
}
static class Person {
private int age = 20;
public int getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
}
注意:更改JDK版本需要对以下四个地方做出改动
运行结果如下:
有此我们可以看出,在反射创建对象上, JDK8的运行速度更快,而我们框架使用的基本上都是反射
所以说spring5.0速度更快,JDK8除了速度更快以外还有以下两个特性,如下:
第二: @NonNull 注解和@Nullable 注解的使用
用 @Nullable 和 @NotNull 注解来显示表明可为空的参数和以及返回值
这样就够在编译的时候处理空值而不是在运行时抛出 NullPointerExceptions
第三: 日志记录方面
Spring Framework 5.0 带来了 Commons Logging 桥接模块的封装, 它被叫做 spring-jcl 而不是标准的 Commons Logging
当然,无需任何额外的桥接,新版本也会对 Log4j 2.x, SLF4J, JUL( java.util.logging) 进行自动检测
核心容器的更新
Spring Framework 5.0 现在支持候选组件索引作为类路径扫描的替代方案
该功能已经在类路径扫描器中添加,以简化添加候选组件标识的步骤
应用程序构建任务可以定义当前项目自己的 META-INF/spring.components 文件
在编译时,源模型是自包含的, JPA 实体和 Spring 组件是已被标记的
从索引读取实体而不是扫描类路径对于小于 200 个类的小型项目是没有明显差异
但对大型项目影响较大,加载组件索引开销更低。因此,随着类数的增加,索引读取的启动时间将保持不变
加载组件索引的耗费是廉价的
因此当类的数量不断增长,加上构建索引的启动时间仍然可以维持一个常数,不过对于组件扫描而言,启动时间则会有明显的增长
这个对于我们处于大型 Spring 项目的开发者所意味着的,是应用程序的启动时间将被大大缩减
虽然 20或者 30 秒钟看似没什么,但如果每天要这样登上好几百次,加起来就够你受的了。使用了组件索引的话,就能帮助你每天过的更加高效
你可以在 Spring 的 Jira 上了解更多关于组件索引的相关信息
JetBrains Kotlin 语言支持
Kolin概述:是一种支持函数式编程编程风格的面向对象语言
Kotlin 运行在 JVM 之上,但运行环境并不限于 JVM
{
("/movie" and accept(TEXT_HTML)).nest {
GET("/", movieHandler::findAllView)
GET("/{card}", movieHandler::findOneView)
}
("/api/movie" and accept(APPLICATION_JSON)).nest {
GET("/", movieApiHandler::findAll)
GET("/{id}", movieApiHandler::findOne)
}
}
Kolin 注册 bean 对象到 spring 容器
val context = GenericApplicationContext {
registerBean()
registerBean { Cinema(it.getBean()) }
}
响应式编程风格
此次 Spring 发行版本的一个激动人心的特性就是新的响应式堆栈 WEB 框架
这个堆栈完全的响应式且非阻塞,适合于事件循环风格的处理,可以进行少量线程的扩展
Reactive Streams 是来自于 Netflix, Pivotal, Typesafe, Red Hat, Oracle, Twitter 以及
Spray.io 的工程师特地开发的一个 API。它为响应式编程实现 的实现提供一个公共 的 API,好实现
Hibernate 的 JPA。这里 JPA 就是这个 API, 而 Hibernate 就是实现
Reactive Streams API 是 Java 9 的官方版本的一部分
在 Java 8 中, 你会需要专门引入依赖来使用 Reactive Streams API
Spring Framework 5.0 对于流式处理的支持依赖于 Project Reactor 来构建, 其专门实现了Reactive Streams API
Spring Framework 5.0 拥有一个新的 spring-webflux 模块,支持响应式 HTTP 和 WebSocket 客户端
Spring Framework 5.0 还提供了对于运行于服务器之上,包含了 REST, HTML, 以及 WebSocket 风格交互的响应式网页应用程序的支持
在 spring-webflux 中包含了两种独立的服务端编程模型:
使用@Controller 以及 Spring MVC 的其它一些注解
使用 Java 8 lambda 表达式的函数式风格的路由和处理
有 了 Spring Webflux, 你 现 在 可 以 创 建 出 WebClient,
它 是 响 应 式 且 非 阻 塞 的 , 可 以 作 为RestTemplate 的一个替代方案
这里有一个使用 Spring 5.0 的 REST 端点的 WebClient 实现:
WebClient webClient = WebClient.create();
Mono person = webClient.get()
.uri("http://localhost:8080/movie/42")
.accept(MediaType.APPLICATION_JSON)
.exchange()
.then(response -> response.bodyToMono(Movie.class));
Junit5 支持
完全支持 JUnit 5 Jupiter,所以可以使用 JUnit 5 来编写测试以及扩展
此外还提供了一个编程以及扩展模型, Jupiter 子项目提供了一个测试引擎来在 Spring 上运行基于 Jupiter 的测试
另外, Spring Framework 5 还提供了在 Spring TestContext Framework 中进行并行测试的扩展
针对响应式编程模型, spring-test 现在还引入了支持 Spring WebFlux 的 WebTestClient 集成测试的支持,类似于 MockMvc,并不需要一个运行着的服务端
使用一个模拟的请求或者响应, WebTestClient就可以直接绑定到 WebFlux 服务端设施
你可以在这里找到这个激动人心的 TestContext 框架所带来的增强功能的完整列表
当然, Spring Framework 5.0 仍然支持我们的老朋友 JUnit! 在我写这篇文章的时候, JUnit 5 还只是发展到了 GA 版本
对于 JUnit4, Spring Framework 在未来还是要支持一段时间的。
依赖类库的更新
终止支持的类库
Portlet.
Velocity.
JasperReports.
XMLBeans.
JDO.
Guava.
支持的类库
Jackson 2.6+
EhCache 2.10+ / 3.0 GA
Hibernate 5.0+
JDBC 4.0+
XmlUnit 2.x+
OkHttp 3.x+
Netty 4.1+
更多推荐
所有评论(0)