一、Sping声明式事务
1. 编程式事务介绍
整个事务控制代码都需要程序员自己编写的事务叫做编程式事务。
2. 声明式事务介绍
由Spring帮助封装起来固定性代码,只需进行简单的XMl配置就可以完成事务管理,不再编写事务管理代码。这就是Spring非常重要的功能之一:声明式事务。
3. 声明式事务式底层实现
声明式事务是基于AOP实现的。把开启事务的代码放在前置通知中,把事务回滚和事务提交的代码放在了后置通知中
二、声明式事务代码
1.配置声明式事务
需要在配置文件中引入xmlns:tx命名空间。
<!-- 3. 配置事务管理类:Spring封装事务固定套路代码的类-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 事务管理必须连接数据库,需要注入数据源对象 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. Spring提供了很多事务控制方法,设定哪个方法应用具体哪种方案 -->
<!-- 目前只是设定insert方法是一个事务单元,没有具体设定其他详细的事务配置-->
<!-- 只有方法出现了异常触发异常通知,实现事务回滚,所以绝对不能在service里面try...catch-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="insert"/>
</tx:attributes>
</tx:advice>
<!-- 1. 设定哪个方法需要被声明式事务管理,使用AOP完成,通过切点定义需要进行声明式事务管理的方法 -->
<aop:config>
<aop:pointcut id="mypoint" expression="execution(* com.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint"></aop:advisor>
</aop:config>
2.使用通配符配置声明式事务
在配置声明式事务通知时,使用*进行配置。修改applicationContext.xml
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- <tx:method name="select*"/>-->
<!-- 所有的方法都需要进行事务管理。在配置方法名称是支持*作为通配符 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="mypoint" expression="execution(* com.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint"></aop:advisor>
</aop:config>
3.注解配置声明式事务
Spring 注解配置事务时,只需要在需要有事务管理的方法上添加@Transactional注解。
必须保证配置注解的方法所在的类已经放入到Spring容器中。
配置注解扫描:
<context:component-scan base-package="com.service.impl"></context:component-scan>
开启事务注解的支持
<tx:annotation-driven></tx:annotation-driven>
必须配置事务管理器类
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
重要提示:
@TransactionManager 默认寻找叫做transactionManager的事务管理器。如果没有找到会报异常NoSuchBeanDefinitionException。所以,如果希望配置注解时简单点直接写@Transactional就生效,就必须在XML配置事务管理器时,id必须叫做transactionManager。
如果在XML配置事务管理器时,id不叫transactionManager,需要在@Transactional(transactionManager="XML配置时id值")。
@Transactional用在类上,整个类中方法都生效。
@Transactional用在方法上,该方法生效。用在方法上优先级更高。
三、声明式事务四个基础属性介绍
1. name属性
配置哪些方法需要有事务控制,支持*通配符
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 表示所有以insert开头的方法需要进行事务管理 -->
<tx:method name="insert*"></tx:method>
<!-- 表示所有方法需要进行事务管理-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
2. readonly属性(true|false)
是否为只读事务。
true:告诉数据库此事务为只读事务。底层支持查询的代码逻辑,不走提交事务和回滚事务的代码,会对性能有一定提升,所以只要是查询的方法,建议设置readonly="true"。
false(默认值):需要控制的事务。新增,删除,修改,不设置readonly属性或设置readonly="false"
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 所有select开头的方法执行查询逻辑 -->
<!-- 多个tx:method标签时取交集-->
<tx:method name="select*" read-only="true"></tx:method>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
3. rollback-for属性
异常类型全限定路径,表示出现什么类型的异常进行数据回滚。
默认运行时异常及子类异常回滚,检查时异常不回滚。
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 定义只要方法出现了Exception类型异常及子类型异常都需要进行回滚 -->
<tx:method name="insert*" rollback-for="java.lang.Exception"></tx:method>
<tx:method name="select*" read-only="true"></tx:method>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
4. no-rollback-for属性
异常类型全限定路径,当出现什么异常的时候不进行数据回滚。
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 定义只要出现运行时异常就不回滚 -->
<tx:method name="insert*" no-rollback-for="java.lang.RuntimeException"></tx:method>
<tx:method name="select*" read-only="true"></tx:method>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
四、事务传播行为
事务传播行为:当出现service的方法调用另一个service方法时(这些方法都被声明式事务管理),这些方法如何进行事务管理。
注意:
1. 默认情况下都认为每个方法都是没有事务的(事务自动提交)。
2. 整个调用最终都是在调用者里面统一提交回滚。
3. 在声明式事务中,如果是同一个类的多个方法相互调用,属于同一个事务。
4. 如果希望测试效果,必须把方法放入到多个不同的业务类中进行测试。
出现上面问题3的原因:
在同一个Service中测试的效果,如果是同一个类的多个方法相互直接调用的流程。因为声明式事务是基于AOP实现的,AOP是基于动态代理实现的,为同一个对象创建一个代理对象,所以实现出来的效果只有对第一个调用的方法添加上了声明式事务管理,其他方法都是普通的方法调用。
可以通过进行配置tx:method或@Transactional中的propagation属性来进行传播行为的设置,propagation属性的可选值有:
REQUIRED(默认值):如果当前有事务则加入到事务中。如果当前没有事务则新增事务。
NEVER:必须在非事务状态下执行,如果当前没有事务,正常执行,如果当前有事务,报错。
NESTED:必须在事务状态下执行。如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务(子事务)。
REQUIRES_NEW:必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务挂起, 重新建个事务。(调用者统一提交回滚)
SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行。
NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前事务挂起。
MANDATORY:必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务,报错。(可以配置在入口方法)
五、事务隔离级别
多个事务同时操作数据库时,允许多个事务操作的方式就是事务隔离级别。事务隔离级别主要是通过添加锁操作实现的。事务隔离级别主要是解决高并发下脏读、幻读、不可重复读问题的。
脏读:
事务A没有提交事务,事务B读取到事务A未提交的数据,这个过程称为脏读。读取到的数据叫做脏数据。
不可重复读:
当事务A读取到表中一行数据时,同时另一个事务修改这行数据,事务A读取到的数据和表中真实数据不一致。
幻读:
事务A对表做查询全部操作,事务B向表中新增一条数据。事务A查询出来的数据和表中数据不一致,称为幻读。
可以在tx:method或@Transactional中设置属性isolation的值来进行配置。(select @@transaction_isolation查询数据库支持的事务隔离级别)
isolation可取值分别为:
DEFAULT:
表示用数据库的隔离级别,MySQL8默认的事务隔离级别REPEATABLE_READ。
READ_UNCOMMITTED:
读未提交(脏读,幻读,不可重复读)。
READ_COMMITTED:
读已提交(幻读,不可重复读)。
REPEATABLE_READ:
可重复读(幻读)。
SERIALIZABLE
串行读来通过牺牲性能解决脏读、不可重复度、幻读问题。
六、属性配置文件
1. 属性配置文件
在配置applicationcontext.xml的时候是直接将数据源参数在配置文件中直接配置的,就是数据库的链接参数。spring也提供了一种解耦合的配置方式,就是将数据源中的数据库链接参数单独的写在一个配置文件中。
属性配置文件: 需要直接声明在src目录下,文件名随意。键名任意,但是用户名不能使用username,可能会和系统的变量冲突。
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/account?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
user=root
password=root
applicationcontext.xml配置文件:
<!--配置参数配置文件路径-->
<context:property-placeholder location="classpath:db.properties"/>
<!--配置数据源bean-->
<bean id="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driver}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
七、获取属性文件中的值
@Value
作用:用来替换配置文件中的属性注入的。
使用:在属性上声明,值为${“键名”}
注意:使用此注解的注入,无需提供get/set方法。
@Component("u")
public class User {
@Value("1")
private Integer uid;
@Value("${driver}")
private String uname;
private String pwd;
......
}
八、Bean的生命周期
在这种情况下会调用类的构造方法进行实例化。
-
通过标签的init-method和destory-method自定义初始化和销毁方法。
-
实现各种Aware接口,例如BeanNameAware、BeanFactoryAware、ApplicationContextAware等,可以获取bean名字信息,bean工厂信息,容器信息。
-
通过InitializingBean,DisposableBean实例化Bean和销毁Bean。
-
通过BeanFactoryPostProcessor,BeanPostProcessor进行增强。
但是当前类不能实现BeanFactoryPostProcessor和BeanPostProcessor接口,且不能同一个类同时实现BeanFactoryPostProcessor,BeanPostProcessor。
1.定义bean的信息
1.xml
2.注解
3.JavaConfig
4....
2.使用BeanDefinitionReader实现类读取Bean的定义信息
1.bean的定义信息(BeanDefinition)
2.[可选]自定义实现类实现BeanDefinitionReader接口
3.解析bean定义信息
4.[可选] 实现BeanFactoryPostProcessor
1.操作bean的定义信息
2....
5.实例化bean
6.属性填充
7.[可选] 实现相关Aware接口(查看bean的信息,进行相关操作,...)
8.[可选] 实现BeanPostProcessor before(操作bean)
9.初始化
10.[可选] 实现BeanPostProcessor after(操作bean)
11.添加到单例池(spring存储bean的容器,本质为Map)