Spring Boot 集成Mybatis實現主從(多數據源)分離方案

新建一個Maven項目,最終項目結構如下:
Spring Boot éæMybatiså®ç°ä¸»ä»ï¼å¤æ°æ®æºï¼å离æ¹æ¡
多數據源注入到sqlSessionFactory
POM增加如下依賴:

<!--JSON-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-joda</artifactId>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-parameter-names</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.11</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>
        <!--mapper-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>1.1.0</version>
        </dependency>
        <!--pagehelper-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.1.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>mybatis-spring-boot-starter</artifactId>
                    <groupId>org.mybatis.spring.boot</groupId>
                </exclusion>
            </exclusions>
        </dependency>

這裏需要注意的是:項目是通過擴展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration來實現多數據源注入的。在mybatis-spring-boot-starter:1.2.0中,該類取消了默認構造函數,因此本項目依舊使用1.1.0版本。需要關注後續版本是否會重新把擴展開放處理。
之所以依舊使用舊方案,是我個人認爲開放擴展是合理的,相信在未來的版本中會迴歸。

增加主從庫配置(application.yml)

druid:
    type: com.alibaba.druid.pool.DruidDataSource    master:
        url: jdbc:mysql://192.168.249.128:3307/db-test?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true
        driver-class-name: com.mysql.jdbc.Driver
        username: root        password: root
        initial-size: 5
        min-idle: 1
        max-active: 100
        test-on-borrow: true
    slave:
        url: jdbc:mysql://192.168.249.128:3317/db-test?characterEncoding=UTF-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8
        driver-class-name: com.mysql.jdbc.Driver
        username: root        password: root
        initial-size: 5
        min-idle: 1
        max-active: 100
        test-on-borrow: true

創建數據源

@Configuration@EnableTransactionManagementpublic class DataSourceConfiguration {    @Value("${druid.type}")    private Class<? extends DataSource> dataSourceType;    @Bean(name = "masterDataSource")    @Primary
    @ConfigurationProperties(prefix = "druid.master")    public DataSource masterDataSource(){        return DataSourceBuilder.create().type(dataSourceType).build();
    }    @Bean(name = "slaveDataSource")    @ConfigurationProperties(prefix = "druid.slave")    public DataSource slaveDataSource1(){        return DataSourceBuilder.create().type(dataSourceType).build();
    }
}

將多數據源注入到sqlSessionFactory中
前面提到了這裏通過擴展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration來實現多數據源注入的

@Configuration@AutoConfigureAfter({DataSourceConfiguration.class})public class MybatisConfiguration extends MybatisAutoConfiguration {    private static Log logger = LogFactory.getLog(MybatisConfiguration.class);    @Resource(name = "masterDataSource")    private DataSource masterDataSource;    @Resource(name = "slaveDataSource")    private DataSource slaveDataSource;    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {        return super.sqlSessionFactory(roundRobinDataSouceProxy());
    }    public AbstractRoutingDataSource roundRobinDataSouceProxy(){
        ReadWriteSplitRoutingDataSource proxy = new ReadWriteSplitRoutingDataSource();
        Map<Object,Object> targetDataResources = new ClassLoaderRepository.SoftHashMap();
        targetDataResources.put(DbContextHolder.DbType.MASTER,masterDataSource);
        targetDataResources.put(DbContextHolder.DbType.SLAVE,slaveDataSource);
        proxy.setDefaultTargetDataSource(masterDataSource);//默認源
        proxy.setTargetDataSources(targetDataResources);        return proxy;
    }
}

實現讀寫分離(多數據源分離)
這裏主要思路如下:
1-將不同的數據源標識記錄在ThreadLocal中
2-通過註解標識出當前的service方法使用哪個庫
3-通過Spring AOP實現攔截註解並注入不同的標識到threadlocal中
4-獲取源的時候通過threadlocal中不同的標識給出不同的sqlSession

標識存放ThreadLocal的實現

public class DbContextHolder {    public enum DbType{
        MASTER,SLAVE
    }    private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<>();    public static void setDbType(DbType dbType){        if(dbType==null)throw new NullPointerException();
        contextHolder.set(dbType);
    }    public static DbType getDbType(){        return contextHolder.get()==null?DbType.MASTER:contextHolder.get();
    }    public static void clearDbType(){
        contextHolder.remove();
    }

}

註解實現

/**
 * 該註解註釋在service方法上,標註爲鏈接slaves庫
 * Created by Jason on 2017/3/6.
 */@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnlyConnection {
}

Spring AOP對註解的攔截

@Aspect@Componentpublic class ReadOnlyConnectionInterceptor implements Ordered {    public static final Logger logger = LoggerFactory.getLogger(ReadOnlyConnectionInterceptor.class);    @Around("@annotation(readOnlyConnection)")    public Object proceed(ProceedingJoinPoint proceedingJoinPoint,ReadOnlyConnection readOnlyConnection) throws Throwable {        try {
            logger.info("set database connection to read only");
            DbContextHolder.setDbType(DbContextHolder.DbType.SLAVE);
            Object result = proceedingJoinPoint.proceed();            return result;
        }finally {
            DbContextHolder.clearDbType();
            logger.info("restore database connection");
        }
    }    @Override
    public int getOrder() {        return 0;
    }
}

根據標識獲取不同源
這裏我們通過擴展AbstractRoutingDataSource來獲取不同的源。它是Spring提供的一個可以根據用戶發起的不同請求去轉換不同的數據源,比如根據用戶的不同地區語言選擇不同的數據庫。通過查看源碼可以發現,它是通過determineCurrentLookupKey()返回的不同key到sqlSessionFactory中獲取不同源(前面已經展示瞭如何在sqlSessionFactory中注入多個源)

public class ReadWriteSplitRoutingDataSource extends AbstractRoutingDataSource {    @Override
    protected Object determineCurrentLookupKey() {        return DbContextHolder.getDbType();
    }
}

以上就完成了讀寫分離(多數據源)的配置方案。下面是一個具體的實例

使用方式
Entity

@Table(name = "t_sys_dic_type")public class DicType extends BaseEntity{

    String code;

    String name;

    Integer status;

    ...
}

Mapper

public interface DicTypeMapper extends BaseMapper<DicType> {
}

Service

@Servicepublic class DicTypeService {    @Autowired
    private DicTypeMapper dicTypeMapper;    @ReadOnlyConnection
    public List<DicType> getAll(DicType dicType){        if (dicType.getPage() != null && dicType.getRows() != null) {
            PageHelper.startPage(dicType.getPage(), dicType.getRows());
        }        return dicTypeMapper.selectAll();
    }

}

注意這裏的@ReadOnlyConnection註解

Controller

@RestController@RequestMapping("/dictype")public class DicTypeController {    @Autowired
    private DicTypeService dicTypeService;    @RequestMapping(value = "/all")    public PageInfo<DicType> getALL(DicType dicType){
        List<DicType> dicTypeList = dicTypeService.getAll(dicType);        return new PageInfo<>(dicTypeList);
    }
}

通過mvn spring-boot:run啓動後

後臺打印出

c.a.d.m.ReadOnlyConnectionInterceptor : set database connection to read only

說明使用了從庫的鏈接獲取數據

大家記得點贊關注一下   有好的網站渠道記得分享在評論區

動動手指掃描二維碼關注一下我

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章