Spring 事务控制我们要明确的
- JavaEE 体系进行分层开发,事务处理位于业务层(Service层)
- spring 框架为我们提供了一组事务控制的接口。这组接口是在spring-tx-5.2.8.RELEASE.jar 中
- spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。实际开发中很少使用编程方式。
Spring 中事务控制的 API 介绍
PlatformTransactionManager
此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法
我们在开发中都是使用上述接口的实现类:真正管理事务的对象
org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 SpringJDBC 或 iBatis 进行持久化数据时使用
org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate 版本进行持久化数据时使用
TransactionDefinition
它是事务的定义信息对象,里面有如下方法:
事务的隔离级别
并发安全:ISOLATION_READ_COMMITTED < ISOLATION_REPEATABLE_READ < ISOLATION_SERIALIZABLE
并发效率:ISOLATION_READ_COMMITTED > ISOLATION_REPEATABLE_READ > ISOLATION_SERIALIZABLE
事务的传播行为
- REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选 择(默认值)。增删改一般选择这个
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)。查询一般选择这个
- MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
- REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- NEVER:以非事务方式运行,如果当前存在事务,抛出异常
- NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
实际开发中一般只用1、2
超时时间
默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
是否是只读事务
建议查询时设置为只读。 默认为false
TransactionStatus
此接口提供的是事务具体的运行状态, 方法介绍如下图
基于 XML 的声明式事务控制(配置方式)
以银行账户之间转账为例
引入依赖
spring-context,spring-jdbc,spring-tx,mysql,aspectjweaver
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
数据库表和实体类
sql语句:
create table account (
id int primary key auto_increment,
name varchar (40),
money float
)character set utf8 collate utf8_general_ci;
insert into account(name, money) values('aaa', 1000);
insert into account (name,money) values ('bbb',1000);
insert into account (name, money) values('ccc', 1000) ;
实体类:
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
//生成set/get
}
业务层接口和实现类
//接口
public interface IAccountService {
void transfer(String sourceName,String targeName,Float money);
}
//实现类
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;//生成set/get
//转账方法
public void transfer(String sourceName, String targeName, Float money) {
//1.根据名称查询两个账户
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targeName);
//2.修改两个账户的金额
source.setMoney(source.getMoney()-money);//转出账户减钱
target.setMoney(target.getMoney()+money);//转入账户加钱
//3.更新两个账户
accountDao.updateAccount(source);
int i=1/0; //抛出异常测试事务是否起作用
accountDao.updateAccount(target);
}
}
Dao 接口和实现类
//接口
public interface IAccountDao {
Account findAccountByName(String name);
void updateAccount(Account account);
}
//实现类 继承JdbcDaoSupport的方式
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
//根据账户名称查询
public Account findAccountByName(String name) {
List<Account> list = getJdbcTemplate().query("select * from account where name = ? ",
new AccountRowMapper(),name);
if(list.isEmpty()){
return null;
}
if(list.size()>1){
throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
}
return list.get(0);
}
//更新账户
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set money = ? where id = ? ",
account.getMoney(),account.getId());
}
}
//账户的封装类 RowMapper 的实现类
public class AccountRowMapper implements RowMapper {
public Object mapRow(ResultSet resultSet, int i) throws SQLException {
Account account = new Account();
account.setId(resultSet.getInt("id"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getFloat("money"));
return account;
}
}
Spring配置文件
#数据库配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=root
<?xml version="1.0" encoding="UTF-8"?>
<!--导入相关名称空间和约束 tx的,aop的-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
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/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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--引入配置文件-->
<context:property-placeholder location="classpath:jdbcConfig.properties"/>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--配置service-->
<bean id="accountService" class="com.mg.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置dao-->
<bean id="accountDao" class="com.mg.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--Spring 基于xml的声明式事务控制配置步骤-->
<!--1.配置事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--2.配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--3.配置事务属性-->
<tx:attributes>
<!--name属性为方法名字,可以使用通配符。find*定义后所有的查询方法都要以find开头,方便管理-->
<!--以下配置意思为:查询方法执行一种事务策略,其他方法执行另一种事务策略-->
<tx:method name="*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!--4.配置AOP切入点表达式-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.mg.service.impl.*.*(..))"/>
<!--5.建立事务的通知和切入点表达式的关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
细节:
1. 配置事务通知
<tx:advice id="txAdvice" transaction-manager="transactionManager">
属性:
id:给事务通知一个唯一标识
transaction-manager:给事务通知一个事务管理器的引用
2.配置事务属性
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
属性:
name:方法名字,可以使用通配符
read-only:是否是只读事务。默认 false,不只读。
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为: -1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚
基于注解的声明式事务
以银行账户之间转账为例
引入依赖(同XML方式)
数据库表和实体类(同XML方式)
业务层接口(同XML方式)和实现类
//实现类 加入相应注解
@Service("accountService")
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
@Transactional(readOnly=false,propagation=Propagation.REQUIRED)
public void transfer(String sourceName, String targeName, Float money) {
//1.根据名称查询两个账户
Account source = accountDao.findAccountByName(sourceName);
Account target = accountDao.findAccountByName(targeName);
//2.修改两个账户的金额
source.setMoney(source.getMoney()-money);//转出账户减钱
target.setMoney(target.getMoney()+money);//转入账户加钱
//3.更新两个账户
accountDao.updateAccount(source);
int i=1/0; //抛出异常测试事务是否起作用
accountDao.updateAccount(target);
}
}
细节:
该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口
Dao 接口(同XML)和实现类
//实现类,加入相应注解。不能使用继承JdbcDaoSupport的方式了
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public Account findAccountByName(String name) {
List<Account> list = jdbcTemplate.query("select * from account where name = ? ",
new AccountRowMapper(),name
);
if(list.isEmpty()){
return null;
}
if(list.size()>1){
throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
}
return list.get(0);
}
public void updateAccount(Account account) {
jdbcTemplate.update("update account set money = ? where id = ? ",
account.getMoney(),account.getId()
);
}
}
//账户的封装类 RowMapper 的实现类 AccountRowMapper也和XML方式相同
public class AccountRowMapper implements RowMapper {
public Object mapRow(ResultSet resultSet, int i) throws SQLException {
Account account = new Account();
account.setId(resultSet.getInt("id"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getFloat("money"));
return account;
}
}
Spring配置文件
#数据库配置文件 同xml方式
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=root
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置 spring 创建容器时要扫描的包 -->
<context:component-scan base-package="com.mg"></context:component-scan>
<!--引入配置文件-->
<context:property-placeholder location="classpath:jdbcConfig.properties"/>
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!--Spring 基于xml的声明式事务控制配置步骤-->
<!--1.配置事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.开启 spring 对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 3.在业务层使用@Transactional 注解,见service层代码 -->
</beans>
纯注解方式
基于上述注解方式,利用之前所学的知识,继续改造代码,去除Spring配置文件
引入依赖(同注解方式)
数据库表和实体类(同注解方式)
业务层接口和实现类(同注解方式)
Dao 接口和实现类 (同注解方式)
创建配置类,替换Spring的配置文件
#数据库配置文件 同注解方式
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=root
JDBC相关配置类
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.DriverManagerDataSource;
import javax.sql.DataSource;
@PropertySource("classpath:jdbcConfig.properties")
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean(name = "dataSource")
public DriverManagerDataSource createDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
}
事务相关配置类
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
public class TransactionConfig {
@Bean(name = "transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
Spring 主配置类
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScan(basePackages = {"com.mg"})
@Import({JdbcConfig.class,TransactionConfig.class})
@EnableTransactionManagement //开启Spring注解事务的支持
public class SpringConfig {
}