随着并发量的不断增加,显然单个数据库已经承受不了高并发带来的压力。一个项目使用多个数据库(无论是主从复制- - 读写分离还是分布式数据库结构)的重要性变得越来越明显。传统项目中(个人对传统项目的理解就是所有的业务模块都在一个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);
}
}
注意:
使用下面这个注解(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整合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 注解。