一、Sping声明式事务
1. 编程式事务介绍
整个事务控制代码都需要程序员自己编写的事务叫做编程式事务。
@Override
public int deleteById(int[] ids) {
SqlSession session = FactoryUtil.factory.openSession();
int index = 0;
try {
PeopleMapper peopleMapper = session.getMapper(PeopleMapper.class);
index = peopleMapper.deleteByIds(ids);
session.commit();
} catch (Exception e) {
e.printStackTrace();
session.rollback();
}finally {
session.close();
}
return index;
}
2. 声明式事务介绍
Spring框架发现既然都是固定性代码,就由Spring帮助封装起来。封装后对外让程序员只需进行简单的XMl配置就可以完成事务管理,不再编写事务管理代码。这就是Spring非常重要的功能之一:声明式事务。
3. 声明式事务式底层实现
声明式事务是基于AOP实现的。程序员只需要编写调用持久层代码和业务逻辑代码。把开启事务的代码放在前置通知中,把事务回滚和事务提交的代码放在了后置通知中
4. 如何配置声明式事务(重要)
1.需要依赖spring-tx。
<!-- 事务管理包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.16</version>
</dependency>
2.需要在配置文件中引入xmlns:tx命名空间。
<!--1. 配置声明式事务(事务开启,事务提交,事务回滚都进行了封装,配合AOP完成事务的控制 -->
<!--
前置通知:开启事务
后置通知:事务提交
异常通知:事务回滚
-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 事务管理必须连接数据库,需要注入数据源对象 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2. 配置事务通知 配置哪些方法织入事务的通 -->
<!-- 只有方法出现了异常触发异常通知,实现事务回滚,所以绝对不能在service里面try...catch-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- 配置的方法才会织入事务的通知 -->
<tx:attributes>
<!-- -->
<tx:method name="alertAccountt"/>
<!-- <tx:method name="select*"/>-->
<!-- 所有的方法都需要进行事务管理。在配置方法名称是支持*作为通配符 -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--
3. 设定哪个方法需要被声明式事务管理,使用AOP完成,
根据切点表达式获取切入点(目标对象中的目标方法),为目标对象创建代理对象,
将通知和目标方法完成织入。
-->
<aop:config>
<aop:pointcut id="mypoint" expression="execution(* com.bjsxt.service.impl.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint"></aop:advisor>
</aop:config>
经过以上配置,被配置的方法已经进行了声明式事务管理。
5. 注解配置声明式事务(重要)
Spring 注解配置事务时,只需要在需要有事务管理的方法上添加@Transactional注解。
必须保证配置注解的方法所在的类已经放入到Spring容器中。
配置注解扫描:
<context:component-scan base-package="com.bjsxt.service.impl"/>
开启事务注解的支持:
<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用在方法上,该方法生效。用在方法上优先级更高。
三、声明式事务四个基础属性介绍
<tx:method>
标签有下面属性的配置,@Transaction注解也支持部分属性(如果有则含义与相同,如果没有则表示在注解中不需要配置)。
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>
5. timeout属性
执行超过了设置的超时时间,回滚操作。(超时抛出异常)
四、事务传播行为(面试题)
1. 介绍(重要)
事务传播行为:当出现service的方法调用另一个service方法时(这些方法都被声明式事务管理)这些方法如何进行事务管理。
注意:
1. 默认情况下都认为每个方法都是没有事务的(事务自动提交)。
2. 整个调用最终都是在调用者里面统一提交回滚。
3. 在声明式事务中,如果是同一个类的多个方法相互调用,属于同一个事务。
原因:
因为声明式事务是基于AOP实现的,AOP是基于动态代理实现的,为同一个对象创建一个代理对象,所以实现出来的效果只有对第一个调用的方法添加上了声明式事务管理,其他方法都是普通的方法调用。
可以通过进行配置tx:method或@Transactional中的propagation属性来进行传播行为的设置 。
@Transactional(propagation = Propagation.MANDATORY)
2. propagation属性的可选值
Propagation.REQUIRED(默认) | 当前有事务,使用当前事务。当前没有事务,开启新的事务。 |
Propagation.NEVER | 必须在非事务状态下执行。没有事务,正常运行。有事务,抛出异常。 |
Propagation.NESTED | 必须在事务状态下执行。没有事务,创建事务。有事务,创建嵌套事务。 |
Propagation.REQUIRES_NEW | 没有事务,创建事务。有事务,挂起当前事务,创建新的事务。最后统一操作。 |
SUPPORTS | 没有事务,非事务执行。有事务,使用当前事务。 |
Propagation.NOT_SUPPORTED | 非事务下执行。有事务,挂起事务,以非事务执行,执行后,恢复挂起的事务。 |
propagation = Propagation.MANDATORY | 事务下执行。没有事务,抛出异常。 |
五、事务隔离级别(面试题)
- 多个事务同时操作数据库时,允许多个事务操作的方式就是事务隔离级别。
- 事务隔离级别主要是通过添加锁操作实现的。
- 事务隔离级别出现场景:高并发场景。
- 事务隔离级别主要是解决高并发下脏读、幻读、不可重复读问题的。
1.事务隔离级别解决的问题
脏读 | 一个事务读取到了其他事务未提交的数据。 |
不可重复读 | 一个事务多次读取,读取的数据不一致,其他事务进行了修改的操作。 |
幻读 | 一个事务多次读取,读取的数据不一致,其他事务进行了添加的操作。 |
可以在 tx:method 或 @Transactiona l中设置属性 isolation 的值来进行配置事务隔离级别。(select @@transaction_isolation查询数据库支持的事务隔离级别)
2. isolation属性的值
Isolation.READ_UNCOMMITTED | 读未提交(出现,脏读,不可重复读,幻读)。 |
Isolation.READ_COMMITTED | 读已提交(出现,不可重复读,幻读)。 |
Isolation.REPEATABLE_READ | 可重复读(解决常规的幻读)。 |
Isolation.SERIALIZABLE | 序列化(没有任何问题)。为表添加表锁,同一时刻只能一个事务操作,牺牲性能 。 |
可重复读中MySQL采用了MVCC版本控制:
不添加锁的读,快照读(读取的数据状态进行临时存储,快照读读取临时数据),解决幻读(修改全部数据会出现幻读)。
六、属性配置文件
1. 属性配置文件
配置文件需要直接声明在src目录下,文件名随意。键名任意,但是用户名不能使用username,可能会和系统的变量冲突。
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/account?useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
user=root
password=1234
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 的生命周期_spring bean的生命周期_老周聊架构的博客-CSDN博客
Spring中Bean的生命周期就是指Bean从初始化到销毁的过程。Bean最简单的实现就是直接使用<bean>
标签定义这个Bean。
1. 生命周期流程图
在这种情况下会调用类的构造方法进行实例化。
通过标签的init-method和destory-method自定义初始化和销毁方法。
实现各种Aware接口,例如BeanNameAware、BeanFactoryAware、ApplicationContextAware等,可以获取bean名字信息,bean工厂信息,容器信息。
通过InitializingBean,DisposableBean实例化Bean和销毁Bean。
通过BeanFactoryPostProcessor,BeanPostProcessor进行增强。
- BeanFactoryPostProcessor 和 BeanPostProcessor 这两个接口,都是 Spring 初始化 bean 时对外 暴露的扩展点,一般叫做 Spring 的 Bean 后置处理器接口,作用是为 Bean 的初始化前后 提供可扩展的空间。
- Spring 中的 BeanFactoryPostProcessor 在实例化之前被调用,而 BeanPostProcessor 则是在实例化过程中使用。
- 但是当前类不能实现BeanFactoryPostProcessor和BeanPostProcessor接口,且不能同一个类同时实现BeanFactoryPostProcessor,BeanPostProcessor。
Bean生命周期流程:
- 编写bean的定义信息(xml,注解)。
- 通过BeanDefinitionReader 读取bean的定义信息。
- 解析出bean的定义信息。
- 可以通过BeanFactoryPostProcessor接口实现类,操作bean的定义信息。
- 实例化bean对象。
- 属性注入。
- 可以使用相关的Aware接口,可以获取bean的相关信息,容器信息...。
- 可以使用BeanPostProcessor接口中before方法操作对象。
- 可以使用init-method调用自定义的初始化方法。
- 可以使用BeanPostProcessor接口中after方法操作对象。
- 存储到单例池(一级缓存中)。
2. 生命周期效果演示
2.1 创建实体类
//继承了接口BeanNameAware, BeanFactoryAware, ApplicationContextAware
ublic class People implements BeanNameAware, BeanFactoryAware, ApplicationContextAware {
private int id;
public People() {
System.out.println("实例化了");
}
public void init(){
System.out.println("初始化了");
}
public void destroy(){
System.out.println("销毁了");
}
public void setId(int id) {
this.id = id;
System.out.println("属性赋值了:" + id);
}
@Override
public void setBeanName(String s) {
System.out.println("BeanNameAware 中setBeanName方法");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("BeanFactoryAware 中setBeanFactory方法");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("ApplicationContextAware 中setApplicationContext方法");
}
}
2.2 增强处理
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化前增强:" + beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("初始化后增强:" + beanName);
return bean;
}
}
2.3 配置
<bean class="com.bjsxt.pojo.MyBeanPostProcessor"/>
<bean id="people" class="com.bjsxt.pojo.People" init-method="init" destroy-method="destroy">
<property name="id" value="1"/>
</bean>