SpringBoot整合多数据源及事务使用(Mybatis)

介绍

随着并发量的不断增加,显然单个数据库已经承受不了高并发带来的压力。一个项目使用多个数据库(无论是主从复制- - 读写分离还是分布式数据库结构)的重要性变得越来越明显。传统项目中(个人对传统项目的理解就是所有的业务模块都在一个tomcat中完成,多个相同的tomcat集群也可认为是传统项目)整合多数据源有两种方法:分包和注解。

第一种实现方式(分包)

实现方式

1、引入依赖
这里使用MySQL数据库

 <dependency>
     <groupId>org.mybatis.spring.bootgroupId>
     <artifactId>mybatis-spring-boot-starterartifactId>
     <version>2.1.0version>
 dependency>
 
 <dependency>
     <groupId>mysqlgroupId>
     <artifactId>mysql-connector-javaartifactId>
     <scope>runtimescope>
 dependency>

2、application.yml 配置文件

server:
  port: 8080 # 启动端口
spring:
  datasource: 
    db1: # 数据源1
      jdbc-url: jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver
    db2: # 数据源2
      jdbc-url: jdbc:mysql://localhost:3306/db2?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=GMT%2B8
      username: root
      password: root
      driver-class-name: com.mysql.cj.jdbc.Driver

注意:各个版本的 springboot 配置 datasource 时参数有所变化,例如低版本配置数据库 url 时使用 url 属性,高版本使用 jdbc-url 属性,请注意区分。

3、建立连接数据源的配置文件
java配置类中制定了这个数据源的dao接口都在哪个包里,使用这个包里面的dao接口,操作的就是这个数据源了。同时也配置mapper的xml文件。

(1)第一个配置文件

@Configuration
//配置mybatis的接口类放的地方
@MapperScan(basePackages = "com.example.multipledatasource.mapper.db1", sqlSessionFactoryRef = "db1SqlSessionFactory")
public class DataSourceConfig1 {

