Spring 中的事务控制
一、事务控制JavaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方案Spring 框架为我们提供了一组事务控制的接口。这组接口是在spring-tx-5.0.2.RELEASE.jar 中Spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现二、事务控制的 API1、PlatformTrans...
一、事务控制
- JavaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方案
- Spring 框架为我们提供了一组事务控制的接口。这组接口是在spring-tx-5.0.2.RELEASE.jar 中
- Spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现
二、事务控制的 API
1、PlatformTransactionManager 接口
此接口是 spring 的事务管理器(平台事务管理器),它里面提供了我们常用的操作事务的方法:
TransactionStatus getTransaction(TransactionDefinition definition)
获取事务状态信息void commit(TransactionStatus status)
提交事务void rollback(TransactionStatus status)
回滚事务
真正的事务管理对象:
org.springframework.jdbc.datasource.DataSourceTransactionManager
使用 Spring JDBC 或 MyBatis 进行持久化数据时使用org.springframework.orm.hibernate5.HibernateTransactionManager
使用Hibernate 版本进行持久化数据时使用
2、TransactionDefinition
它是事务的定义信息对象,里面有如下方法:
String getName()
获取事务对象名称int getIsolationLevel()
获取事务的隔离级别int PropagationBehavior()
获取事务的传播行为int getTimeout()
获取事务的超时时间boolean isReadOnly()
获取事务是否只读
3、事务的隔离级别
事务的隔离级别反映了事务提交并发访问时的处理态度
ISOLATION_DEFAULT
: 默认级别,归属下列某一种ISOLATION_READ_UNCOMMITTED
: 可以读取未提交的数据ISOLATION_READ_COMMITTED
: 只能读取已提交的数据ISOLATION_REPEATABLE_READ
: 是否读取替他事务修改后的数据(MySQL的默认级别)ISOLATION_SERIALAZABLE
: 是否读取替他事务提交添加后的数据
4、事务的传播行为
REQUIRED
:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)SUPPORTS
:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)MANDATORY
:使用当前的事务,如果当前没有事务,就抛出异常REQUERS_NEW
:新建事务,如果当前在事务中,把当前事务挂起。NOT_SUPPORTED
:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起NEVER
:以非事务方式运行,如果当前存在事务,抛出异常NESTED
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
5、超时时间
默认值是-1,没有超时限制。如果有,以秒为单位进行设置
6、TransactionStatus 接口
此接口提供的是事务具体的运行状态,描述了某个时间点上事务对象的状态信息方法有:
void flush()
:刷新事务boolean hasSavePoint()
: 获取事务是否存在存储点boolean isCompleted()
: 获取事务是否完成boolean isNewTransaction()
: 获取事务是否为新事务boolean isRollBackOnly()
: 获取事务是否回滚void setRollbackOnly()
: 设置事务回滚
三、示例:未开启事务的转账
第一步:封装 Account.java 实体类来映射数据库表
package cn.lemon.domain;
import java.io.Serializable;
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "账户信息{" +
"编号=" + id +
", 姓名='" + name + '\'' +
", 余额=" + money +
'}';
}
}
第二步:新建持久层(dao)接口以及实现类
package cn.lemon.dao;
import cn.lemon.domain.Account;
import java.util.List;
public interface IAccountDao {
void updateAccount(Account account);
Account findByIdAccount(Integer accountId);
}
package cn.lemon.dao.impl;
import cn.lemon.dao.IAccountDao;
import cn.lemon.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void updateAccount(Account account) {
try{
jdbcTemplate.update("update account set name = ?, money = ? where id = ?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e){
throw new RuntimeException(e);
}
}
@Override
public Account findByIdAccount(Integer accountId) {
try{
return jdbcTemplate.queryForObject("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
第三步:新建 applicationContext.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"
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">
<bean id="dateSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///db_account"></property>
<property name="username" value="root"></property>
<property name="password" value="lemon"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dateSource"></property>
</bean>
<context:component-scan base-package="cn.lemon"></context:component-scan>
</beans>
第四步:新建业务层(service)接口以及实现类
package cn.lemon.servcie;
public interface IAccountService {
/**
* 执行转账操作
* @param inId 转入的id
* @param outId 转出的id
* @param money 转入转出的金额
*/
void transfer(Integer inId, Integer outId, Double money);
}
package cn.lemon.servcie.impl;
import cn.lemon.dao.IAccountDao;
import cn.lemon.domain.Account;
import cn.lemon.servcie.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao iAccountDao;
@Override
public void transfer(Integer inId, Integer outId, Double money) {
Account in = iAccountDao.findByIdAccount(inId);
Account out = iAccountDao.findByIdAccount(outId);
in.setMoney(in.getMoney() + money);
iAccountDao.updateAccount(in);
//int d = 1/0; //模拟异常
out.setMoney(out.getMoney() - money);
iAccountDao.updateAccount(out);
}
}
第五步:测试类
package cn.lemon.servcie.impl;
import cn.lemon.servcie.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:/applicationContext.xml")
public class AccountServiceImplTest {
@Autowired
private IAccountService iAccountService;
@Test
public void testtrnsfer() {
iAccountService.transfer(1,2,100d);
}
}
分析结果:
当在第四步的实现类中,开启异常时,会发生转账失败,也就是id为1的账户增加了100(转入成功),而id为2的账户却没有减少(转出失败),这不是我们想要的结果
四、Spring 基于 XML 的事务控制
第一步: 配置事务管理器
<!--配置事务管理器-->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入DataSource-->
<property name="dataSource" ref="dateSource"></property>
</bean>
第二步:配置事务的通知引用事务管理器
<!--配置事务的通知,事务增强-->
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager"></tx:advice>
第三步:配置事务的属性
<!--在 tx:advice 标签内部 配置事务的属性 -->
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED" read-only="false"/><!--这是转账的方法 transfer-->
<tx:method name="*" propagation="REQUIRED" read-only="true"></tx:method><!--这是除了转账之外的其他方法-->
</tx:attributes>
read-only
:是否是只读事务。默认 false,不只读。isolation
:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。propagation
:指定事务的传播行为。timeout
:指定超时时间。默认值为:-1。永不超时。rollback-for
:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。no-rollback-for
:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。
第四步:配置 AOP 切入点表达式
<!--配置AOP-->
<aop:config proxy-target-class="true">
<!--配置切入点表达式-->
<aop:pointcut id="pointCut" expression="execution(* cn.lemon.service.impl.*.*(..))"></aop:pointcut>
</aop:config>
第五步:配置切入点表达式和事务通知的对应关系
<!-- 建立事务的通知和切入点表达式的关系 织入事务增强-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"/>
完整配置
五、Spring 基于 XML &注解的事务控制
第一步:删除配置文件 applicationContext.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: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">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///db_account?serverTimezone=GMT%2B8"></property>
<property name="username" value="root"></property>
<property name="password" value="lemon"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--注解搜索的包-->
<context:component-scan base-package="cn.lemon"></context:component-scan>
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--
<tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
<tx:attributes>
<tx:method name="transfer"/>
<tx:method name="*" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<aop:config proxy-target-class="true">
<aop:pointcut id="pointCut" expression="execution(* cn.lemon.service.impl.*.*(..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"></aop:advisor>
</aop:config>
-->
<!--开启事务的注解支持-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager" proxy-target-class="true"></tx:annotation-driven>
</beans>
第二步:在业务层(service)的实现类中使用@Transactional 注解
package cn.lemon.service.impl;
import cn.lemon.dao.IAccountDao;
import cn.lemon.domain.Account;
import cn.lemon.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(propagation = Propagation.REQUIRED, readOnly = true)
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao iAccountDao;
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void transfer(Integer inId, Integer outId, Double money) {
Account in = iAccountDao.findByIdAccount(inId);
Account out = iAccountDao.findByIdAccount(outId);
in.setMoney(in.getMoney() + money);
iAccountDao.updateAccount(in);
//int i = 1/0;//模拟异常
out.setMoney(out.getMoney() - money);
iAccountDao.updateAccount(out);
}
}
六、Spring 基于注解的事务控制
第一步:修改上面的代码,即:删除 applicationContext.xml 文件,添加连接数据库的属性文件 JdbcConfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///db_account?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=lemon
第二步:写配置类 SpringConfiguration.java 以及子配置类 JdbcConfig.java
package cn.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration//定义本类是一个配置类
@Import(JdbcConfig.class)//定义子配置类
@ComponentScan("cn.lemon")//定义注解要搜索的包
@PropertySource("classpath:/jdbc.properties")//读取属性文件
@EnableTransactionManagement(proxyTargetClass = true)//开启事务对注解的支持,,子类代理,不写的话为接口代理
public class SpringConfiguration {
}
package cn.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration//定义本类是一个配置类,是可以省略的
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
// * Bean
// * 作用:用于把当前方法的返回值作+为bean对象存入spring的ioc容器中
// * 属性:
// name:用于指定bean的id。当不写时,默认值是当前方法的名称
// * 细节:
// 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。
// 查找的方式和Autowired注解的作用是一样的
@Bean
public DataSource dataSource() {// 在spring 中产生对象
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {//配置事务管理器
return new DataSourceTransactionManager(dataSource);
}
}
第三步:修改测试类
package cn.lemon.service.impl;
import cn.config.SpringConfiguration;
import cn.lemon.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceImplTest {
@Autowired
private IAccountService iAccountService;
@Test
public void transfer() {
iAccountService.transfer(2,1,100d);
}
}
更多推荐
所有评论(0)