简单记录spring的事务管理,及三种声明式事务的实现。
事务的特性
事务四个特性ACID:原子性、一致性、隔离性、持久性
原子性(atomicity): 一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
一致性(consistency): 事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation): 一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability): 持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
事务的隔离级别
事务的隔离级别是为了解决脏读、不可重复读、幻读
隔离级别有四个级别:
级别0:Read uncommitted (未提交读)
最低级别,效率最高,但不能避免并发引起的脏读、不可重复读、幻读问题
级别1:Read committed (提交读)
解决脏读问题,不能解决不可重复读、幻读
级别2:Repeatable read (可重复读)
解决了脏读、不可重复读问题,不能解决幻读
级别3:Serializable(可序列化)
最高隔离级别,保证事务串行执行,解决了脏读、不可重复读、幻读问题。但是效率最低,一般不建议用。
注意:mysql默认使用级别为二的Repeatable read (可重复读),Oracle默认是级别一Read committed (提交读)
spring特有的事务传播行为
在开发业务中可能会出现一个事务调用另一个事务的情况,spring定义了七种事务的传播行为。使用最多和默认使用的是 PROPAGATION_REQUIRED
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就不使用事务。 |
PROPAGATION_MANDATORY | 如果当前有事务,使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
spring在TransactionDefinition类中定义了四种隔离级别和七种传播行为
spring事务的实现
一.编程式事务管理
一般不用。略…
二.声明式事务管理
1.基于注解的事务管理方式
2.使用xml配置声明式事务,基于aop
3.基于aspectJ的XML方式的配置
一、使用spring声明式事务管理,基于注解的方式。(最简单,方便的方式)
使用mybatis + spring模拟转账环境
数据库
CREATE DATABASE transaction;
-- 创建表
CREATE TABLE person(
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户Id',
`name` varchar(120) NOT NULL COMMENT '用户名字',
`money` bigint NOT NULL COMMENT '账户余额',
PRIMARY KEY (`id`)
)ENGINE=InnoDB DEFAULT CHARSET = utf8 COMMENT '人员表';
-- 插入数据
insert into person(name,money)
values
("张三",12000),
("李四",1000),
("王五",32000),
("赵六",7000),
("田七",180000);
dao层接口personDao.java
package com.smallchili.Transaction.dao;
import org.apache.ibatis.annotations.Param;
public interface PersonDao {
/**
* @param outId 转账人Id
* @param money 金额
*/
public void outMoney(@Param("outId") int outId,@Param("money") int money);
/**
* @param inId 收款方Id
* @param money 金额
*/
public void inMoney(@Param("inId") int inId,@Param("money") int money);
}
resources下mapper文件夹对应的personDao.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.smallchili.Transaction.dao.PersonDao">
<update id="outMoney">
update
person
set
money = money - #{money}
where money >= 0
and money >= #{money}
and id = #{outId}
</update>
<update id="inMoney">
update
person
set
money = money + #{money}
where id = #{inId}
</update>
</mapper>
Service层
接口PersonService
package com.smallchili.Transaction.service;
public interface PersonService {
public void transfer(int outId, int inId, int money);
}
实现类PersonServiceImpl
package com.smallchili.Transaction.service.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.smallchili.Transaction.dao.PersonDao;
import com.smallchili.Transaction.service.PersonService;
@Service
public class PersonServiceImpl implements PersonService{
@Autowired
PersonDao personDao;
@Override
/**
* @Transactional的注解属性:
* propagation:事务的传播行为
* isolation :事务隔离级别
* readOnly :只读
* rollbackFor:发生哪些异常要回滚
* noRollbackFor:发生哪些异常不回滚
* */
@Transactional
public void transfer(int outId, int inId, int money) {
personDao.outMoney(outId, money);
int a = 1/0;//制造异常
personDao.inMoney(inId,money);
}
}
测试类
package com.smallchili.Transaction;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.smallchili.Transaction.service.PersonService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext1.xml")//配置1,注解
//@ContextConfiguration("classpath:applicationContext2.xml")//配置2,xml
//@ContextConfiguration("classpath:applicationContext3.xml")//配置3,aspectJ
public class PersonServiceTest {
//@Resource(name="personServiceProxy")//配置2,要代理类
@Resource
PersonService personService;
@Test
public void transferTest(){
int outId = 2;
int inId = 1;
int money = 10000;
personService.transfer(outId, inId, 10000);
}
}
项目依赖
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<!-- 日志依赖 使用slf4j+logback -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.1.1</version>
</dependency>
<!-- 实现slf4j接口并整合 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.1.1</version>
</dependency>
<!-- mysql数据库连接驱动和c3p0连接池 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- mybatis相关依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<!-- MyBatis自身实现spring整合的依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.2.3</version>
</dependency>
<!-- spring依赖 -->
<!-- 1.spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!-- 2.spring dao层依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency><!-- 使用声名式事务,事务相关依赖 -->
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!-- spring 测试相关依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
spring整合mybatis基本配置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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 配置mybatis过程 -->
<!-- 1.配置数据库相关参数 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 2.数据库连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 3.配置sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 扫描sql配置文件,mapper需要的xml文件 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!--4.配置扫描Dao接口包,动态实现Dao接口,注入到spring容器中 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 给出需要扫的接口包 -->
<property name="basePackage" value="com.smallchili.Transaction.dao"/>
</bean>
<!-- 扫描service包下注解 -->
<context:component-scan base-package="com.smallchili.Transaction.service"></context:component-scan>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置基于注解的声明式事务
默认使用注解来管理事务行为
默认是jdk代理 proxy-target-class="false" 如果设置true 则使用cglib,要引入cglib依赖
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
一、 方式一:基于注解的事务管理方式的主要依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<dependency><!-- 使用声明式事务,事务相关依赖 -->
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
主要配置
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置基于注解的声明式事务
默认使用注解来管理事务行为
默认是jdk代理 proxy-target-class="false" 如果设置true 则使用cglib,要引入cglib依赖
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
要加事务的方法上
@Transactional
public void transfer(int outId, int inId, int money) {
personDao.outMoney(outId, money);
int a = 1/0;//制造异常
personDao.inMoney(inId,money);
}
注解里有一些可选属性配置,可根据需求配置
/**
* @Transactional的注解属性:
* propagation:事务的传播行为
* isolation :事务隔离级别
* readOnly :只读
* rollbackFor:发生哪些异常要回滚
* noRollbackFor:发生哪些异常不回滚
* */
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)
二、方式2:使用xml配置声明式事务,基于代理AOP
主要依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!-- 使用aop -->
<!-- aop联盟的包 -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
主要配置文件
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置要使用事务的类,目标类的bean -->
<bean id="personService" class="com.smallchili.Transaction.service.impl.PersonServiceImpl"/>
<!-- 配置业务层的代理: -->
<bean id="personServiceProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 注入目标对象 -->
<property name="target" ref="personService"/>
<!-- 注入事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 注入事务属性 -->
<property name="transactionAttributes">
<props>
<!-- transfer是类里的方法
PROPAGATION 事务传播行为 -->
<prop key="transfer">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
另外,还可以这样配置抽取出来,使用用parent属性
<bean id="baseTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
* abstract="true">
* <property name="transactionManager" ref="transactionManager"/>
* <property name="transactionAttributes">
* <props>
* <prop key="insert*">PROPAGATION_REQUIRED</prop>
* <prop key="update*">PROPAGATION_REQUIRED</prop>
* <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
* </props>
* </property>
* </bean>
*
* <bean id="myProxy" parent="baseTransactionProxy">
* <property name="target" ref="myTarget"/>
* </bean>
*
* <bean id="yourProxy" parent="baseTransactionProxy">
* <property name="target" ref="yourTarget"/>
* </bean>
注意测试时要用注入的是代理类
package com.smallchili.Transaction;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.smallchili.Transaction.service.PersonService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class PersonServiceTest {
@Resource(name="personServiceProxy")//指定代理类
PersonService personService;
@Test
public void transferTest(){
int outId = 2;
int inId = 1;
int money = 10000;
personService.transfer(outId, inId, 10000);
}
}
三、基于aspectJ的XML方式的配置
主要依赖
<!-- 2.spring dao层依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
<!-- 使用 aspectj-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<!-- spring整合aspectj的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.1.7.RELEASE</version>
</dependency>
</dependencies>
主要配置
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务通知(事务的增强) -->
<tx:advice id="txAdivce" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution(* com.smallchili.Transaction.service.*.*(..))" id="pointcut"/>
<!-- 配置切面 -->
<aop:advisor advice-ref="txAdivce" pointcut-ref="pointcut"/>
</aop:config>