SpringBoot整合Mybatis实现多数据源配置与跨数据源事务实例

作者:喜羊羊sk 时间:2023-06-29 23:47:34 

Pom依赖

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.2.2.RELEASE</version>
   <relativePath/>
</parent>
<dependencies>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-jdbc</artifactId>
   </dependency>
   <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
   </dependency>
   <dependency>
       <groupId>com.alibaba</groupId>
       <artifactId>druid-spring-boot-starter</artifactId>
       <version>1.2.14</version>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
   </dependency>
   <dependency>
       <groupId>org.mybatis.spring.boot</groupId>
       <artifactId>mybatis-spring-boot-starter</artifactId>
       <version>2.2.2</version>
   </dependency>
   <!--   分页插件     -->
   <dependency>
       <groupId>com.github.pagehelper</groupId>
       <artifactId>pagehelper-spring-boot-starter</artifactId>
       <version>1.3.0</version>
   </dependency>
   <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-aop</artifactId>
   </dependency>
</dependencies>

application.yml

# 数据源
spring:
  datasource:
    # master主数据源配置
    master:
      driverClassName: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/ds1?characterEncoding=UTF-8&serverTimezone=GMT%2B8
      username: root
      password: root
    # cluster从数据源配置
    cluster:
      driverClassName: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://localhost:3306/ds2?characterEncoding=UTF-8&serverTimezone=GMT%2B8
      username: root
      password: root
#pagehelper分页插件配置
pagehelper:
  master:
    helper-dialect: mysql  #设置数据库类型
    reasonable: true  #开启合理化:页码<=0 查询第一页,页码>=总页数查询最后一页
    support-methods-arguments: true  #支持通过 Mapper 接口参数来传递分页参数
    params: count=countsql

多数据源配置

Java代码方式创建主从数据源配置。 使用@Primary: 用于指定bean的注入优先级。被@Primary修饰的bean对象优先注入

MasterDataSourceConfig

/**
* @ClassName: MasterDataSourceConfig
* @Description: mysql主库配置类
*/
@Configuration
@MapperScan(basePackages = MasterDataSourceConfig.PACKAGE,sqlSessionTemplateRef = "masterSqlSessionTemplate")
public class MasterDataSourceConfig {
   /**
    * 设置扫描包的路径
    */
   public static final String PACKAGE = "cn.zysheep.dao.master";
   /**
    * Mapper配置文件路径
    */
   public static final String MAPPER_LOCATION = "classpath*:mapper/master/*.xml";
   /**
    * 实体别名
    */
   public static final String ENTITY_ALIASES = "cn.zysheep.entity";
   @Value("${spring.datasource.master.driverClassName}")
   private String driverClassName;
   @Value("${spring.datasource.master.url}")
   private String url;
   @Value("${spring.datasource.master.username}")
   private String user;
   @Value("${spring.datasource.master.password}")
   private String password;
   @Value("${pagehelper.master.helper-dialect}")
   private String helperDialect;
   @Value("${pagehelper.master.reasonable}")
   private String reasonable;
   /**
    * 创建数据源
    * @return DataSource
    * @Primary: 用于指定bean的注入优先级。被@Primary修饰的bean对象优先注入
    */
   @Bean(name = "masterDataSource")
   @Primary
   public DataSource masterDataSource() {
       DruidDataSource dataSource = new DruidDataSource();
       dataSource.setDriverClassName(driverClassName);
       dataSource.setUrl(url);
       dataSource.setUsername(user);
       dataSource.setPassword(password);
       return dataSource;
   }
   /**
    * 创建工厂
    *@param dataSource
    *@throws Exception
    *@return SqlSessionFactory
    */
   @Bean(name = "masterSqlSessionFactory")
   @Primary
   public SqlSessionFactory masterSqlSessionFactory(@Qualifier("masterDataSource") DataSource dataSource) throws Exception {
       SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
       sessionFactory.setDataSource(dataSource);
       sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
       sessionFactory.setTypeAliasesPackage(ENTITY_ALIASES);
       org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
       // 字段值为空也可以插入
       configuration.setJdbcTypeForNull(JdbcType.NULL);
       configuration.setCallSettersOnNulls(true);
       // mybatis log
       configuration.setLogImpl(StdOutImpl.class);
       sessionFactory.setConfiguration(configuration);
       PageInterceptor pageInterceptor = new PageInterceptor();
       // 分页配置
       Properties p = new Properties();
       p.setProperty("helperDialect", helperDialect);
       p.setProperty("reasonable", reasonable);
       pageInterceptor.setProperties(p);
       sessionFactory.setPlugins(pageInterceptor);
       return sessionFactory.getObject();
   }
   /**
    * 创建事务
    *@param dataSource
    *@return DataSourceTransactionManager
    */
   @Bean(name = "masterTransactionManager")
   @Primary
   public DataSourceTransactionManager masterDataSourceTransactionManager(@Qualifier("masterDataSource") DataSource dataSource) {
       return new DataSourceTransactionManager(dataSource);
   }
   /**
    * 创建模板
    *@param sqlSessionFactory
    *@return SqlSessionTemplate
    */
   @Bean(name = "masterSqlSessionTemplate")
   @Primary
   public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("masterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
       return new SqlSessionTemplate(sqlSessionFactory);
   }
}

