一致性:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态
持久性:持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的。
隔离性:不同的事务操作的互相不干扰
并发事务的问题
脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据,针对对某一项数据
不可重复读:一个事务范围内多次查询却返回了不同的数据值,是由于在查询间隔,被另一个事务修改并提交了。
幻读:是指在事务执行过程中多次查询出来的数据数,不一致,比如通过where筛选第二次比第一次多几条别的事务新插入的数据,幻读针对的是一批数据整体
事务的隔离级别
READ_UNCOMMITTED 读未提交:可以读到没有没有提交事务做出的数据修改内容(隔离级别最低,并发性能高)
READ_COMMITTED 读已提交:只能读到事务以及提交的数据(锁定正在读取的行)
REPEATABLE_READ 可重复读:保证在一个事务中 多次读同样的数据是一致的。不会被其他事务所影响(锁定所读取的所有行)
SERIALIZABLE 可串行化:事务串行化顺序执行,
Spring事务
Spring提供了统一的事务API来支持不同的资源类型
Spring也提供了声明式事务管理的方式,与业务代码解耦
很容易和Spring生态框架整合
支持多资源的事务管理与同步
1. 事务抽象
1.1 PlatformTransactionManager
事务管理器:主要提供提交操作、回滚操作以及根据事务定义获取事务状态的统一接口
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
1.2 TransactionDefinition
事务定义:事务的一些基础信息,如超时时间、隔离级别、传播属性等
事务传播机制,也就是在事务在多个方法的调用中是如何传递的;如:两个service的方法都是事务操作,一个事务方法调用另一个事务方法如何进行处理?
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
default int getPropagationBehavior() {
return PROPAGATION_REQUIRED;
default int getIsolationLevel() {
return ISOLATION_DEFAULT;
default int getTimeout() {
return TIMEOUT_DEFAULT;
default boolean isReadOnly() {
return false;
@Nullable
default String getName() {
return null;
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
1.3 TransactionStatus
事务状态:事务的一些状态信息,如是否是一个新的事务、是否已被标记为回滚
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
boolean hasSavepoint();
@Override
void flush();
public interface TransactionExecution {
boolean isNewTransaction();
void setRollbackOnly();
boolean isRollbackOnly();
boolean isCompleted();
2. PlatformTransactionManager常见的实现
DataSourceTransactionManager (用于JDBC Template、MyBatis等)
JpaTransactionManager (用于data-jpa、hibernate等)
JmsTransactionManager (用于消息中间件等)
JtaTransactionManager (主要用于分布式事务)
3. Spring单数据源事务实际案例
3.1 环境说明:
DataSource: Alibaba Druid
Database: MySQL 5.7
SpringBoot: 2.2.2.RELEASE
ORM: MyBatis
GitHub: github.com/imyiren/tra…
3.2 实例代码
此案例额基于转账业务实现 PlatformTransactionManager则是DataSourceTransactionManager
@Slf4j
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDAO accountDAO;
@Autowired
PlatformTransactionManager transactionManager;
* 声明式事务
* propagation = Propagation.REQUIRED (默认值就是REQUIRED) 如果调用方有事务就直接使用调用方的事务,如果没有就新建一个事务
* transactionManager = "transactionManager" 也是默认值
* isolation= Isolation.DEFAULT 隔离级别
* 还有timeout等参数 可自行查看Transactional的源码 里面都有说明
* @param sourceAccountId 源账户
* @param targetAccountId 目标账户
* @param amount 金额
* @return 操作结果信息
@Override
@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED, rollbackFor = Exception.class, isolation= Isolation.DEFAULT)
public String transferAnnotation(Long sourceAccountId, Long targetAccountId, BigDecimal amount) {
AccountDO sourceAccountDO = accountDAO.selectByPrimaryKey(sourceAccountId);
AccountDO targetAccountDO = accountDAO.selectByPrimaryKey(targetAccountId);
if (null == sourceAccountDO || null == targetAccountDO) {
return "转入或者转出账户不存在";
if (sourceAccountDO.getBalance().compareTo(amount) < 0) {
return "转出账户余额不足";
sourceAccountDO.setBalance(sourceAccountDO.getBalance().subtract(amount));
accountDAO.updateByPrimaryKeySelective(sourceAccountDO);
targetAccountDO.setBalance(targetAccountDO.getBalance().add(amount));
accountDAO.updateByPrimaryKeySelective(targetAccountDO);
return "转账成功!";
@Override
public String transferCode(Long sourceAccountId, Long targetAccountId, BigDecimal amount) {
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
try {
AccountDO targetAccountDO = accountDAO.selectByPrimaryKey(targetAccountId);
AccountDO sourceAccountDO = accountDAO.selectByPrimaryKey(sourceAccountId);
if (null == sourceAccountDO || null == targetAccountDO) {
return "转入或者转出账户不存在";
error("code error");
if (sourceAccountDO.getBalance().compareTo(amount) < 0) {
return "转出账户余额不足";
sourceAccountDO.setBalance(sourceAccountDO.getBalance().subtract(amount));
targetAccountDO.setBalance(targetAccountDO.getBalance().add(amount));
accountDAO.updateByPrimaryKeySelective(sourceAccountDO);
accountDAO.updateByPrimaryKeySelective(sourceAccountDO);
transactionManager.commit(transaction);
return "转账成功!";
} catch (Exception e) {
log.error("转账发生错误,开始回滚,source: {}, target: {}, amount: {}, errMsg: {}",
sourceAccountId, targetAccountId, amount, e.getMessage());
transactionManager.rollback(transaction);
return "转账失败";
@Override
public List<AccountDO> listAll() {
return accountDAO.selectAll();
private static void error(String msg) {
throw new RuntimeException(msg);
3.3 Transactional注解实现事务
使用注解式事务,Spring会利用代理实现一个代理类,
然后从上下文中得到事务管理器,开启一个事务后执行业务代码,
如果满足你设置的回滚异常条件,就执行rollback
我们调用的时候是直接调用的Service的方法
但是Spring在实现的时候,实际是通过AOP Proxy(AOP 代理服务)来调用Transaction Advisor(做事务管理的)然后来处理调用注解式事务的service方法
我们通过接口/api/account/transfer/annotation?source=1&target=2&amount=123 触发账户1转给账户2 123块钱,转账操作
日志信息(开启DEBUG日志):
o.s.web.servlet.DispatcherServlet : GET "/api/account/transfer/annotation?source=1&target=2&amount=123", parameters={masked}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to io.ilss.transaction.onedatasource.web.AccountController#transferAnnotation(Long, Long, String)
o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [io.ilss.transaction.onedatasource.service.impl.AccountServiceImpl.transferAnnotation]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; 'transactionManager',-java.lang.Exception
o.s.j.d.DataSourceTransactionManager : Acquired Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] for JDBC transaction
o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] to manual commit
org.mybatis.spring.SqlSessionUtils : Creating a new SqlSession
org.mybatis.spring.SqlSessionUtils : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
o.m.s.t.SpringManagedTransaction : JDBC Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] will be managed by Spring
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Parameters: 1(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey : <== Total: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42] from current transaction
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Parameters: 2(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey : <== Total: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42] from current transaction
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Preparing: update account SET nickname = ?, username = ?, `password` = ?, balance = ?, create_time = ?, update_time = ? where id = ?
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Parameters: 小一(String), xiaoyi(String), 123456(String), 877.00(BigDecimal), 2020-01-09T17:04:28(LocalDateTime), 2020-01-09T17:44:33(LocalDateTime), 1(Long)
i.i.t.o.d.A.updateByPrimaryKeySelective : <== Updates: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42] from current transaction
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Preparing: update account SET nickname = ?, username = ?, `password` = ?, balance = ?, create_time = ?, update_time = ? where id = ?
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Parameters: 小二(String), xiaoer(String), 123456(String), 223.00(BigDecimal), 2020-01-09T17:04:40(LocalDateTime), 2020-01-09T17:04:40(LocalDateTime), 2(Long)
i.i.t.o.d.A.updateByPrimaryKeySelective : <== Updates: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@39615e42]
o.s.j.d.DataSourceTransactionManager : Initiating transaction commit
o.s.j.d.DataSourceTransactionManager : Committing JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@185f0a96]
o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@185f0a96] after transaction
m.m.a.RequestResponseBodyMethodProcessor : Using 'text/html', given [text/html, application/xhtml+xml, image/webp, image/apng, application/xml;q=0.9, application/signed-exchange;v=b3;q=0.9, */*;q=0.8] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
m.m.a.RequestResponseBodyMethodProcessor : Writing ["转账成功!"]
o.s.web.servlet.DispatcherServlet : Completed 200 OK
成功执行过程
GET 请求到 /api/account/transfer/annotation接口 然后调用service方法
根据写的Transactional注解的设置 创建一个新事务
选用JDBC连接 然后创建 SqlSession
为SqlSession注册事务同步
SQL操作
然后把事务提交
最后释放资源。完成方法调用
接口返回200
虽然代码中指定了transactionManager,但实际却没有增加任何Bean注册或者其他有关DataSource的事务管理器代码。这个地方是因为Spring帮你做了,我设置transactionManager是因为自动装配的transactionManager名字就是这个。写出来提醒一下有这个配置,这个配置顾名思义就是配置对这个事务的管理器实现。简而言之就是PlatformTransactionManager的指定。后面多数据源的时候我们会通过指定不同的transactionManager来实现事务管理。
如果报错会是怎么样呢?
先看日志(开启DEBUG日志):
o.s.web.servlet.DispatcherServlet : GET "/api/account/transfer/annotation?source=1&target=2&amount=123", parameters={masked}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to io.ilss.transaction.onedatasource.web.AccountController#transferAnnotation(Long, Long, String)
o.s.j.d.DataSourceTransactionManager : Creating new transaction with name [io.ilss.transaction.onedatasource.service.impl.AccountServiceImpl.transferAnnotation]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; 'transactionManager',-java.lang.Exception
o.s.j.d.DataSourceTransactionManager : Acquired Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] for JDBC transaction
o.s.j.d.DataSourceTransactionManager : Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] to manual commit
org.mybatis.spring.SqlSessionUtils : Creating a new SqlSession
org.mybatis.spring.SqlSessionUtils : Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
o.m.s.t.SpringManagedTransaction : JDBC Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] will be managed by Spring
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Parameters: 1(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey : <== Total: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58] from current transaction
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Preparing: select id, nickname, username, `password`, balance, create_time, update_time from account where id = ?
i.i.t.o.d.AccountDAO.selectByPrimaryKey : ==> Parameters: 2(Long)
i.i.t.o.d.AccountDAO.selectByPrimaryKey : <== Total: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils : Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58] from current transaction
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Preparing: update account SET nickname = ?, username = ?, `password` = ?, balance = ?, create_time = ?, update_time = ? where id = ?
i.i.t.o.d.A.updateByPrimaryKeySelective : ==> Parameters: 小一(String), xiaoyi(String), 123456(String), 754.00(BigDecimal), 2020-01-09T17:04:28(LocalDateTime), 2020-01-09T17:44:33(LocalDateTime), 1(Long)
i.i.t.o.d.A.updateByPrimaryKeySelective : <== Updates: 1
org.mybatis.spring.SqlSessionUtils : Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
org.mybatis.spring.SqlSessionUtils : Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@66240e58]
o.s.j.d.DataSourceTransactionManager : Initiating transaction rollback
o.s.j.d.DataSourceTransactionManager : Rolling back JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec]
o.s.j.d.DataSourceTransactionManager : Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1b6963ec] after transaction
o.s.web.servlet.DispatcherServlet : Failed to complete request: java.lang.RuntimeException: annotation error!
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is
java.lang.RuntimeException: annotation error!
.......
o.a.c.c.C.[Tomcat].[localhost] : Processing ErrorPage[errorCode=0, location=/error]
o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error?source=1&target=2&amount=123", parameters={masked}
s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#errorHtml(HttpServletRequest, HttpServletResponse)
o.s.w.s.v.ContentNegotiatingViewResolver : Selected 'text/html' given [text/html, text/html;q=0.8]
o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 500
失败执行过程
GET 请求到 /api/account/transfer/annotation接口 然后调用service方法
根据写的Transactional注解的设置 创建一个新事务
选用JDBC连接 然后创建 SqlSession
为SqlSession注册事务同步
SQL操作:可以从日志看到,执行到第一个跟新操作发生错误
直接回滚Rolling back JDBC transaction on Connection后打印错误日志。后面的第二个更新操作也就没了
方法异常退出,接口500
3.4 编程实现事务管理
编程式事务主要步骤
创建事务定义TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
设置事务属性:如传播属性、隔离属性等
通过TransactionManager开启事务
执行业务代码
提交事务 / 处理回滚
此实例的编程式事务的执行基本和声明式事务相同,有兴趣可以自行下载代码自己实现。
// todo: 下一篇多数据源事务管理