    @Primary // 表示这个数据源是默认数据源, 这个注解必须要加,因为不加的话spring将分不清楚那个为主数据源(默认数据源)
    @Bean("db1DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.db1") //读取application.yml中的配置参数映射成为一个对象
    public DataSource getDb1DataSource(){
        return DataSourceBuilder.create().build();
    }

	
    @Primary// 表示这个数据源是默认数据源
    @Bean("db1SqlSessionFactory")
    // @Qualifier表示查找Spring容器中名字为test1DataSource的对象
    public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        // mapper的xml形式文件位置必须要配置,不然将报错:no statement (这种错误也可能是mapper的xml中,namespace与项目的路径不一致导致)
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/db1/*.xml"));
        return bean.getObject();
    }

    @Primary
    @Bean("db1SqlSessionTemplate")
    public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        return new SqlSessionTemplate(sqlSessionFactory);
    }

	//创建db1的事务管理器
	@Bean()
	@Primary
	public DataSourceTransactionManager db1TransactionManager(@Qualifier("db1DataSource") DataSource dataSource){
	return new DataSourceTransactionManager(dataSource);
	}
	
}

(2)第二个配置文件

@Configuration
//配置mybatis的接口类放的地方
@MapperScan(basePackages = "com.example.multipledatasource.mapper.db2", sqlSessionFactoryRef = "db2SqlSessionFactory")
public class DataSourceConfig2 {

    @Bean("db2DataSource")
    @ConfigurationProperties(prefix = "spring.datasource.db2")
    public DataSource getDb1DataSource(){
        return DataSourceBuilder.create().build();
    }

    @Bean("db2SqlSessionFactory")
    public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db2DataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/db2/*.xml"));
        return bean.getObject();
    }

    @Bean("db2SqlSessionTemplate")
    public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db2SqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        return new SqlSessionTemplate(sqlSessionFactory);
    }
	//创建db2的事务管理器
	@Bean()
	public DataSourceTransactionManager db1TransactionManager(@Qualifier("db2DataSource") DataSource dataSource){
	return new DataSourceTransactionManager(dataSource);
	}
}

注意:

  1. @Primary这个注解必须要加,因为不加的话spring将分不清楚那个为主数据源(默认数据源)
  2. bean.setMapperLocations(newPathMatchingResourcePatternResolver().getResources(“XXXX”));mapper的xml形式文件位置必须要配置,不然将报错:no statement (这种错误也可能是mapper的xml中,namespace与项目的路径不一致导致的,具体看情况吧,注意一下就行,问题不大的)
  3. 在service层中根据不同的业务注入不同的dao层。
  4. 如果是主从复制- -读写分离:比如test01中负责增删改,test02中负责查询。但是需要注意的是负责增删改的数据库必须是主库(master)

事务问题

操作单个数据源的事务时

使用下面这个注解(spring声明式事务注解),需要注意的是,必须要指定对应数据源的数据管理器(也就是这个属性:transactionManager)

@Transactional(rollbackFor = Exception.class,transactionManager = "memberTransactionManager")
@RequestMapping("/addUser")
public String addUser(String name, Integer age)  {
    return memberMapper.addUser(name, age)>0?"success":"fail";
}

多数据源事务处理

这里需要使用jta-atomikos进行多数据源事务管理。
pom中引入jta-atomikos依赖:

<dependency>
	<groupId>org.springframework.bootgroupId>
	<artifactId>spring-boot-starter-jta-atomikosartifactId>
dependency>

修改application.yml文件,添加一些配置,注意,这里的数据库地址的key是’url’,不然会报空指针。

server:
  port: 8080
spring:
  datasource:
    db1:
      url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf-8
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver
      borrowConnectionTimeout: 30
      loginTimeout: 30
      maintenanceInterval: 60
      maxIdleTime: 60
      maxLifetime: 20000
      maxPoolSize: 25
      minPoolSize: 3
      uniqueResourceName: db1DataSource
    db2:
      url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf-8
      username: root
      password: root
      driver-class-name: com.mysql.jdbc.Driver
      borrowConnectionTimeout: 30
      loginTimeout: 30
      maintenanceInterval: 60
      maxIdleTime: 60
      maxLifetime: 20000
      maxPoolSize: 25
      minPoolSize: 3
      uniqueResourceName: db2DataSource

在config/db1中添加Db1Config配置类,属性与yml中的数据源配置是一致的

@Data
@ConfigurationProperties(prefix = "spring.datasource.db1")  //负责解析对应属性在yml文件的前缀。
public class Db1Config {
    private String url;
    private String username;
    private String password;
    private int minPoolSize;
    private int maxPoolSize;
    private int maxLifetime;
    private int borrowConnectionTimeout;
    private int loginTimeout;
    private int maintenanceInterval;
    private int maxIdleTime;
    private String testQuery;
    private String uniqueResourceName;
}

在config/db2中添加Db2Config配置类,属性与yml中的数据源配置是一致的

@Data
@ConfigurationProperties(prefix = "spring.datasource.db2") //负责解析对应属性在yml文件的前缀。
public class Db2Config {
    private String url;
    private String username;
    private String password;
    private int minPoolSize;
    private int maxPoolSize;
    private int maxLifetime;
    private int borrowConnectionTimeout;
    private int loginTimeout;
    private int maintenanceInterval;
    private int maxIdleTime;
    private String testQuery;
    private String uniqueResourceName;
}

修改创建db1DataSource的方法,这里db1Config可能会报红无法注入,先不用管,之后在启动类上加上一个注解即可消除,文章稍后会讲到。可以看到,这里的ConfigurationProperties注解被删除了,因为被移到Db1Config里了。

 	@Bean("db1DataSource")
    public DataSource db1DataSource(Db1Config db1Config ) throws SQLException {
        System.out.println(db1Config );
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(db1Config .getUrl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(db1Config .getPassword());
        mysqlXaDataSource.setUser(db1Config .getUsername());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        //注册到全局事务
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName(db1Config .getUniqueResourceName());
        xaDataSource.setMinPoolSize(db1Config .getMinPoolSize());
        xaDataSource.setMaxPoolSize(db1Config .getMaxPoolSize());
        xaDataSource.setMaxLifetime(db1Config .getMaxLifetime());
        xaDataSource.setBorrowConnectionTimeout(db1Config .getBorrowConnectionTimeout());
        xaDataSource.setLoginTimeout(db1Config .getLoginTimeout());
        xaDataSource.setMaintenanceInterval(db1Config .getMaintenanceInterval());
        xaDataSource.setMaxIdleTime(db1Config .getMaxIdleTime());
        xaDataSource.setTestQuery(db1Config .getTestQuery());
        return xaDataSource;
    }

之后删除或者注释掉该类中的事务管理器。

修改创建db2DataSource的方法,这里db2Config可能会报红无法注入,先不用管,之后在启动类上加上一个注解即可消除,文章稍后会讲到。可以看到,这里的ConfigurationProperties注解被删除了,因为被移到Db2Config里了。

@Bean("db2DataSource")
    public DataSource orderDataSource(Db2Config db2Config ) throws SQLException {
        System.out.println(db2Config );
        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
        mysqlXaDataSource.setUrl(db2Config .getUrl());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
        mysqlXaDataSource.setPassword(db2Config .getPassword());
        mysqlXaDataSource.setUser(db2Config .getUsername());
        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);

        //注册到全局事务
        AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
        xaDataSource.setXaDataSource(mysqlXaDataSource);
        xaDataSource.setUniqueResourceName(db2Config .getUniqueResourceName());
        xaDataSource.setMinPoolSize(db2Config .getMinPoolSize());
        xaDataSource.setMaxPoolSize(db2Config .getMaxPoolSize());
        xaDataSource.setMaxLifetime(db2Config .getMaxLifetime());
        xaDataSource.setBorrowConnectionTimeout(db2Config .getBorrowConnectionTimeout());
        xaDataSource.setLoginTimeout(db2Config .getLoginTimeout());
        xaDataSource.setMaintenanceInterval(db2Config .getMaintenanceInterval());
        xaDataSource.setMaxIdleTime(db2Config .getMaxIdleTime());
        xaDataSource.setTestQuery(db2Config .getTestQuery());
        return xaDataSource;
    }

之后删除或者注释掉该类中的事务管理器.

Transactional注解的transactionManager属性需要删除,因为不需要指定事务管理器了,统一为xa事务管理器。

在启动类中添加EnableConfigurationProperties注解,解决之前提到的配置类报红显示无法注入的问题。

@SpringBootApplication
@EnableConfigurationProperties({DB1Config.class, Db2Config.class})
public class MultiDatasourceApplication {
    public static void main(String[] args) {
        SpringApplication.run(MultiDatasourceApplication.class);
    }
}

第二种实现方式(注解)

springboot+druid+mybatisplus使用注解整合

介绍:下面完成了springboot整合mybatisplus、多数据源整合、多数据源事务

1、pom文件依赖

<dependency>
           <groupId>com.baomidougroupId>
           <artifactId>mybatis-plus-boot-starterartifactId>
           <version>3.2.0version>
       dependency>
       <dependency>
           <groupId>com.baomidougroupId>
           <artifactId>dynamic-datasource-spring-boot-starterartifactId>
           <version>2.5.6version>
       dependency>
       <dependency>
           <groupId>mysqlgroupId>
           <artifactId>mysql-connector-javaartifactId>
           <scope>runtimescope>
       dependency>
       
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.1.12version>
        dependency>
       
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jta-atomikosartifactId>
        dependency>
       <dependency>
           <groupId>org.projectlombokgroupId>
           <artifactId>lombokartifactId>
           <optional>trueoptional>
       dependency>

2、 application.yml 配置文件

spring:
  jta:
    # 事务管理器唯一标识符
    transaction-manager-id: txManager
  datasource:
    # Druid连接池配置。spring-boot-2默认连接池hikari不支持MysqlXADataSource
    type: com.alibaba.druid.pool.xa.DruidXADataSource
    # 最小空闲连接
    min-pool-size: 5
    # 池中最大连接数
    max-pool-size: 20
    # 设置连接在池中被自动销毁之前保留的最大秒数。 可选,默认为0(无限制)。
    max-life-time: 60
    # 返回连接前用于测试连接的SQL查询
    test-query: SELECT 1
 
    # 多数据源配置
    cpq-db:
      name: cpq
      url: jdbc:mysql://localhost:3306/cpq?useUnicode=true&characterEncoding=utf8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=false
      username: root
      password: cpq..123
    shiro-db:
      name: shiro
      url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&useSSL=false
      username: root
      password: cpq..123
 
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #打印sql

配置数据源的DataSource、SqlSessionFactory

/**
 * cpq数据库配置类
 */
@Configuration
@MapperScan(basePackages = "com.example.multidatasource.cpq.**.mapper",
        sqlSessionFactoryRef = CpqDataSourcesConfig.SQL_SESSION_FACTORY)
public class CpqDataSourcesConfig {
 
