Spring-事务管理
目录
事务回顾
事务的作用
事务特征(ACID)
事务隔离级别
核心事务对象
好接口
PlatformTransactionManager
案例环境(XML)
AOP改造编程式事务(XML)
声明式事务(XML)(TX命名空间管理事务)
tx配置
aop:advice与aop:advisor区别
实例演示
tx:method属性
事务传播行为
声明式事务(注解)
名称:@Transactional
名称:tx:annotation-driven
实例测试
声明式事务(纯注解)
实例演示
-
事务回顾
-
事务的作用
- 1.当数据库操作序列中个别操作失败时,提供一种方式使数据库状态恢复到正常状态(A),保障数据库即使在异常状态下仍能保持数据一致性(C) (要么操作前状态,要么操作后状态)
- 2.当出现并发访问数据库时,在多个访问间进行相互隔离,防止并发访问操作结果互相干扰(I)
-
事务特征(ACID)
- 原子性(Atomicity)
- 指事务是一个不可分割的整体,其中的操作要么全执行或全不执行
- 一致性(Consistency)
- 事务前后数据的完整性必须保持一致
- 隔离性(Isolation)
- 事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离
- 持久性(Durability)
- 持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响
-
事务隔离级别
- 脏读:允许读取未提交的信息
- 原因:Read uncommitted
- 解决方案:Read committed(表级读锁)
- 不可重复读:读取过程中单个数据发生了变化
- 解决方案:Repeatable read(行级写锁)
- 幻读:读取过程中数据条目发生了变化
- 解决方案:Serializable(表级写锁)
-
核心事务对象
-
好接口
- J2EE开发使用分层设计的思想进行,对于简单的业务层转调数据层的单一操作,事务开启在业务层或者数据层并无太大差别
- 当业务中包含多个数据层的调用时,需要在业务层开启事务,对数据层中多个操作进行组合并归属于同一个事务进行处理
- Spring为业务层提供了整套的事务解决方案
- PlatformTransactionManager
- TransactionDefinition
- TransactionStatus
-
PlatformTransactionManager
- 平台事务管理器实现类
- DataSourceTransactionManager
- 适用于Spring JDBC或MyBatis
- HibernateTransactionManager
- 适用于Hibernate3.0及以上版本
- JpaTransactionManager
- 适用于JPA
- JdoTransactionManager
- 适用于JDO
- JtaTransactionManager
- 适用于JTA
- JPA(Java Persistence API)
- Java EE标准之一,为POJO提供持久化标准规范,并规范了持久化开发的统一API,符合JPA规范的开发可以在不同的JPA框架下运行
- JDO(Java Data Object)
- 是Java对象持久化规范,用于存取某种数据库中的对象,并提供标准化API
- 与JDBC相比,JDBC仅针对关系数据库进行操作
- JDO可以扩展到关系数据库、文件、XML、对象数据库(ODBMS)等,可移植性更强
- JTA(Java Transaction API)
- Java EE标准之一,允许应用程序执行分布式事务处理
- 与JDBC相比,JDBC事务则被限定在一个单一的数据库连接
- 而一个JTA事务可以有多个参与者,比如JDBC连接、JDO都可以参与到一个JTA事务中
- 此接口定义了事务的基本操作
- 获取事务:
- TransactionStatus getTransaction(TransactionDefinition definition)
- 提交事务:
- void commit(TransactionStatus status)
- 回滚事务:
- void rollback(TransactionStatus status)
- 此接口定义了事务的基本信息
- 获取事务定义名称
- String getName()
- 获取事务的读写属性
- boolean isReadOnly()
- 获取事务隔离级别
- int getIsolationLevel()
- 获取事务超时时间
- int getTimeout()
- 获取事务传播行为特征
- int getPropagationBehavior()
- 此接口定义了事务在执行过程中某个时间点上的状态信息及对应的状态操作
- 获取事务是否处于新开启事务状态
- boolean isNewTransaction()
- 获取事务是否处于已完成状态
- boolean isCompleted()
- 获取事务是否处于回滚状态
- boolean isRollbackOnly()
- 刷新事务状态
- void flush()
- 获取事务是否具有回滚存储点
- boolean hasSavepoint()
- 设置事务处于回滚状态
- void setRollbackOnly()
-
案例环境(XML)
- 银行转账业务说明
- 银行转账操作中,涉及从A账户到B账户的资金转移操作
- 数据层仅提供单条数据的基础操作,未设计多账户间的业务操作
- src下domain
-
public class Account { 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 "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
- src下service
- src下dao
- resources下jdbc
- resources下context
- 测试成功
-
public class App { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = (AccountService) ctx.getBean("accountService"); accountService.transfer("Jock1","Jock2",100D); } }
-
AOP改造编程式事务(XML)
- 将业务层的事务处理功能抽取出来制作成AOP通知,利用环绕通知运行期动态织入
- src下aop
- context配置aop
- 抽取后service测试成功
-
声明式事务(XML)(TX命名空间管理事务)
-
tx配置
- 名称:tx:advice
- 类型:标签
- 归属:beans标签
- 作用:专用于声明事务通知
- 格式:
- <beans>
- <tx:advice id="txAdvice" transaction-manager="txManager">
- </tx:advice>
- </beans>
- 基本属性:
- id:用于配置aop时指定通知器的id
- transaction-manager:指定事务管理器bean
- 名称:tx:attributes
- 类型:标签
- 归属:tx:advice标签
- 作用:定义通知属性
- 格式: <tx:advice id="txAdvice" transaction-manager="txManager">
- <tx:attributes>
- </tx:attributes> </tx:advice>
- 基本属性:
- 无
- 名称:tx:method
- 类型:标签
- 归属:tx:attribute标签
- 作用:设置具体的事务属性
- 格式: <tx:attributes> <tx:method name="*" read-only="false"/> <tx:method name="get*" read-only="true"/>
- </tx:attributes>
- 说明:
- 通常事务属性会配置多个,包含1个读写的全事务属性,1个只读的查询类事务属性
-
aop:advice与aop:advisor区别
- aop:advice配置的通知类可以是普通java对象,不实现接口,也不使用继承关系
- aop:advisor配置的通知类必须实现通知接口
-
实例演示
- 开启tx命名空间
- 更新context配置(使用声明式事务)
- 此时原有aop文件可以删除了(不需要了)
-
//该文件已经可以删除了 public class TxAdvice { private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public Object transactionManager(ProceedingJoinPoint pjp) throws Throwable { //开启事务 PlatformTransactionManager ptm = new DataSourceTransactionManager(dataSource); //事务定义 TransactionDefinition td = new DefaultTransactionDefinition(); //事务状态 TransactionStatus ts = ptm.getTransaction(td); Object ret = pjp.proceed(pjp.getArgs()); //提交事务 ptm.commit(ts); return ret; } }
- 测试成功
-
tx:method属性
- <tx:method
- name="*"
- 待添加事务的方法名表达式(支持*号通配符),例如get*、*、·····
- read-only="false"
- 设置事务的读写属性, true为只读, false为读写
- timeout="-1"
- 设置事务超时时长,单位秒
- isolation="DEFAULT"
- 设置事务隔离级别,该隔离级设定是基于Spring的设定,非数据库端
- no-rollback-for=""
- 设置事务中不回滚的异常,多个异常间使用,分割
- rollback-for=""
- 设置事务中必回滚的异常,多个异常间使用,分割
- propagation="REQUIRED"
- 设置事务的传播行为
- />
-
事务传播行为
- 事务传播行为描述的是事务协调员对事务管理员所携带事务的处理态度
- 传播属性 事务管理员 事务协调员
- REQUIRED 开启T1 加入T1
- 无 新建T2
- REQUIRES_NEW 开启T1 新建T2
- 无 新建T2
- SUPPORTS 开启T1 加入T1
- 无 无
- NOT_SUPPORTED 开启T1 无
- 无 无
- MANDATORY 开启T1 加入T1
- 无 ERROR
- NEVER 开启T1 ERROR
- 无 无
- NESTED
- 设置savePoint,一旦事务回滚,事务将回滚到savePoint处,交由客户响应提交/回滚
- 企业开发过程中,发现同属于同一个事务控制的各个业务中,如果某个业务与其他业务隔离度较高,拥有差异化的数据业务控制情况,通常使用事务传播行为对其进行控制
-
声明式事务(注解)
-
名称:@Transactional
- 类型:方法注解,类注解,接口注解
- 位置:方法定义上方,类定义上方,接口定义上方
- 作用:设置当前类/接口中所有方法或具体方法开启事务,并指定相关事务属性
- 范例:
- @Transactional(
- readonly=false,
- timeout=-1,
- isolation=Isolation.DEFAULT,
- rollbackFor = {ArithmeticException.class,IOException.class},
- noRollbackFor = {},
- propagation= Propagation.REQUIRES_NEW
- )
-
名称:tx:annotation-driven
- 类型:标签
- 归属:beans标签
- 作用:开启事务注解驱动,并指定对应的事务管理器
- 范例:
- <tx:annotation-driven transaction-manager="txManager"/>
-
实例测试
- 更新context配置为注解驱动形式
-
<?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:util="http://www.springframework.org/schema/util" xmlns:p="http://www.springframework.org/schema/p" 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/util https://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd"> <!--加载properties配置文件的信息--> <context:property-placeholder location="classpath:*.properties"/> <!--加载druid资源--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!--配置service作为spring的bean--> <bean id="accountService" class="com.superdemo.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> </bean> <!--spring整合mybatis后控制的创建连接用的对象--> <bean class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="typeAliasesPackage" value="com.superdemo.domain"/> </bean> <!--加载mybatis映射配置的扫描,将其作为spring的bean进行管理--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.superdemo.dao"/> </bean> <!--<bean id="txAdvice" class="com.superdemo.aop.TxAdvice"> <property name="dataSource" ref="dataSource"/> </bean> <aop:config> <aop:pointcut id="pt" expression="execution(* *..transfer(..))"/> <aop:aspect ref="txAdvice"> <aop:around method="transactionManager" pointcut-ref="pt"/> </aop:aspect> </aop:config>--> <!-- TX格式 --> <!--创建事务管理器--> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="txManager"/> <!--定义事务管理的通知类--> <!--<tx:advice id="txAdvice" transaction-manager="txManager"> 定义控制的事务 <tx:attributes> <tx:method name="*" read-only="false"/> <tx:method name="get*" read-only="true"/> <tx:method name="find*" read-only="true"/> <tx:method name="transfer" read-only="false"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="pt" expression="execution(* *..transfer(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/> </aop:config>--> </beans>
- 使用注解
-
public interface AccountService { /* * outName 出账用户名 * inName 入账用户名 * money 转账金额 */ @Transactional( readOnly = false, timeout = -1, isolation = Isolation.DEFAULT, rollbackFor = {}, noRollbackFor = {}, propagation = Propagation.REQUIRED ) public void transfer(String outName, String inName, Double money); }
- 测试成功
-
声明式事务(纯注解)
- 名称:@EnableTransactionManagement
- 类型:类注解
- 位置:Spring注解配置类上方
- 作用:开启注解驱动,等同XML格式中的注解驱动
- 范例:
- @Configuration
- @ComponentScan("com.superdemo")
- @PropertySource("classpath:jdbc.properties")
- @Import({JDBCConfig.class,MyBatisConfig.class,TransactionManagerConfig.class})
- @EnableTransactionManagement
- public class SpringConfig{}
- public class TransactionManagerConfig{
- @Bean
- public PlatformTransactionManager getTransactionManager(@Autowired DataSource dataSource){
- return new DataSourceTransactionManager(dataSource);
- }
- }
-
实例演示
- src下domain
-
public class Account { 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 "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
- resources
-
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/dp1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false jdbc.username=root jdbc.password=109923
- src下dao
-
public interface AccountDao { /* * 入账操作 * name 入账用户名 * money 入账金额 */ @Update("update account set money = money + #{money} where name = #{name}") void inMoney(@Param("name") String name, @Param("money") Double money); /* * 出账操作 * name 出账用户名 * money 出账金额 */ @Update("update account set money = money - #{money} where name = #{name}") void outMoney(@Param("name") String name, @Param("money") Double money); }
- src下service
-
public interface AccountService { /* * outName 出账用户名 * inName 入账用户名 * money 转账金额 */ @Transactional public void transfer(String outName, String inName, Double money); }
-
@Service("accountService") public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; public void transfer(String outName, String inName, Double money) { accountDao.inMoney(outName,money); //int i = 1/0; accountDao.outMoney(inName,money); } }
- src下config
-
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("dataSource") public DataSource getDataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(userName); ds.setPassword(password); return ds; } @Bean public PlatformTransactionManager getTransactionManager(DataSource dataSource){ return new DataSourceTransactionManager(dataSource); } }
-
public class MybatisConfig { /* 对应XML格式下: spring整合mybatis后控制的创建连接用的对象 <bean class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="typeAliasesPackage" value="com.superdemo.domain"/> </bean> 加载mybatis映射配置的扫描,将其作为spring的bean进行管理 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.superdemo.dao"/> </bean>*/ @Bean public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){ SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean(); ssfb.setTypeAliasesPackage("com.superdemo.domain"); ssfb.setDataSource(dataSource); return ssfb; } @Bean public MapperScannerConfigurer getMapperScannerConfigurer(){ MapperScannerConfigurer msc = new MapperScannerConfigurer(); msc.setBasePackage("com.superdemo.dao"); return msc; } }
-
@Configuration @ComponentScan("com.superdemo") @PropertySource("classpath:jdbc.properties") @Import({JDBCConfig.class, MybatisConfig.class}) @EnableTransactionManagement public class SpringConfig { }
- test类
-
//设定spring专用的类加载器 @RunWith(SpringJUnit4ClassRunner.class) //设定加载的spring上下文对应的配置 @ContextConfiguration(classes = SpringConfig.class) public class UserServiceTest { @Autowired private AccountService accountService; @Test public void testTransfer(){ accountService.transfer("Jock1","Jock2",100D); } }
- 测试成功