ClusterDataSourceConfig

/**
* @ClassName: DataSourceConfig
* @Description: mysql从库配置类
*/
@Configuration
@MapperScan(basePackages = ClusterDataSourceConfig.PACKAGE,sqlSessionTemplateRef = "clusterSqlSessionTemplate")
public class ClusterDataSourceConfig {
   /**
    * 设置扫描包的路径
    */
   public static final String PACKAGE = "cn.zysheep.dao.cluster";
   /**
    * Mapper配置文件路径
    */
   public static final String MAPPER_LOCATION = "classpath*:mapper/cluster/*.xml";
   /**
    * 实体别名
    */
   public static final String ENTITY_ALIASES = "cn.zysheep.entity";
   @Value("${spring.datasource.cluster.driverClassName}")
   private String driverClassName;
   @Value("${spring.datasource.cluster.url}")
   private String url;
   @Value("${spring.datasource.cluster.username}")
   private String user;
   @Value("${spring.datasource.cluster.password}")
   private String password;
   @Value("${pagehelper.master.helper-dialect}")
   private String helperDialect;
   @Value("${pagehelper.master.reasonable}")
   private String reasonable;
   /**
    * 创建数据源
    *@return DataSource
    */
   @Bean(name = "clusterDataSource")
   public DataSource masterDataSource() {
       DruidDataSource dataSource = new DruidDataSource();
       dataSource.setDriverClassName(driverClassName);
       dataSource.setUrl(url);
       dataSource.setUsername(user);
       dataSource.setPassword(password);
       return dataSource;
   }
   /**
    * 创建工厂
    *@param dataSource
    *@throws Exception
    *@return SqlSessionFactory
    */
   @Bean(name = "clusterSqlSessionFactory")
   public SqlSessionFactory masterSqlSessionFactory(@Qualifier("clusterDataSource") DataSource dataSource) throws Exception {
       SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
       sessionFactory.setDataSource(dataSource);
       sessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
       sessionFactory.setTypeAliasesPackage(ENTITY_ALIASES);
       org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
       // 字段值为空也可以插入
       configuration.setJdbcTypeForNull(JdbcType.NULL);
       configuration.setCallSettersOnNulls(true);
       // mybatis log
       configuration.setLogImpl(StdOutImpl.class);
       sessionFactory.setConfiguration(configuration);
       PageInterceptor pageInterceptor = new PageInterceptor();
       // 分页配置
       Properties p = new Properties();
       p.setProperty("helperDialect", helperDialect);
       p.setProperty("reasonable", reasonable);
       pageInterceptor.setProperties(p);
       sessionFactory.setPlugins(pageInterceptor);
       return sessionFactory.getObject();
   }
   /**
    * 创建事务管理器
    *@param dataSource
    *@return DataSourceTransactionManager
    */
   @Bean(name = "clusterTransactionManager")
   public DataSourceTransactionManager masterDataSourceTransactionManager(@Qualifier("clusterDataSource") DataSource dataSource) {
       return new DataSourceTransactionManager(dataSource);
   }
   /**
    * 创建模板
    *@param sqlSessionFactory
    *@return SqlSessionTemplate
    */
   @Bean(name = "clusterSqlSessionTemplate")
   public SqlSessionTemplate masterSqlSessionTemplate(@Qualifier("clusterSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
       return new SqlSessionTemplate(sqlSessionFactory);
   }
}

启动类

启动类添加@EnableTransactionManagement注解,开启注解事务

@EnableTransactionManagement
@SpringBootApplication(exclude = PageHelperAutoConfiguration.class)
public class MultiDataSourceApplication {
   public static void main(String[] args) {
       SpringApplication.run(MultiDataSourceApplication.class, args);
   }
}

使用

在service层类或者方法上添加@Transactional注解。并指定属性 事务管理器transactionManager的值,根据使用不同的数据源,选择不同的数据事务管理器

@Transactional(transactionManager = "masterTransactionManager")
public void insertUser() {
   // TODO 添加用户业务操作
}
@Transactional(transactionManager = "clusterTransactionManager")
public void listUser() {
   // TODO 查询用户业务操作
}

思考: @Transactional只能指定一个事务管理器,并且注解不允许重复,所以就只能使用一个数据源的事务管理器了。那么对于一个方法涉及到多个数据源操作需要保证事务一致性的怎么办呢?

实现跨数据源事务

基于注解+AOP使用编程式事务的方式实现跨数据源事务

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiDataSourceTransactional {
   /**
    * 事务管理器数组
    */
   String[] transactionManagers();
}

首先多个数据源的事务分别都开起来,然后各事务分别去执行对应的sql(此所谓第一阶段提交),最后如果都成功就把事务全部提交,只要有一个失败就把事务都回滚&mdash;&mdash;此所谓第二阶段提交。

/**
* 需要注意的点:
* 1)声明事务和提交事务或者回滚事务的顺序应该相反的,就是先进后出,所以采用了栈来存储。
* 2)线程执行结束后记得清空本地变量。
* 3)可以参照@Transactional的那些属性升级功能,比如隔离级别回滚异常等。
*/
@Component
@Aspect
public class MultiTransactionAop {
   /**
    * 线程本地变量:为什么使用栈?※为了达到后进先出的效果※
    */
   private static final ThreadLocal<Stack<Map<DataSourceTransactionManager, TransactionStatus>>> THREAD_LOCAL = new ThreadLocal<>();
   private static Logger logger = LoggerFactory.getLogger(MultiTransactionAop.class);
   /**
    * 用于获取事务管理器
    */
   @Autowired
   private ApplicationContext applicationContext;
   /**
    * 事务声明
    */
   private DefaultTransactionDefinition def = new DefaultTransactionDefinition();
   {
       // 非只读模式
       def.setReadOnly(false);
       // 事务隔离级别:采用数据库的
       def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
       // 事务传播行为
       def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
   }
   /**
    * 切点
    */
   @Pointcut("@annotation(cn.zysheep.datasource.anntation.MultiDataSourceTransactional)")
   public void pointcut() {
   }
   /**
    * 声明事务
    *
    * @param transactional 注解
    */
   @Before("pointcut() && @annotation(transactional)")
   public void before(MultiDataSourceTransactional transactional) {
       // 根据设置的事务名称按顺序声明,并放到ThreadLocal里
       String[] transactionManagerNames = transactional.transactionManagers();
       Stack<Map<DataSourceTransactionManager, TransactionStatus>> pairStack = new Stack<>();
       for (String transactionManagerName : transactionManagerNames) {
           DataSourceTransactionManager transactionManager = applicationContext.getBean(transactionManagerName, DataSourceTransactionManager.class);
           TransactionStatus transactionStatus = transactionManager.getTransaction(def);
           Map<DataSourceTransactionManager, TransactionStatus> transactionMap = new HashMap<>();
           transactionMap.put(transactionManager, transactionStatus);
           pairStack.push(transactionMap);
       }
       THREAD_LOCAL.set(pairStack);
   }
   /**
    * 后置增强,相当于AfterReturningAdvice,方法退出时执行
    *
    * 提交事务
    */
   @AfterReturning("pointcut()")
   public void afterReturning() {
       // ※栈顶弹出(后进先出)
       Stack<Map<DataSourceTransactionManager, TransactionStatus>> pairStack = THREAD_LOCAL.get();
       while (!pairStack.empty()) {
           Map<DataSourceTransactionManager, TransactionStatus> pair = pairStack.pop();
           pair.forEach((key,value)->key.commit(value));
       }
       THREAD_LOCAL.remove();
   }
   /**
    * 异常抛出增强,相当于ThrowsAdvice
    *
    * 回滚事务
    */
   @AfterThrowing(value = "pointcut()")
   public void afterThrowing() {
       // ※栈顶弹出(后进先出)
       Stack<Map<DataSourceTransactionManager, TransactionStatus>> pairStack = THREAD_LOCAL.get();
       logger.info("=========================");
       logger.info("Pair Stack:{}", pairStack);
       logger.info("=========================");
       while (!pairStack.empty()) {
           Map<DataSourceTransactionManager, TransactionStatus> pair = pairStack.pop();
           pair.forEach((key,value)->key.rollback(value));
       }
       THREAD_LOCAL.remove();
   }
}

在事务的方法上添加前面我们的多数据源事务注解

/**
    * 测试多数据源事务
    */
   @MultiDataSourceTransactional(transactionManagers = {"masterTransactionManager", "clusterTransactionManager"})
   @Override
   public void saveUser() {
       String rid = UUID.randomUUID().toString();
       User user = new User();
       user.setId(rid);
       userMapper.saveUser(user);
       Log log = new Log();
       log.setId(rid);
       logMapper.saveLog(log);
       throw new RuntimeException();
   }

来源:https://blog.csdn.net/qq_45297578/article/details/127660906

标签:SpringBoot,Mybatis,多数据源配置,跨数据源事务
0
投稿

猜你喜欢

