前言
可能由于业务上的某些需求,我们的系统中有时往往要连接多个数据库,这就产生了多数据源问题。
多数据源的情况下,一般我们要做到可以自动切换,此时会涉及到事务注解 Transactional 不生效问题和分布式事务问题。
关于多数据源方案,笔者在网上看过一些例子,然而大部分都是错误示例,根本跑不通,或者没办法兼容事务。
今天,我们就一点点来分析这些问题产生的根源和相应的解决方法。
一、多数据源
为了剧情的顺利开展,我们模拟的业务是创建订单和扣减库存。
所以,我们先创建订单表和库存表。注意,把他们分别放到两个数据库中。
CREATE TABLE `t_storage` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
CREATE TABLE `t_order` (
`id` bigint(16) NOT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT '0',
`amount` double(14,2) DEFAULT '0.00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
1、数据库连接
通过YML文件先把两个数据库都配置一下。
spring:
datasource:
ds1:
jdbc_url: jdbc:mysql://127.0.0.1:3306/db1
username: root
password: root
ds2:
jdbc_url: jdbc:mysql://127.0.0.1:3306/db2
username: root
password: root
2、配置DataSource
我们知道,Mybatis执行一条SQL语句的时候,需要先获取一个Connection。这时候,就交由Spring管理器到DataSource中获取连接。
Spring中有个具有路由功能的DataSource,它可以通过查找键调用不同的数据源,这就是AbstractRoutingDataSource
。
public abstract class AbstractRoutingDataSource{
//数据源的集合
@Nullable
private Map<Object, Object> targetDataSources;
//默认的数据源
@Nullable
private Object defaultTargetDataSource;
//返回当前的路由键,根据该值返回不同的数据源
@Nullable
protected abstract Object determineCurrentLookupKey();
//确定一个数据源
protected DataSource determineTargetDataSource() {
//抽象方法 返回一个路由键
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.targetDataSources.get(lookupKey);
return dataSource;
}
}
可以看到,该抽象类的核心就是先设置多个数据源到Map集合中,然后根据Key可以获取不同的数据源。
那么,我们就可以重写这个determineCurrentLookupKey方法,它返回的是一个数据源的名称。
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
return dataBaseType;
}
}
然后还需要一个工具类,来保存当前线程的数据源类型。
public class DataSourceType {
public enum DataBaseType {
ds1, ds2
}
// 使用ThreadLocal保证线程安全
private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();
// 往当前线程里设置数据源类型
public static void setDataBaseType(DataBaseType dataBaseType) {
if (dataBaseType == null) {
throw new NullPointerException();
}
TYPE.set(dataBaseType);
}
// 获取数据源类型
public static DataBaseType getDataBaseType() {
DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.ds1 : TYPE.get();
return dataBaseType;
}
}