事务隔离级别和传播行为
Isolation:隔离级别
隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。
具体的设置方式(注解):例如 @Transactional(isolation = Isolation.DEFAULT)
隔离级别 | 含义 |
---|---|
DEFAULT | 使用数据库默认的事务隔离级别 |
READ_UNCOMMITTED | 允许读取尚未提交的修改,可能导致脏读、幻读和不可重复读 |
READ_COMMITTED | 允许从已经提交的事务读取,可防止脏读、但幻读,不可重复读仍然有可能发生 |
REPEATABLE_READ | 对相同字段的多次读取的结果是一致的,除非数据被当前事务自生修改。可防止脏读和不可重复读,但幻读仍有可能发生 |
SERIALIZABLE | 完全服从 acid 隔离原则,确保不发生脏读、不可重复读、和幻读,但执行效率最低。 |
Propagation:传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。
具体的设置方式(注解):@Transactional(propagation = Propagation.REQUIRED)
传播行为 | 含义 |
---|---|
REQUIRED | 表示当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚) |
MANDATORY | 表示当前方法必须在一个事务中运行,如果没有事务,将抛出异常 |
NEVER | 表示当方法务不应该在一个事务中运行,如果存在一个事务,则抛出异常 |
NOT_SUPPORTED | 表示该方法不应该在一个事务中运行。如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行 |
SUPPORTS | 表示当前方法不必需要具有一个事务上下文,但是如果有一个事务的话,它也可以在这个事务中运行 |
NESTED | 表示如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。如果封装事务不存在,则同 propagation_required 的一样 |
REQUIRES_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。 |
基于 Aspectj AOP 配置事务
几点说明:
导入外部资源文件
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder
注册组件包扫描,把类上标注了 @Controller @Service @Repository @Component 都会自动加入到 Spring 容器中
<context:component-scan base-package="zfcoding.tx.aspectaop"></context:component-scan>
<<tx:advice>> 配置一个事物通知,即执行的方法隔离级别和传播行为,<<aop:config>> 配置事务通知在类上执行操作(切入点)。
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 为连接点指定事务属性 -->
<tx:method name="insert*" isolation="DEFAULT" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="point" expression="execution (* zfcoding.tx.aspectaop..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="point"></aop:advisor>
</aop:config>
实现步骤:
1、定义数据库的配置文件(db.properties), 定义业务类 UserDao,UserService。
2、定义 Spring 的配置文件(spring-aspect.xml)
导入通过 <<context:property-placeholder>> 导入数据库配置文件,然后通过包扫描的方式 <<context:component-scan>> 把 UserDao,UserService 注册到 Spring 的容器当中,配置数据额源,JDBC 的模板,最后事务管理器,配置事务通知,切入点。
配置文件(db.properties)
db.username=root
db.password=root
db.url=jdbc:mysql://127.0.0.1:3306/springboot?serverTimezone=UTC&useSSL=false&autoReconnect=true&tinyInt1isBit=false&useUnicode=true&characterEncoding=utf8
db.driverClass=com.mysql.jdbc.Driver
业务类(UserDao,UserService)
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void insertUser() {String sql = "INSERT INTO t_user (username,`password`) VALUES(?,?);";
String username = UUID.randomUUID().toString().substring(0, 3);
jdbcTemplate.update(sql, username, 12);
System.out.println("插入成功");
int i=10/0;
}
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
public void insertUser() {userDao.insertUser();
}
}
Spring 配置文件(spring-aspect.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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 导入外部资源文件 -->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!-- 包扫描,把类上标注了 @Controller @Service @Repository @Component 都会自动加入到 Spring 容器中 -->
<context:component-scan base-package="zfcoding.tx.aspectaop"></context:component-scan>
<!-- 配置 C3P0 数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
<property name="jdbcUrl" value="${db.url}"></property>
<property name="driverClass" value="${db.driverClass}"></property>
</bean>
<!-- 配置 Spring 的 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 为连接点指定事务属性 -->
<tx:method name="insert*" isolation="DEFAULT" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="point" expression="execution (* zfcoding.tx.aspectaop..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="point"></aop:advisor>
</aop:config>
</beans>
测试方法
public class AspectTest {
@Test
public void aspectAop(){ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:spring-aspect.xml");
UserService userService = applicationContext.getBean(UserService.class);
userService.insertUser();}
}
基于 @Transactional 的声明式事务管理
基于注解实现
第一步是在需要事务的类或者方法上面添加@Transactional()
注解,里面可以通过 propagation 和 isolation 指定事务的隔离级别和传播行为。
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT)
public void insertUser() {userDao.insertUser();
}
}
@EnableTransactionManagement
来启用注解式事务管理,相当于之前在 xml 中配置的 <tx:annotation-driven />
注解驱动。
通过包扫描 @ComponentScan 相当于 <<context:component-scan>> 实现业务类注册到 Spring 容器当中。
通过 @PropertySource 和 @Value 实现读取配置文件,并且把之赋值到对应的属性当中。
@Configuration 说明该类是一个配置类相当于(<beans></beans>),@Bean 相当于(<bean></bean>), 注册数据源,JDBC 模板,事务管理器。
@EnableTransactionManagement
@ComponentScan(basePackages = "zfcoding.tx")
@PropertySource("classpath:db.properties")
@Configuration
public class MyDataSourceConfig {@Value("${db.username}")
private String username;
@Value("${db.password}")
private String password;
@Value("${db.url}")
private String url;
@Value("${db.driverClass}")
private String driveClass;
@Bean
public DataSource dataSource() throws PropertyVetoException {ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setUser(username);
dataSource.setPassword(password);
dataSource.setJdbcUrl(url);
dataSource.setDriverClass(driveClass);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate() throws PropertyVetoException {JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource());
return jdbcTemplate;
}
// 注册事务管理器在容器中
@Bean
public PlatformTransactionManager transactionManager() throws PropertyVetoException {return new DataSourceTransactionManager(dataSource());
}
}
测试类
public class TxTest {
@Test
public void test(){AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(MyDataSourceConfig.class);
UserService userService = applicationContext.getBean(UserService.class);
userService.insertUser();}
}
基于 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:context="http://www.springframework.org/schema/context"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 导入外部资源文件 -->
<context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
<!-- 包扫描,把类上标注了 @Controller @Service @Repository @Component 都会自动加入到 Spring 容器中 -->
<context:component-scan base-package="zfcoding.xmltx"></context:component-scan>
<!-- 配置 C3P0 数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
<property name="jdbcUrl" value="${db.url}"></property>
<property name="driverClass" value="${db.driverClass}"></property>
</bean>
<!-- 配置 Spring 的 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启自动事务扫描 -->
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
测试类
public class XmlTxTest {
@Test
public void test(){ClassPathXmlApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:bean.xml");
UserServiceXml userServiceXml = applicationContext.getBean(UserServiceXml.class);
userServiceXml.insertUser();}
}
以上就是 Spring 事务配置的全过程,完事。
往期内容
Spring IOC 知识点汇总
Spring Aop 的实现方式(XML,注解)
深入理解 Spring 中 Bean 的生命周期
我是阿福,公众号「阿福聊编程」作者,对后端技术保持学习爱好者,我会经常更新 JAVA 技术文章,在进阶的路上,共勉!
欢迎大家关注我的公众号,后台回复666,领取 Java 全套进阶实战课程。