SpringBoot Mybatis之读写分离

原理

在执行不同的逻辑前,选择使用具体的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 targetDataSources;

    private Object defaultTargetDataSource;

    private boolean lenientFallback = true;

    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

    private Map 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.
     * 

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(this.targetDataSources.size()); for (Map.Entry 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.bootgroupId>
    <artifactId>spring-boot-starter-webartifactId>
    <version>1.5.4.RELEASEversion>
dependency>
<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>1.3.1version>
dependency>
<dependency>
    <groupId>org.aspectjgroupId>
    <artifactId>aspectjweaverartifactId>
    <version>1.8.13version>
dependency>
<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <version>6.0.4version>
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 targetDataSources() {
        Map 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 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);
    }
}

这样就实现了一个简单的读写分离,当然如果想要完成更多的功能还需要做什么。

源码地址:https://github.com/shuaiweili/spring-boot-example

你可能感兴趣的:(SpringBoot,MyBatis)