事务—【01】Spring事务管理介绍以及SpringBoot+Druid+MyBatis单数据源事务管理实现

前置知识

  • 简单介绍 详解自行google.

事务是什么?

  • 事务是一种可靠、一致的方式,访问和操作数据库中的程序单元

事务的特性

  • 原子性:要么做,要么不做
  • 一致性:一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态
  • 持久性:持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的。
  • 隔离性:不同的事务操作的互相不干扰

并发事务的问题

  1. 脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据,针对对某一项数据
  2. 不可重复读:一个事务范围内多次查询却返回了不同的数据值,是由于在查询间隔,被另一个事务修改并提交了。
  3. 幻读:是指在事务执行过程中多次查询出来的数据数,不一致,比如通过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;

    // 不管调用方是否有事务执行,自己都要起一个新事务。把原先的事务挂起,这个只在jta的事务管理器中起作用(事务是不支持嵌套的)
	int PROPAGATION_REQUIRES_NEW = 3;

    // 即使调用方有事务,我也要在非事务中执行,把原先的事务挂起
	int PROPAGATION_NOT_SUPPORTED = 4; 

    // 绝不在事务里面执行
	int PROPAGATION_NEVER = 5;

    // 嵌套事务(实际是不支持的,是利用存盘点来实现,把调用方之前执行的存盘点,然后类似于再开启一个事务执行,执行完毕后恢复存盘点,继续执行。JDBC3.0以上支持)
	int PROPAGATION_NESTED = 6;

    // 以下定义的是事务的隔离机制
    // 根据数据库的默认的隔离机制而定
	int ISOLATION_DEFAULT = -1;

    // 读未提交
	int ISOLATION_READ_UNCOMMITTED = 1;  // same as java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;

    // 读已提交
	int ISOLATION_READ_COMMITTED = 2;  // same as java.sql.Connection.TRANSACTION_READ_COMMITTED;

    // 可重复读
	int ISOLATION_REPEATABLE_READ = 4;  // same as java.sql.Connection.TRANSACTION_REPEATABLE_READ;

    // 串行化
	int ISOLATION_SERIALIZABLE = 8;  // same as java.sql.Connection.TRANSACTION_SERIALIZABLE;


    // 默认超时时间,以数据库设定为准
	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

  • 事务状态:事务的一些状态信息,如是否是一个新的事务、是否已被标记为回滚
// Savepoiont就是在Nested这种传播机制中提供保存点机制来实现嵌套事务,出错的时候可以选择恢复到保存点
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: https://github.com/imyiren/transaction-example/tree/master/one-data-source

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);
        // error("annotation error!");
        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

  • 成功执行过程
  1. GET 请求到 /api/account/transfer/annotation接口 然后调用service方法
  2. 根据写的Transactional注解的设置 创建一个新事务
  3. 选用JDBC连接 然后创建 SqlSession
  4. 为SqlSession注册事务同步
  5. SQL操作
  6. 然后把事务提交
  7. 最后释放资源。完成方法调用
  8. 接口返回200
  • 通过日志可看出我们使用MyBatis+Druid 配置数据源 事务管理实际是使用的DataSourceTransactionManager.

  • 虽然代码中指定了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

  • 失败执行过程
  1. GET 请求到 /api/account/transfer/annotation接口 然后调用service方法
  2. 根据写的Transactional注解的设置 创建一个新事务
  3. 选用JDBC连接 然后创建 SqlSession
  4. 为SqlSession注册事务同步
  5. SQL操作:可以从日志看到,执行到第一个跟新操作发生错误
  6. 直接回滚Rolling back JDBC transaction on Connection后打印错误日志。后面的第二个更新操作也就没了
  7. 方法异常退出,接口500

3.4 编程实现事务管理

  • 编程式事务主要步骤
  1. 创建事务定义TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
  2. 设置事务属性:如传播属性、隔离属性等
  3. 通过TransactionManager开启事务
  4. 执行业务代码
  5. 提交事务 / 处理回滚
  • 此实例的编程式事务的执行基本和声明式事务相同,有兴趣可以自行下载代码自己实现。

// todo: 下一篇多数据源事务管理

  • https://blog.csdn.net/ilo114/article/details/103937126

