Spring Boot 集成Mybatis实现主从(多数据源)分离方案

栏目: Java · 发布时间: 5年前

内容简介:这里需要注意的是:项目是通过扩展mybatis-spring-boot-starter的org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration来实现多数据源注入的。在mybatis-spring-boot-starter:1.2.0中,该类取消了默认构造函数,因此本项目依旧使用1.1.0版本。需要关注后续版本是否会重新把扩展开放处理。之所以依旧使用旧方案,是我个人认为开放扩展是合理的,相信在未来的版本中会回归。增加主从库配置(appli

新建一个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
@EnableTransactionManagement
public 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
@Component
public 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

@Service
public 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

说明使用了从库的链接获取数据

备注:如何保证多源事务呢?

br/>1-在读写分离场景中不会考虑主从库事务,在纯读的上下文上使用@ReadOnlyConnection标签。其他则默认使用主库。

2-在多源场景中,Spring的@Transaction是可以保证多源的各自事务性的。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Java Servlet & JSP Cookbook

Java Servlet & JSP Cookbook

Bruce W. Perry / O'Reilly Media / 2003-12-1 / USD 49.99

With literally hundreds of examples and thousands of lines of code, the Java Servlet and JSP Cookbook yields tips and techniques that any Java web developer who uses JavaServer Pages or servlets will ......一起来看看 《Java Servlet & JSP Cookbook》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具