  • SpringBoot文件分片上传的示例代码

    2023-06-18 11:30:15
  • Flutter倒计时/计时器的实现代码

    2023-07-01 03:50:50
  • 自定义类加载器以及打破双亲委派模型解析

    2023-06-22 22:03:59
  • C语言运算符优先级列表(超详细)

    2023-07-04 08:17:54
  • C++实现的O(n)复杂度内查找第K大数算法示例

    2023-06-30 15:51:13
  • Java 异步编程实践_动力节点Java学院整理

    2023-06-18 17:04:12
  • 替换so文件来动态替换Flutter代码实现详解

    2023-06-23 16:24:06
  • 基于Android中获取资源的id和url方法总结

    2023-06-20 06:05:21
  • 浅谈c++性能测试工具之计算时间复杂度

    2023-07-09 12:40:05
  • C语言字符串另类用法的实现

    2023-06-19 02:05:25
  • Android RecyclerBarChart绘制使用教程

    2023-06-19 12:18:36
  • Flutter 容器盒子模型的使用示例

    2023-06-18 18:47:43
  • Android仿简书搜索框效果的示例代码

    2023-06-18 16:02:58
  • Android 实现签到足迹功能

    2023-06-21 15:16:24
  • C#中动态显示当前系统时间的实例方法

    2023-06-20 14:46:02
  • 基于Java数组实现循环队列的两种方法小结

    2023-06-30 16:09:01
  • 完美解决Android Studio集成crashlytics后无法编译的问题

    2023-06-23 16:49:07
  • 10种简单的Java性能优化

    2023-06-20 20:43:41
  • java简单实现斗地主发牌功能

    2023-06-18 16:22:44
  • Java进阶:Struts多模块的技巧

    2023-06-18 09:40:47
  • asp之家 软件编程 m.aspxhome.com