    public static final String DATABASE_PREFIX = "spring.datasource.cpq-db.";
 
    public static final String DATA_SOURCE_NAME = "cpqDataSource";
    public static final String SQL_SESSION_FACTORY = "cpqSqlSessionFactory";
 
 
    /**
     * 通过配置文件创建DataSource,一个数据库对应一个DataSource
     * @param environment 环境变量,spring-boot会自动将IOC中的environment实例设置给本参数值
     * 由于IOC中有多个DataSource实例,必须给其中一个实例加上@Primary
     */
    @Primary
    @Bean(DATA_SOURCE_NAME)
    public DataSource dataSource(Environment environment) {
        return DataSourceUtil.createAtomikosDataSourceBean(DATA_SOURCE_NAME, environment, DATABASE_PREFIX);
    }
 
    /**
     * 通过dataSource创建SqlSessionFactory
     * 由于IOC中有多个DataSource实例,必须给其中一个实例加上@Primary
     */
    @Primary
    @Bean(name = SQL_SESSION_FACTORY)
    public SqlSessionFactory sqlSessionFactory(@Qualifier(DATA_SOURCE_NAME) DataSource dataSource) throws Exception {
        return DataSourceUtil.createSqlSessionFactory(dataSource);
    }
 
}
/**
 * shiro数据库配置类
 */
@Configuration
@MapperScan(basePackages = "com.example.multidatasource.shiro.**.mapper",
        sqlSessionFactoryRef = ShiroDataSourcesConfig.SQL_SESSION_FACTORY)
public class ShiroDataSourcesConfig {
 