关于我

  • 坐标杭州,普通本科在读,计算机科学与技术专业,20年6月毕业,疯狂找工作中。。。。
  • 目前处于菜鸟阶段,各位大佬轻喷,小弟正在疯狂学习。
  • 欢迎大家和我交流鸭!!!
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 首先,为了使用多数据源和分布事务,我们需要添以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.6</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency> ``` 接下来,我们需要在application.properties文件中配置数据源事务管理器: ```properties # 配置主数据源 spring.datasource.url=jdbc:mysql://localhost:3306/main_db?characterEncoding=utf8&amp;useSSL=false spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver # 配置从数据源 spring.datasource.slave.url=jdbc:mysql://localhost:3306/slave_db?characterEncoding=utf8&amp;useSSL=false spring.datasource.slave.username=root spring.datasource.slave.password=root spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver # 配置Mybatis mybatis.mapper-locations=classpath:mapper/*.xml mybatis.type-aliases-package=com.example.entity # 配置Druid数据源 spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.druid.initial-size=1 spring.datasource.druid.max-active=10 spring.datasource.druid.min-idle=1 spring.datasource.druid.max-wait=60000 spring.datasource.druid.time-between-eviction-runs-millis=60000 spring.datasource.druid.min-evictable-idle-time-millis=300000 spring.datasource.druid.test-while-idle=true spring.datasource.druid.test-on-borrow=false spring.datasource.druid.test-on-return=false spring.datasource.druid.filters=stat,wall,log4j spring.datasource.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 # 配置事务管理spring.transaction.default-timeout=600 spring.transaction.rollback-on-commit-failure=true spring.transaction.allow-bean-definition-overriding=true spring.transaction.jta.registry-name=atomikos spring.jta.enabled=true spring.jta.atomikos.connectionfactory.min-pool-size=5 spring.jta.atomikos.connectionfactory.max-pool-size=10 spring.jta.atomikos.connectionfactory.borrow-connection-timeout=30 spring.jta.atomikos.connectionfactory.max-idle-time=60 spring.jta.atomikos.connectionfactory.concurrency-level=100 ``` 然后,我们需要创建两个数据源的配置类,分别为主数据源和从数据源: ```java @Configuration @MapperScan(basePackages = "com.example.mapper.main", sqlSessionTemplateRef = "mainSqlSessionTemplate") public class MainDataSourceConfig { @Bean(name = "mainDataSource") @ConfigurationProperties(prefix = "spring.datasource") public DataSource mainDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "mainSqlSessionFactory") public SqlSessionFactory mainSqlSessionFactory(@Qualifier("mainDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/main/*.xml")); return bean.getObject(); } @Bean(name = "mainTransactionManager") public DataSourceTransactionManager mainTransactionManager(@Qualifier("mainDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "mainSqlSessionTemplate") public SqlSessionTemplate mainSqlSessionTemplate(@Qualifier("mainSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } ``` ```java @Configuration @MapperScan(basePackages = "com.example.mapper.slave", sqlSessionTemplateRef = "slaveSqlSessionTemplate") public class SlaveDataSourceConfig { @Bean(name = "slaveDataSource") @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name = "slaveSqlSessionFactory") public SqlSessionFactory slaveSqlSessionFactory(@Qualifier("slaveDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/slave/*.xml")); return bean.getObject(); } @Bean(name = "slaveTransactionManager") public DataSourceTransactionManager slaveTransactionManager(@Qualifier("slaveDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "slaveSqlSessionTemplate") public SqlSessionTemplate slaveSqlSessionTemplate(@Qualifier("slaveSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } } ``` 最后,我们需要在事务管理器上添注解@EnableTransactionManagement,并在需要使用事务的方法上添注解@Transactional: ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Transactional(rollbackFor = Exception.class, transactionManager = "mainTransactionManager") @Override public void save(User user) { userMapper.insert(user); } @Transactional(rollbackFor = Exception.class, transactionManager = "slaveTransactionManager") @Override public User findById(int id) { return userMapper.selectByPrimaryKey(id); } } ``` 以上就是使用SpringBoot+Mybatis+druid数据源和分布事务的基本步骤。 ### 回答2: Spring Boot是一个用于构建独立的、生产级的应用程序的框架。它简化了应用程序的开发过程,并通过自动配置来减少了繁琐的配置。MyBatis是一个ORM(对象关系映射)框架,它提供了将数据库操作映射到Java对象的功能。Druid是一种高性能的数据库连接池。 要在Spring Boot中使用MyBatisDruid进行多数据源配置和分布事务管理,可以按照以下步骤进行操作: 1. 添依赖:在项目的pom.xml文件中,添Spring Boot、MyBatisDruid的依赖。 2. 配置数据源:在application.properties文件中,配置并命名多个数据源,设置数据库连接等信息。 3. 创建数据源配置类:创建一个配置类,使用@Configuration注解将其标记为配置类,并使用@ConfigurationProperties注解将数据源属性注入。 4. 创建数据源:根据配置类中的属性,创建多个数据源,并将其入到数据源路由器中。 5. 配置MyBatis:创建一个配置类,使用@MapperScan注解设置MyBatis的mapper接口路径,并将数据源注入到SqlSessionFactory中。 6. 配置分布事务:使用@EnableTransactionManagement注解启用事务管理,并配置事务管理器。 7. 编写数据库操作代码:在mapper接口中定义数据库操作方法,并在Service层中调用这些方法进行数据库操作。 通过以上步骤,你就可以在Spring Boot项目中完成MyBatisDruid的多数据源配置和分布事务管理。不过需要注意的是,使用多数据源和分布事务会增项目的复杂性和性能开销,所以在使用之前需要仔细考虑是否真正需要这些功能。 ### 回答3: Spring Boot是一种快速构建Java应用程序的框架,MyBatis是一种流行的Java持久化框架,Druid是一种高性能的数据库连接池。本文将介绍如何在Spring Boot中使用MyBatisDruid实现数据源和分布事务。 要使用多个数据源,我们首先需要配置多个数据源。在Spring Boot中,我们可以通过在application.properties或者application.yml文件中配置多个数据源的连接信息。我们需要为每个数据源指定不同的URL、用户名和密码。然后,我们可以使用@Primary和@Qualifier来指定主数据源和其他数据源。 在配置数据源后,我们需要配置MyBatis来使用这些数据源。我们可以通过创建多个SqlSessionFactory来实现数据源,然后在每个SqlSessionFactory中设置相应的数据源。我们还可以使用@MapperScan注解来自动扫描和注册Mapper接口。 在使用MyBatis和多个数据源时,我们可能会遇到事务管理的问题。为了解决这个问题,我们可以使用Spring Boot提供的@Transactional注解来标记需要进行事务管理的方法,然后Spring Boot会自动为我们处理事务。对于需要跨多个数据源进行事务管理的情况,我们可以使用JTA(Java Transaction API)实现分布事务。在Spring Boot中,我们可以使用Atomikos或Bitronix等JTA提供商来实现分布事务。 总结起来,使用Spring Boot、MyBatisDruid,我们可以很容易地实现数据源和分布事务。通过正确配置数据源和使用相关注解,我们可以在几分钟内完成这些任务。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值