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

目录
  • Pom依赖
  • application.yml
  • 多数据源配置
    • MasterDataSourceConfig
    • ClusterDataSourceConfig
    • 启动类
    • 使用
  • 实现跨数据源事务

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(此所谓第一阶段提交),最后如果都成功就把事务全部提交,只要有一个失败就把事务都回滚——此所谓第二阶段提交。

/**
 * 需要注意的点:
 * 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();
    }

到此这篇关于SpringBoot整合Mybatis实现多数据源配置与跨数据源事务实例的文章就介绍到这了,更多相关SpringBoot多数据源配置内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • SpringBoot Mybatis批量插入Oracle数据库数据

    目录 前端数据 数据表结构 后端Controller: mapper xml 前端数据 有如下需求,前端提交一个对象cabinData,保存到数据表中,对象结构如下:  {      "shipId":"424",      "shipName":"大唐2号",      "ballastCabinData":["艏尖舱","双层底1左","双层底1右&qu

  • 在springboot中如何给mybatis加拦截器

    目录 1.实现Interceptor接口,并添加拦截注解 @Intercepts 1.在mybatis中可被拦截的类型有四种(按照拦截顺序) 2.各个参数的含义 2.在配置文件中添加拦截器 (1)第一种 (2)第二种 (3)第三种 拦截器的作用就是我们可以拦截某些方法的调用,在目标方法前后加上我们自己逻辑 Mybatis拦截器设计的一个初衷是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑. mybatis 自定义拦截器 1.实现Interceptor 接口,并添加拦截注

  • SpringBoot项目使用mybatis-plus代码生成的实例详解

    目录 前言 安装依赖 application.yml添加配置 代码生成实例 代码生成依赖 数据源配置 globalConfig处理通用配置 packageConfig包名设置 strategyConfig配置 小结 总结 前言 mybatis-plus官方地址 https://baomidou.com mybatis-plus是mybatis的增强,不对mybatis做任何改变,涵盖了代码生成,自定义ID生成器,快速实现CRUD,自动分页,逻辑删除等功能,更多功能请查阅官方文档 安装依赖 myb

  • SpringBoot整合Mybatis-plus案例及用法实例

    目录 一.mybatis-plus简介: 二.springboot整合mybatis-plus案例 mybatis plus强大的条件构造器queryWrapper.updateWrapper 总结 一.mybatis-plus简介: Mybatis-Plus(简称MP)是一个 Mybatis 的增强工具,在 Mybatis 的基础上只做增强不做改变,为简化开发.提高效率而生.这是官方给的定义,关于mybatis-plus的更多介绍及特性,可以参考mybatis-plus官网.那么它是怎么增强的

  • SpringBoot整合Mybatis简单实现增删改查

    目录 前言 第一:创建MySQL数据库 第二:创建SpringBoot项目,引入需要的依赖包 第三:创建程序目录和配置核心application.xml文件 第四:依次编写Entity.Dao.Service.Controller层,还有创建mapper.xml文件 第五:测试结果(这里我用的postman来进行接口测试) 第六:总结 前言 为什么要学SpringBoot和Mybatis呢,我觉得作为一个合格的后端程序员增删改查(CRUD)肯定是要会的,而且是最基本的一项技能,不然怎么完成给你分

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

    目录 Pom依赖 application.yml 多数据源配置 MasterDataSourceConfig ClusterDataSourceConfig 启动类 使用 实现跨数据源事务 Pom依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.

  • SpringBoot整合Mybatis,解决TypeAliases配置失败的问题

    问题描述 在应用MyBatis时,使用对象关系映射,将对象和Aliase映射起来. 在Mybatis的文档明确写出,如果你没有明确定义实体类的Aliase,框架会自动将Class Name自动作为别名. 那么问题来了,当使用java -jar xxx.jar&启动的时候,会报出以下错误, Error resolving class. Cause: org.apache.ibatis.type.TypeException: Could not resolve type alias 'XXXXX'.

  • springboot整合mybatis的超详细过程(配置模式+注解模式)

    目录 一.简单介绍 二具体配置 2.1.配置相关的依赖. 2.2 写.mapper.controller.service 2.2.1mapper文件 2.2.2service文件 2.2.2controller文件 2.3配置相关文件 三.结果截图 四.可能遇到的报错 一.简单介绍 1.配置相关的依赖2.配置模式3写.mapper.controller.service4.配置yaml文件 配置mybatis全局配置文件(这里我使用的是配置模式+注解模式所以需要配置全局文件) 二具体配置 2.1.

  • SpringBoot整合MyBatis逆向工程及 MyBatis通用Mapper实例详解

    一.添加所需依赖,当前完整的pom文件如下: <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&q

  • SpringBoot整合mybatis简单案例过程解析

    这篇文章主要介绍了SpringBoot整合mybatis简单案例过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.在springboot项目中的pom.xml中添加mybatis的依赖 <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifac

  • SpringBoot整合Mybatis使用Druid数据库连接池

    本文实例为大家分享了SpringBoot整合Mybatis使用Druid数据库连接池的方法,具体内容如下 在SpringBoot项目中,增加如下依赖 <!-- spring mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version

  • SpringBoot整合mybatis常见问题(小结)

    Spring中常见问题 1.NoSuchBeanDefinitionException 2.'..Service' that could not be found service找不到 3.port 80 was already in use 端口号被占用 4.TemplateInputException 模板解析异常或找不到模板 1.检查模板所在的目录是否与配置的前缀目录相同 2.检查返回的模板是否存在,返回值类型是否一致 3.检查配置前缀时是否以"/"斜杠结尾 4.控制层的url与

  • 使用sts工具、SpringBoot整合mybatis的详细步骤

    SpringBoot 集成 Mybatis 框架 一.1.SpringBoot 集成 Mybatis 的基本步骤 第一步:添加依赖: 第二步:配置数据源: 第三步:扫描接口包. 二.详细的集成步骤如下: 1.第一步:添加依赖: 添加依赖:除了常规依赖外,需要加入 Mybatis 代码如下(示例): <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XM

  • SpringBoot整合MyBatis超详细教程

    1.整合MyBatis操作 前面一篇提到了SpringBoot整合基础的数据源JDBC.Druid操作,实际项目中更常用的还是MyBatis框架,而SpringBoot整合MyBatis进行CRUD也非常方便. 下面从配置模式.注解模式.混合模式三个方面进行说明MyBatis与SpringBoot的整合. 1.1.配置模式 MyBatis配置模式是指使用mybatis配置文件的方式与SpringBoot进行整合,相对应的就有mybatis-config.xml(用于配置驼峰命名,也可以省略这个文

  • SpringBoot整合Mybatis自定义拦截器不起作用的处理方案

    目录 SpringBoot整合Mybatis自定义拦截器不起作用 1. 原始的读取mybatis-config.xml文件 2. 与SpringBoot容器整合 2.1 mybatis的自动装载 3. 在mybatis-config.xml配置又放入Spring容器 SpringBoot 自定义Mybatis拦截器 第一种 第二种 第三种 SpringBoot整合Mybatis自定义拦截器不起作用 Mybatis插件生效的方式: 1. 原始的读取mybatis-config.xml文件 该方式和

随机推荐