Spring(四):声明式事务
文章目录
1. 事务概述
事务是最小的执行单元,一个事务中的多个操作要么同时成功,要么同时失败。这体现了事务的原子性。
之前已经写了一篇关于事务和事务的隔离级别的文章。这里就不赘述了。
Mysql事务
2. Spring中的声明式事务
在JavaEE三层架构中,事务处理一般处于业务层。Spring框架提供了事务控制的api。事务控制基于AOP实现,可以用配置方式实现,配置方式实现的事务是方法级别的粗粒度事务控制。也可以使用编程方式实现,这是一种细粒度的行级事务控制
2.1 事务控制相关api
-
PlatformTransactionManager
- 继承结构
|--PlatformTransactionManager //事务管理器接口 //接口的实现类,真正管理事务的对象 |--DataSourceTransactionManager // 使用SpringJDBC和mybatis进行持久化数据时使用 |--HibernateTransactionManager // 使用Hibernate持久化数据时使用
- 相关方法
// 获取事务状态信息 TransactionStatus getTransaction(TranactionDefinition) // 提交事务 void commit(TransactionStatus status) // 回滚事务 void rollback(TransactionStatus status)
TransactionDefinition 是事务的定义信息对象,里面有如下方法:
// 获取事务名称
String getName()
// 获取事务隔离级别
int getIsolationLevel()
// 获取事务传播级别
int getPropagationBehavior()
// 获取事务超时时间
int getTimeOut()
// 获取事务是否只读
boolean isReadOnly()
- TransactionStatus
此接口提供事务具体的运行状态,拥有如下6个方法
// 刷新事务
void flush()
// 获取是否存在存储点
boolean hasSavepoint()
// 获取事务是否完成
boolean isCompleted()
// 获取事务是否为新的事务
Boolean isNewTransaction()
// 获取事务是否回滚
boolean isRollbackOnly()
// 设置事务回滚
void setRollbackOnly()
2.2 事务的传播级别
- REQUIRED:如果当前没有事务,就新建一个事务。如果已经存在一个事务中,加入这个事务中(默认值)
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)。
- MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
- REQUERS_NEW:新建事务,如果当前存在事务,就把当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- NEVER:以非事务方式运行,如果当前存在事务,抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务内运行。如果当前没有事务,则执行REQUIRED类似的操作。
###3. XML实现声明式事务
在service层插入两条账户信息, 插入一条账户信息之后故意产生一个除零异常,如果事务控制生效会回滚事务,这个时候查看数据库,会发现没有插入数据。
3.0 导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
3.1 数据表和实体类
package com.zmysna.entity;
public class Account {
private Integer accountId;
private Integer uid;
private Double money;
...set/get方法
}
3.2 dao层和service业务层
- dao层
public interface IAccountDao {
void save(Account account);
}
@Repository
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public void save(Account account) {
jdbcTemplate.update("insert into account values (null,?,?)", account.getUid(),account.getMoney());
}
}
- service层
public interface IAccountService {
void save(Account account);
}
/**
* 同时保存两个用户,进行事务控制
*/
@Service
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Override
public void save(Account account) {
// 插入一条账户信息
accountDao.save(account);
// 执行到这里会产生一个异常,如果事务控制生效会回滚事务,数据库不会插入数据
int i = 1/0;
accountDao.save(account);
return null;
}
}
3.3 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:jdbc.properties"></context:property-placeholder>
<!--开启注解扫描,可以配置多个包,以逗号分隔-->
<context:component-scan base-package="com.zmysna.service, com.zmysna.dao">
</context:component-scan>
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="driverClassName" value="${jdbc.driverName}"></property>
</bean>
<!--jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--事务通知
name属性表示声明事务的方法
propagation表示事务的传播行为
read-only 为true时表示只读,用于查询;read-only为false用于增删改方法
-->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!--查询-->
<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="query*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="search*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<!--增删改-->
<tx:method name="*" read-only="false" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!--配置aop-->
<aop:config>
<aop:pointcut id="pt" expression="execution(* com..*AccountServiceImpl.*(..))"/>
<aop:advisor pointcut-ref="pt" advice-ref="txAdvice"></aop:advisor>
</aop:config>
</beans>
外部配置文件jdbc.properties
jdbc.driverName=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=awsed2zx
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
3.4 测试
//spring整合Junit
@RunWith(SpringJUnit4ClassRunner.class)
//通过配置文件创建一个容器
@ContextConfiguration("classpath:bean.xml")
public class XmlTxTest {
@Autowired
private IAccountService accountService;
@Test
public void transactionTest() {
Account account = new Account();
account.setUid(51);
account.setMoney(999.0D);
//打印accountService的类型,查看是不是一个代理类
System.out.println(accountService.getClass());
accountService.save(account);
}
}
结果:
先观察accountService是不是一个代理类,在观察数据库是否插入数据,如果是代理类,并且成功产生除零异常。则证明事务控制生效,数据库不会插入数据。
// 产生JDK动态代理对象
class com.sun.proxy.$Proxy31
// 除零错误
java.lang.ArithmeticException: / by zero
......略
4. 注解实现事务控制
注解实现事务控制只需在要实现注解控制的类和方法上添加@Transaction注解,然后开启spring声明式事务的注解支持。
4.1 @Transactional事务注解
- 定义位置
- 接口、父类上——表示实现当前接口的类,继承父类的子类都应用spring声明式事务
- 接口方法上——表示接口的实现类重写的方法应用spring声明式事务
- 实现类——该类的所有方法的应用声明式事务
- 实现类的方法上——这个方法应用声明式事务
- 属性
- readOnly——为 true 时表示只读,用于查询;为 false 用于增删改方法。默认为false
- propagation——表示事务的传播行为,默认为Propagation.REQUIRED
4.2 改造
- 在service层添加声明式事务注解
@Service
@Transactional
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Override
public void save(Account account) {
accountDao.save(account);
int i = 1/0;
accountDao.save(account);
}
}
- 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: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:jdbc.properties"></context:property-placeholder>
<!--开启注解扫描,可以配置多个包,以逗号分隔-->
<context:component-scan base-package="com.zmysna.service, com.zmysna.dao">
</context:component-scan>
<!--配置数据源-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="password" value="${jdbc.password}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="driverClassName" value="${jdbc.driverName}"></property>
</bean>
<!--jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启spring声明式事务的注解支持-->
<tx:annotation-driven transaction-manager="txManager"></tx:annotation-driven>
</beans>
- 其他类、配置文件、测试和测试结果都相同
4.3 继续改造实现零配置
- 相关注解
@Configuration——指定当前类为配置类
@Import——导入其他配置类
@ComponentScan——注解扫描
@Bean——应用在方法上,将方法返回的对象加入Spring容器
@PropertySource——加载外部配置文件
@EnableTransactionManagement——开启声明式事务的注解支持
-
改造
主配置类
package com.zmysna.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScans({
@ComponentScan("com.zmysna.service"),
@ComponentScan("com.zmysna.dao"),
})
@Import(JdbcConfig.class)
@EnableTransactionManagement //开启声明式事务的注解支持
public class SpringConfiguration {
}
数据源配置类
package com.zmysna.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
import javax.xml.crypto.Data;
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.driverName}")
private String driverClassName;
@Bean("dataSource")
public DataSource createDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setDriverClassName(driverClassName);
dataSource.setPassword(password);
return dataSource;
}
@Bean("jdbcTemplate")
public JdbcTemplate createJDBCTemplate(@Qualifier("dataSource") DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean("txManager")
public DataSourceTransactionManager createTransactionManager(@Qualifier("dataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
- 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)//加载配置类
public class AnnoTxTest {
@Autowired
private IAccountService accountService;
@Test
public void transactionTest() {
Account account = new Account();
account.setUid(51);
account.setMoney(999.0D);
System.out.println(accountService.getClass());
accountService.save(account);
}
}
- 测试结果不变,xml配置文件可删去。
5. 编程实现事务管理
声明式事务是方法级别的事务,粒度比较粗。如果想实现行级事务,就要需要编程实现,实现方法也很简单。将想要实现事务管理的代码定义在TransactionCallback事务回调函数中,然后使用transactionTemplate调用回调函数。
步骤:
- 使用spring创建并管理对象
<?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:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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/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">
<!-- 加载 jdbc.properties 文件 -->
<context:property-placeholder
location="classpath:jdbc.properties"></context:property-placeholder>
<!--1. 创建 dataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName"
value="${jdbc.driverClassName}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--2. 创建 jdbcTemplate ,注入 dataSource-->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--3. 创建 dao ,注入 jdbcTemplate-->
<bean id="accountDao" class="com.zmysna.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--4. 创建 service ,注入 dao-->
<bean id="accountService"
class="com.zmysna.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="transactionTemplate"
ref="transactionTemplate"></property>
</bean>
<!--5.Spring 事务管理器 ( 切面类 )-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 创建 Spring 编程式事务控制的事务模板类 -->
<bean id="transactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager"
ref="transactionManager"></property>
</bean>
</beans>
- 在service层中编程实现事务
import com.zmysna.dao.IAccountDao;
import com.zmysna.domain.Account;
import com.zmysna.service.IAccountService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
public class AccountServiceImpl implements IAccountService {
// 注入 dao
private IAccountDao accountDao;
// 注入事务模板
private TransactionTemplate transactionTemplate;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
public void setTransactionTemplate(TransactionTemplate
transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void save(Account account) {
// 定义回调函数
TransactionCallback<Object> callback = new
TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
// 需要事务控制的代码,写到这里
accountDao.save(account);
int i = 1 / 0;
accountDao.save(account);
return null;
}
};
// 事务控制执行 , 传入回调函数
transactionTemplate.execute(callback);
}
}