spring中的事务控制
环境:
Idea:2019.3.1
系统:windows10 家庭版
Jdk: 8
spring:5.0.3 release
spring文档
项目代码
一.概述
1.JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案
2.spring框架为我们提供了一组事务控制的接口,这组接口是在spring-tx-5.0.2.RELEASE.jar中
3.spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现
二.spring中事务控制的API介绍
PlatformTransactionManager
此接口是spring的事务管理器,它里面提供了我们常用的操作事务的方法,包含3个具体的操作
1.获取事务状态信息
TransactionStatus getTransaction(TransactionDefinition definition)
2.提交事务
void commit(TransactionStatus status)
3.回滚事务
void rollback(TransactionStatus status)
我们在开发中都是使用它的实现类
1.org.springframework.jdbc.datasource.DataSourceTransactionManager
使用Spring JDBC或iBatis进行持久化数据时使用
2.org.springframework.orm.hibernate5.HibernateTransactionManager
使用Hibernate版本进行持久化数据时使用
TransactionDefinition
它是事务的定义信息对象,包含的方法如下
String getName()
获取事务对象名称
int getIsolationLevel()
获取事务隔离级别
事务的隔离级别反映事务提交并发访问时的处理态度
-ISOLATION_DEFAULT
默认级别,归属于下列某一种
-ISOLATION_READ_UNCOMMITTED
可以读取未提交数据
-ISOLATION_READ_COMMITEED
只能读取已提交数据,解决脏读问题(Oracle默认级别)
-ISOLATION_REPEATABLE_READ
是否读取其他事务提交修改后的数据,解决不可重复读问题(MySQL默认级别)
-ISOLATION_SERIALIZABLE
是否读取其他事务提交添加后的数据,解决幻影读问题
int getPropagationBehavior()
获取事务传播行为
事务的传播行为
REQUIRED:
如果当前没有事务,就新建一个事务,如果已经存在于一个事务中,则加入到这个事务中。一般的选择(默认值)
SUPPORTS:
支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
MANDATORY:
使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW:
新建事务,如果当前在事务中,把当前事务挂起
NOT_SUPPORTED:
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER:
以非事务方式运行,如果当前存在事务,抛出异常
NESTED:
如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行REQUIRED类似的操作
int getTimeout()
获取事务超时时间
默认值是-1,就是没有超时限制,如果有,以秒为单位进行设置
boolean isReadOnly()
获取事务是否只读
读写型事务:增加、删除、修改开启事务
只读型事务:查询时开启事务,建议查询时设置为只读
TransactionStatus
此接口提供的是事务具体的运行状态,包含了6个具体的操作
void flush()
刷新事务
boolean hasSavepoint()
获取是否存在存储点
boolean isCompleted()
获取事务是否完成
boolean isNewTransaction()
获取事务是否为新的事务
boolean isRollbackOnly()
获取事务是否回滚
void setRollbackOnly()
设置事务回滚
三.spring中基于xml的声明式事务控制
沿用上篇文章中的使用spring的JdbcDaoSupport的工程文件,然后做以下修改
导入依赖
别忘了导入spring-jdbc和spring-tx的坐标,其他所需要的按照之前项目导
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
</beans>
配置事务管理器
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
配置事务的通知
配置事务的通知<tx:advice></tx:advice>
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
配置事务的属性<tx:attributes></tx:attributes>
在这个标签里面用<tx:method/>
标签指定切入点和属性
属性:
name="":用于指定切入点名称
isolation="":用于指定事务的隔离级别,默认值是DEFAULT,表示使用数据库的隔离级别
propagation=""用于指定事务的传播行为,默认值是REQUIRED,表示一定会有事务,增删改的选择,查询方法可以选择SUPPORTS
read-only=""用于指定事务是否只读,只有查询方法才能设置为true,默认值是false,表示读写
timeout=""用于指定事务的超时时间,默认值是-1,表示永不超时,如果指定了数值,以秒为单位
no-rollback-for=""用于指定一个异常,表示当该异常发生时事务不回滚,产生其他异常时都回滚,没有默认值,表示任何异常都回滚
rollback-for=""用于指定一个异常,表示当该异常发生时事务回滚,产生其他异常时事务不回滚,没有默认值,表示任何异常都回滚
name属性可以使用**通配符**来表示多个连接点,所以我们一般地将查询方法都用find开头,这样在设置`<tx:method/>`属性时,可以直接指定name = "find*",因为事务的原因,我们一般都按照下面这种写法,为什么这样设置,可以查看最上面对这些属性的值的说明
<!-- 配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性-->
<tx:attributes>
//增删改方法,命名按自己的来,propagation="REQUIRED",read-only设置为读写
<tx:method name="transfer" propagation="REQUIRED" read-only="false"/>
//所有查询方法都用find打头,这里用find*指定所有查询方法,然后设置propagation="SUPPORTS",read-only设置为只读
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
配置aop中的通用切入点表达式并和事务通知建立关系
<!-- 配置aop中的通用切入点表达式-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.cbw.service.impl.*.*(..))"/>
<!-- 建立事务通知和切入点表达式的关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
完整的配置文件如下
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.cbw"></context:component-scan>
<!-- 配置账户的持久层-->
<bean id="accountDao" class="com.cbw.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/myspringspace"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!-- 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">
<!--5.配置事务的属性
-->
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 3.配置aop中的通用切入点表达式-->
<aop:config>
<aop:pointcut id="pt1" expression="execution(* com.cbw.service.impl.*.*(..))"/>
<!-- 4.建立事务通知和切入点表达式的关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
</beans>
测试
我们运行带int i=1/0错误的转账方法
可以看到转账钱这么多钱,然后报错了,我们再去表中看下,是否转账人转了帐,收账人没收到
可以看到,既没有转账也没有收账,说明事务配置成功了
四.spring中基于注解的事务控制
使用纯注解的方式实现
创建配置类
1.springConfiguration
主配置文件
import org.springframework.context.annotation.*;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* spring的配置类,相当于applicationContext.xml
*/
//表示这是一个配置类
@Configuration
//设置要扫描的包
@ComponentScan("com.cbw")
//导入另外两个配置类
@Import({JdbcConfig.class,TransactionConfig.class})
//导入外部的properties文件,里面写了数据库连接所需参数
@PropertySource("jdbcConfig.properties")
//开启事务支持
@EnableTransactionManagement
public class SpringConfiguration {
}
2.JdbcConfig
和数据库相关的配置类
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
/**
* 和连接数据库相关的类
*/
public class JdbcConfig {
@Value("${jdbc.driver}")
private String classDriver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
*
* 创建JdbcTemplate对象
* @param dataSource
* @return
*/
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDataSource(){
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName(classDriver);
driverManagerDataSource.setUrl(url);
driverManagerDataSource.setUsername(username);
driverManagerDataSource.setPassword(password);
return driverManagerDataSource;
}
}
3.TransactionConfig
和事务相关的配置类
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* 和事务相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器对象的
* @param dataSource
* @return
*/
@Bean("TransactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
jdbcConfig.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/myspringspace
jdbc.username=root
jdbc.password=123456
业务层开启事务
用@Transactional
注解在service层中的service实现类,开启事务
import com.cbw.dao.IAccountDao;
import com.cbw.domain.Account;
import com.cbw.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("accountService")
@Transactional
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public Account findAccountByID(Integer accountId) {
return accountDao.findAccountById(accountId);
}
public void transfer(String sourceName, String targetName, Float money) {
//1.查询出转账人
Account source = accountDao.findAccountByName(sourceName);
//2.查询出收账人
Account target = accountDao.findAccountByName(targetName);
//3.转账人减钱
source.setMoney(source.getMoney() - money);
//4.收账人加钱
target.setMoney(target.getMoney() + money);
//5.更新两个账户
accountDao.updateAccount(source);
accountDao.updateAccount(target);
}
}
当然这里也可以给方法设置事务属性,还是@Transactional
标签
@Transactional(propagation = Propagation.REQUIRED,readOnly = true)
public Account findAccountByID(Integer accountId) {
return accountDao.findAccountById(accountId);
}
测试
之前的@ContextConfiguration
标签里面的location参数改成classes,指明配置类
import com.cbw.config.SpringConfiguration;
import com.cbw.domain.Account;
import com.cbw.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 TestTx {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
//转账前
System.out.println("转账前");
Account source = as.findAccountByID(1);
Account target = as.findAccountByID(2);
System.out.println("转账人:" + source);
System.out.println("收账人:" + target);
//转账
as.transfer(source.getName(),target.getName(),200f);
//转账后
System.out.println("转账后");
source = as.findAccountByID(1);
target = as.findAccountByID(2);
System.out.println("转账人:" + source);
System.out.println("收账人:" + target);
}
}
老样子,在转账的方法中,加入int i=1/0;
查看表
一样没变