zoukankan      html  css  js  c++  java
  • SpringBoot2 + Druid + Mybatis 多数据源动态配置

    在大数据高并发的应用场景下,为了更快的响应用户请求,读写分离是比较常见的应对方案。读写分离会使用多数据源的使用。下面记录如何搭建SpringBoot2 + Druid + Mybatis  多数据源配置以及在使用过程遇到的问题。

    一、先从pom.xml入手(使用springboot 2的版本)

    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.5.RELEASE</version>
    </parent>
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.1</version>
    </dependency>
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
    </dependency>
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
    </dependency>
    <dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
    </dependency>
    </dependencies>

    inject是java依赖注入标准。spring默认支持识别。spring自带的@Autowired的缺省情况等价于JSR-330的@Inject注解;@Qualifier的缺省的根据Bean名字注入情况等价于JSR-330的@Named注解。

    二、添加读取DB的Mapper

    @Mapper
    public interface AssetMapper {

    @Select("select * from Asset where account = #{account}")
    Asset queryName(String account);
    }

    此处使用mybatis的注解功能,因此可以少省去*.xml等配置文件。

    三、添加多数据源的配置参数

    spring.datasource.druid.write.url=jdbc:mysql://192.168.0.110:3306/master?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    spring.datasource.druid.write.username=root
    spring.datasource.druid.write.password=123456
    spring.datasource.druid.write.driver-class-name=com.mysql.cj.jdbc.Driver
    #
    spring.datasource.druid.read.url=jdbc:mysql://192.168.0.110:3306/slave1?characterEncoding=utf-8&serverTimezone=Asia/Shanghai
    spring.datasource.druid.read.username=root
    spring.datasource.druid.read.password=123456
    spring.datasource.druid.read.driver-class-name=com.mysql.cj.jdbc.Driver

    新版本mysql的url后面必需要添加serverTimezone=。 不然会报以下异常:
    2019-06-05 18:47:24.058 ERROR 17804 --- [-Create-6910184] com.alibaba.druid.pool.DruidDataSource   : create connection SQLException, url: jdbc:mysql://localhost:3306/master, errorCode 0, state 01S00

    java.sql.SQLException: The server time zone value '�й���׼ʱ��' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
       

    四、配置数据源

    @Configuration
    public class DataSourceConfig {

    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.write")
    @Primary
    public DataSource masterDataSource() {
    return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.druid.read")
    public DataSource slaveDataSource() {
    return DruidDataSourceBuilder.create().build();
    }

    @Inject
    @Named("masterDataSource")
    private DataSource masterDataSource;

    @Inject
    @Named("slaveDataSource")
    private DataSource slaveDataSource;

    /**
    * 根据数据源创建SqlSessionFactory
    */
    @Bean
    public SqlSessionFactory sqlSessionFactory(DynamicDataSource dataSource) throws Exception {
    SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
    sessionFactory.setDataSource(dataSource);
    return sessionFactory.getObject();
    }
    }

    SqlSessionFactory必需要重新创建,若不创建会报循环调用异常

    Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'masterDataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?

    因为SqlSessionFactory还是走默认创建的方式 。

    上下文中如何得知使用那个数据源,可使用ThreadLocal来处理。

    五、数据源路由


    public class DataSourceContextRouting implements AutoCloseable {

    static final ThreadLocal<String> dataSourceKeyThreadLocal = new ThreadLocal<>();

    public String getDataSourceName(){
    String key = dataSourceKeyThreadLocal.get();
    return StringUtils.isBlank(key) ?"masterDataSource":key;
    }

    public DataSourceContextRouting(String key){
    dataSourceKeyThreadLocal.set(key);
    }

    @Override
    public void close() throws Exception {
    dataSourceKeyThreadLocal.remove();
    }
    }

    spring的提供动态源实现功能。只需要继承AbstractRoutingDataSource,并重写protected Object determineCurrentLookupKey()

    public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
    return DataSourceContextRouting.getDataSourceName();
    }
    }

     //此为核心代码

    @Bean

    public DynamicDataSource dataSource() {
    Map<Object, Object> targetDataSources = new HashMap<>();
    targetDataSources.put("masterDataSource", masterDataSource);
    targetDataSources.put("slaveDataSource", slaveDataSource);

    DynamicDataSource dataSource = new DynamicDataSource();
    //设置数据源映射
    dataSource.setTargetDataSources(targetDataSources);
    //设置默认数据源,当无法映射到数据源时会使用默认数据源
    dataSource.setDefaultTargetDataSource(slaveDataSource);
    dataSource.afterPropertiesSet();
    return dataSource;
    }

     六、controller路由切换

     @RequestMapping("master")
    public String master(String account){
    String key = "masterDataSource";
    new DataSourceContextRouting(key);
    //TODO .....
    }
    @RequestMapping("slave")
    public String slave(String account){
    String key = "slaveDataSource";
    new DataSourceContextRouting(key);
          //TODO......
    }

     到此为止,整个多数据源配置完成了。

    但这种对代码侵入比较多,可以使用注解的方式来处理。先定义注解标识

    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TargetDataSource {
    String value();
    }

     使用注解那需要对此进行解析切入,因此就需要用上spring AOP的功能。

     首先添加maven依赖

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>

     然后添加对其解析Aspect

    @Aspect
    @Named
    public class DataSourceRoutingAspect {
    @Around("@annotation(targetDataSource)")
    public Object routingWithDataSource(ProceedingJoinPoint joinPoint, TargetDataSource targetDataSource) throws Throwable {
    String key = targetDataSource.value();
    try (DataSourceContextRouting ctx = new DataSourceContextRouting(key)) {
    return joinPoint.proceed();
    }
    }
    }

     @RequestMapping("master")
    @TargetDataSource("masterDataSource")
    public String master(String account){
    TODO:.....
    }
    @RequestMapping("slave")
    @TargetDataSource("slaveDataSource")
    public String slave(String account){
    TODO:.....
    }

     
  • 相关阅读:
    Android开发技术周报 Issue#43
    Android开发技术周报 Issue#44
    Android开发技术周报 Issue#45
    Android开发技术周报 Issue#46
    Android开发技术周报 Issue#48
    Android开发技术周报 Issue#47
    Android开发技术周报 Issue#49
    Android开发技术周报 Issue#50
    Android开发技术周报 Issue#51
    angularjs数据交互
  • 原文地址:https://www.cnblogs.com/song27/p/10977241.html
Copyright © 2011-2022 走看看