一、Sping声明式事务
1. 编程式事务介绍
整个事务控制的代码都需要程序员自己编写。包含:开启事务(openSession(),创建SqlSession时MyBatis底层自动创建Transaction对象)、提交事务(session.commit())、回滚事务(session.rollback()),像这种整个事务控制代码都需要程序员自己编写的事务叫做编程式事务。
2. 声明式事务介绍
但是当在MyBatis项目中做多个DML操作时,就会发现:上面的代码是固定性套路代码,很多代码是冗余代码
Spring框架发现既然都是固定性代码,就由Spring帮助封装起来。封装后对外让程序员只需进行简单的XMl配置就可以完成事务管理,不再编写事务管理代码。这就是Spring非常重要的功能之一:声明式事务。
3. 声明式事务式底层实现
声明式事务是基于AOP实现的。程序员只需要编写调用持久层代码和业务逻辑代码。把开启事务的代码放在前置通知中,把事务回滚和事务提交的代码放在了后置通知中
相当于把这些冗余的代码通过通知注入到执行sql的方法中,实现事务的控制
二、声明式事务代码演示
1.搭建基本演示环境
1.1 创建项目并配置依赖
新建Maven项目,并配置依赖。
依赖中包含了Mybatis依赖、数据库驱动依赖、Spring核心依赖、Spring整合MyBatis依赖、log4j依赖、spring-test模块依赖、junit4依赖、AOP相关依赖以外,还多了一个新的依赖spring-tx。
<!-- 事务管理包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.16</version>
</dependency>
4. 如何配置声明式事务
<!-- 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.sh.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint"></aop:advisor>
</aop:config>
6. 注解配置声明式事务(常用)
Spring 注解配置事务时,只需要在需要有事务管理的方法上添加@Transactional注解。
必须保证配置注解的方法所在的类已经放入到Spring容器中。
配置注解扫描:
<context:component-scan base-package="com.sh.service.impl"></context:component-scan>
配置事务管理器类:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
使声明式注解生效:
<tx:annotation-driven></tx:annotation-driven>
使用注解配置事务
重要提示:
@TransactionManager 默认寻找叫做transactionManager的事务管理器。如果没有找到会报异常NoSuchBeanDefinitionException。所以,如果希望配置注解时简单点直接写@Transactional就生效,就必须在XML配置事务管理器时,id必须叫做transactionManager。
如果在XML配置事务管理器时,id不叫transactionManager,需要在@Transactional(transactionManager="XML配置时id值")。
@Transactional用在类上,整个类中方法都生效。
@Transactional用在方法上,该方法生效。用在方法上优先级更高。
声明式事务的属性,注解和配置文件都有,注解更多
name属性: 配置哪些方法需要事务控制,支持通配符
readonly属性 : 是否为只读事务 ,常用与查询操作 @Transaction(readonly = true)
rollback-for属性:异常类型全限定路径,表示出现什么类型的异常进行数据回滚。
no-rollback-for属性:定义出现的异常出现了不处理
四、事务传播行为(面试题)
事务传播行为:当出现service的方法调用另一个service方法时(这些方法都被声明式事务管理),这些方法如何进行事务管理。
注意:
1. 默认情况下都认为每个方法都是没有事务的(事务自动提交)。
2. 整个调用最终都是在调用者里面统一提交回滚。
3. 在声明式事务中,如果是同一个类的多个方法相互调用,属于同一个事务。
4. 如果希望测试效果,必须把方法放入到多个不同的业务类中进行测试。
@Transactional(propagation = 中设置)
REQUIRED(默认值):如果当前有事务则加入到事务中。如果当前没有事务则新增事务。
NEVER:必须在非事务状态下执行,如果当前没有事务,正常执行,如果当前有事务,报错。
NESTED:必须在事务状态下执行。如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务(子事务)。
REQUIRES_NEW:必须在事务中执行,如果当前没有事务,新建事务,如果当前有事务,把当前事务 挂起, 重新建个事务。(调用者统一提交回滚)
SUPPORTS:如果当前有事务就在事务中执行,如果当前没有事务,就在非事务状态下执行。
NOT_SUPPORTED:必须在非事务下执行,如果当前没有事务,正常执行,如果当前有事务,把当前 事务挂起。
MANDATORY:必须在事务内部执行,如果当前有事务,就在事务中执行,如果没有事务,报错。(可 以配置在入口方法)
五、事务隔离级别(面试题)
多个事务同时操作数据库时,允许多个事务操作的方式就是事务隔离级别。事务隔离级别主要是通过添加锁操作实现的。事务隔离级别主要是解决高并发下脏读、幻读、不可重复读问题的。
事务隔离级别出现场景:高并发场景。
Java主要做的就是Web项目(服务端项目),每次客户端发送的都叫请求。运行多人同时请求,每个请求运行同一个方法实际上是多个事务。
脏读:
事务A没有提交事务,事务B读取到事务A未提交的数据,这个过程称为脏读。读取到的数据叫做脏数据。
不可重复读:
当事务A读取到表中一行数据时,同时另一个事务修改这行数据,事务A读取到的数据和表中真实数据不一致。
幻读:
事务A对表做查询全部操作,事务B向表中新增一条数据。事务A查询出来的数据和表中数据不一致,称为幻读。
@Transactional中设置属性isolation的值来进行配置。(select @@transaction_isolation查询数据库支持的事务隔离级别)
isolation可取值分别为:
DEFAULT:
表示用数据库的隔离级别,MySQL8默认的事务隔离级别REPEATABLE_READ。
READ_UNCOMMITTED:
读未提交(脏读,幻读,不可重复读)。
READ_COMMITTED:
读已提交(幻读,不可重复读)。
REPEATABLE_READ:
可重复读(幻读)。
MySQL采用了MVCC版本控制:
1. 不添加锁的读,快照读(读取的数据状态进行临时存储,快照读读取临时数据),解决幻读
2.加锁读,必须读取数据库中的数据,出现幻读
MySQL中锁机制:
1. 显示锁: 1. 执行查询默认不使用锁
2. 查询时使用锁
1. 查询sql for update; 添加排他锁
2. 查询sql lock in share mode; 添加了共享锁
2.自动锁:添加,修改,删除 自动添加排它锁
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的生命周期(面试题)
Spring中Bean的生命周期就是指Bean从初始化到销毁的过程。Bean最简单的实现就是直接使用<bean>
标签定义这个Bean。
<bean id="user" class="com.sh.pojo.User"></bean>
1. 生命周期流程图
在这种情况下会调用类的构造方法进行实例化。
-
通过标签的init-method和destory-method自定义初始化和销毁方法。
-
实现各种Aware接口,例如BeanNameAware、BeanFactoryAware、ApplicationContextAware等,可以获取bean名字信息,bean工厂信息,容器信息。
-
通过InitializingBean,DisposableBean实例化Bean和销毁Bean。
-
通过BeanFactoryPostProcessor,BeanPostProcessor进行增强。