    public static final String DATABASE_PREFIX = "spring.datasource.shiro-db.";
    public static final String DATA_SOURCE_NAME = "shiroDataSource";
    public static final String SQL_SESSION_FACTORY = "shiroSqlSessionFactory";
 
    @Bean(DATA_SOURCE_NAME)
    public DataSource dataSource(Environment environment) {
        return DataSourceUtil.createAtomikosDataSourceBean(DATA_SOURCE_NAME, environment, DATABASE_PREFIX);
    }
 
    @Bean(name = SQL_SESSION_FACTORY)
    public SqlSessionFactory sqlSessionFactory(@Qualifier(DATA_SOURCE_NAME) DataSource dataSource) throws Exception {
        return DataSourceUtil.createSqlSessionFactory(dataSource);
    }
}
public class DataSourceUtil {
 
    public static final String DATA_SOURCE_PREFIX = "spring.datasource.";
 
    /**
     * 创建AtomikosDataSourceBean是使用Atomikos连接池的首选类
     */
    public static AtomikosDataSourceBean createAtomikosDataSourceBean(String uniqueResourceName, Environment environment, String dataBase ){
        AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
        // 这些设置大家可以进入源码中看java-doc
        // 数据源唯一标识
        atomikosDataSourceBean.setUniqueResourceName(uniqueResourceName);
        // XADataSource实现类,使用DruidXADataSource
        atomikosDataSourceBean.setXaDataSourceClassName(environment.getProperty(DATA_SOURCE_PREFIX+"type"));
        // 最小连接数,默认1
        atomikosDataSourceBean.setMinPoolSize(environment.getProperty(DATA_SOURCE_PREFIX+"min-pool-size", Integer.class));
        // 最大连接数,默认1
        atomikosDataSourceBean.setMaxPoolSize(environment.getProperty(DATA_SOURCE_PREFIX+"max-pool-size", Integer.class));
        // 设置连接在池中被自动销毁之前保留的最大秒数。 可选,默认为0(无限制)。
        atomikosDataSourceBean.setMaxLifetime(environment.getProperty(DATA_SOURCE_PREFIX+"max-life-time", Integer.class));
        // 返回连接前用于测试连接的SQL查询
        atomikosDataSourceBean.setTestQuery(environment.getProperty(DATA_SOURCE_PREFIX+"test-query"));
 
        MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
        mysqlXADataSource.setDatabaseName(environment.getProperty(dataBase+"name"));
        mysqlXADataSource.setURL(environment.getProperty(dataBase+"url"));
        mysqlXADataSource.setUser(environment.getProperty(dataBase+"username"));
        mysqlXADataSource.setPassword(environment.getProperty(dataBase+"password"));
        atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
 
        return atomikosDataSourceBean;
    }
 
    /**
     * 创建SqlSessionFactory实例
     */
    public static SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws Exception{
        /**
         * 必须使用MybatisSqlSessionFactoryBean,
         * 不能使用SqlSessionFactoryBean,不然会报invalid bound statement (not found)
         *
         * com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration#sqlSessionFactory(javax.sql.DataSource)
         * 源码中也是使用MybatisSqlSessionFactoryBean
         * 并且源码中使用了@ConditionalOnMissingBean,即IOC中如果存在了SqlSessionFactory实例,mybatis-plus就不创建SqlSessionFactory实例了
         */
        MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean();
        sessionFactoryBean.setDataSource(dataSource);
        MybatisConfiguration configuration = new MybatisConfiguration();
        sessionFactoryBean.setConfiguration(configuration);
        return sessionFactoryBean.getObject();
    }
 
}

3、给使用非默认数据源添加注解@DS
@DS 可以注解在方法上和类上,同时存在方法注解优先于类上注解。
注解在 service 实现或 mapper 接口方法上,不要同时在 service 和 mapper 注解。

你可能感兴趣的:(springboot)