原理
在执行不同的逻辑前,选择使用具体的DataSource;即将多个事先定义好的DataSource放在一个Map中,在需要DataSource的时候,使用具体的key来获取。先看一下javax.sql.DataSource接口:
public interface DataSource extends CommonDataSource, Wrapper {
Connection getConnection() throws SQLException;
Connection getConnection(String username, String password)
throws SQLException;
}
再来看下实现类:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
private Map<Object, DataSource> resolvedDataSources;
private DataSource resolvedDefaultDataSource;
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
/**
* Retrieve the current target DataSource. Determines the
* {@link #determineCurrentLookupKey() current lookup key}, performs
* a lookup in the {@link #setTargetDataSources targetDataSources} map,
* falls back to the specified
* {@link #setDefaultTargetDataSource default target DataSource} if necessary.
* @see #determineCurrentLookupKey()
*/
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
/**
* Determine the current lookup key. This will typically be
* implemented to check a thread-bound transaction context.
* <p>Allows for arbitrary keys. The returned key needs
* to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
protected abstract Object determineCurrentLookupKey();
@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
}
通过源码我们可以看到,创建DataSource的时候指定所有的targetDataSources(一个Map),然后需重写determineCurrentLookupKey()这个方法就可以拿到对应DataSource。
准备
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.4</version>
</dependency>
实现
datasource.properties
# 注:这个一定要加上,不然会报bean循环依赖?
spring.datasource.initialize=false
# master datasource
master.datasource.driverClassName: com.mysql.cj.jdbc.Driver
master.datasource.url: jdbc:mysql://localhost:3306/master?characterEncoding=utf8&serverTimezone=UTC&useSSL=false
master.datasource.username: root
master.datasource.password: 123456
# slave datasource
slave.datasource.driverClassName: com.mysql.cj.jdbc.Driver
slave.datasource.url: jdbc:mysql://localhost:3306/slave?characterEncoding=utf8&serverTimezone=UTC&useSSL=false
slave.datasource.username: root
slave.datasource.password: 123456
数据源配置
@Configuration
@PropertySource(value = "classpath:datasource.properties")
@MapperScan(value = "com.git.lee.spring.boot.example.mapper")
@EnableTransactionManagement
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "master.datasource")
public DataSource masterDataSource() {
return new org.apache.tomcat.jdbc.pool.DataSource();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "slave.datasource")
public DataSource slaveDataSource() {
return new org.apache.tomcat.jdbc.pool.DataSource();
}
//动态数据源
@Bean(name = "dynamicDataSource")
@Primary
public DataSource getDataSource() {
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources());
return dataSource;
}
private Map<Object, Object> targetDataSources() {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER.getType(), masterDataSource());
targetDataSources.put(DataSourceType.SLAVE.getType(), slaveDataSource());
return targetDataSources;
}
}
public class DynamicDataSource extends AbstractRoutingDataSource{
@Override
protected Object determineCurrentLookupKey() {
if (DataSourceHolder.getDataSource() != null) {
return DataSourceHolder.getDataSource();
}
return DataSourceType.MASTER.getType();
}
}
public class DataSourceHolder {
private static final ThreadLocal<String> holder = new ThreadLocal<>();
public static void putDataSource(DataSourceType dataSourceType) {
holder.set(dataSourceType.getType());
}
public static String getDataSource(){
return holder.get();
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
@Inherited
public @interface DataSource {
DataSourceType value() default DataSourceType.MASTER;
}
切面植入
@Configuration
@Aspect
public class DataSourceAspect implements Ordered {
//使用@DataSource注解的时候会设置具体使用的数据源
@Before(value = "@annotation(dataSource)")
public void dataSourcePoint(JoinPoint jp, DataSource dataSource) {
DataSourceHolder.putDataSource(dataSource.value());
}
//order 越低,代表优先级越高
@Override
public int getOrder() {
return -1;
}
}
业务逻辑
@Service
public class NodeService {
@Autowired
private NodeMapper nodeMapper;
@DataSource(DataSourceType.SLAVE)
@Transactional(rollbackFor = Exception.class)
public void add(Node node) {
nodeMapper.save(node);
}
@DataSource(DataSourceType.MASTER)
public Node findById(int id) {
return nodeMapper.findById(id);
}
}
这样就实现了一个简单的读写分离,当然如果想要完成更多的功能还需要做什么。