Spring Boot HikariCP 一 ——集成多数据源

其实这里介绍的东西主要是参考的另外一篇文章,数据库读写分离的。

参考文章就把链接贴出来,里面有那位的代码,简单明了https://gitee.com/comven/dynamic-datasource-demo?spm=5176.100239.blogcont188540.13.iARYDh。

 

读写分离的功能我已经使用replication集成好了,因为我们需要单独设置每个数据源的链接属性,而且使用的还是Hikari数据源,所以又在网上找了两天,最终昨天晚上发现了这种方式。 
我这里说说自己集成的时候的一些注意点。

配置文件:

hikari:
  master:
    jdbc-url: jdbc:mysql://masterhost:3306/testdb?useUnicode=true&characterEncoding=utf8&useSSL=true&allowMultiQueries=true&verifyServerCertificate=false
    username: root
    password: root
    maximum-pool-size: 20
    pool-name: master
    connection-timeout: 30000
    idle-timeout: 600000
    max-lifetime: 1765000
  slave:
    jdbc-url: jdbc:mysql://slavehost:3306/testdb?useUnicode=true&characterEncoding=utf8&useSSL=true&allowMultiQueries=true&verifyServerCertificate=false
    username: root
    password: root
    maximum-pool-size: 80
    pool-name: slave
    connection-timeout: 30000
    idle-timeout: 600000
    max-lifetime: 1765000
    read-only: true

我这里主要用到的是maximum-pool-size这个值,一般情况下读数据库(slave)总会比写(master)要多一些,而且往往是一个master多个slave。所以,maximum-pool-size这个值在master的设置小于slave比较高效。

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author Created by pangkunkun on 2017/12/18.
 */
@Component
@ConfigurationProperties(prefix = "hikari")
public class DBProperties {
    private HikariDataSource master;
    private HikariDataSource slave;

    public HikariDataSource getMaster() {
        return master;
    }

    public void setMaster(HikariDataSource master) {
        this.master = master;
    }

    public HikariDataSource getSlave() {
        return slave;
    }

    public void setSlave(HikariDataSource slave) {
        this.slave = slave;
    }
}

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Created by pangkunkun on 2017/12/18.
 */
@Configuration
@EnableScheduling
public class DataSourceConfig {

    @Autowired
    private DBProperties properties;

    @Bean(name = "dataSource")
    public DataSource dataSource() {
        //按照目标数据源名称和目标数据源对象的映射存放在Map中
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put("master", properties.getMaster());
        targetDataSources.put("slave", properties.getSlave());
        //采用是想AbstractRoutingDataSource的对象包装多数据源
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        //设置默认的数据源,当拿不到数据源时,使用此配置
        dataSource.setDefaultTargetDataSource(properties.getMaster());
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager txManager() {
        return new DataSourceTransactionManager(dataSource());
    }
}

import com.easyar.cloud.cms.common.util.TargetDataSource;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author Created by pangkunkun on 2017/12/18.
 */
@Component
@Aspect
public class DataSourceAspect {


    private final static Logger log= LoggerFactory.getLogger(DataSourceAspect.class);

    //切换放在mapper接口的方法上,所以这里要配置AOP切面的切入点
    @Pointcut("execution( * com.easyar.cloud.cms.dao.mapper.*.*(..))||execution( * com.easyar.cloud.cms.shiro.mapper.*.*(..))")
    public void dataSourcePointCut() {
    }

    @Before("dataSourcePointCut()")
    public void before(JoinPoint joinPoint) {
        Object target = joinPoint.getTarget();
        String method = joinPoint.getSignature().getName();
        Class<?>[] clazz = target.getClass().getInterfaces();
        Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
        try {
            Method m = clazz[0].getMethod(method, parameterTypes);
            //如果方法上存在切换数据源的注解,则根据注解内容进行数据源切换
            if (m != null && m.isAnnotationPresent(TargetDataSource.class)) {
                TargetDataSource data = m.getAnnotation(TargetDataSource.class);
                String dataSourceName = data.value();
                DynamicDataSourceHolder.putDataSource(dataSourceName);
                log.debug("current thread " + Thread.currentThread().getName() + " add " + dataSourceName + " to ThreadLocal");
            } else {
                log.debug("switch datasource fail,use default");
            }
        } catch (Exception e) {
            log.error("current thread " + Thread.currentThread().getName() + " add data to ThreadLocal error", e);
        }
    }

    //执行完切面后,将线程共享中的数据源名称清空
    @After("dataSourcePointCut()")
    public void after(JoinPoint joinPoint){
        DynamicDataSourceHolder.removeDataSource();
    }

}

上面这些代码的结合充分的解决了在看其他文章的时候对数据源引入的部分疑问。 
DBProperties这里直接将配置文件中的配置参数通过spring的注解读取映射成HikariDataSource,在DataSourceConfig 将其加载。

文章中@TargetDataSource的注解是在mapper方法上,但是事物注解@Transactional一般都是在serviceImpl上,当@Transactional先于@TargetDataSource生效的时候根据自己的设置往往会找到默认的数据源(我设置的是master)。

后来仔细想了下,将@Transactional注解由原来的类注解改为具体到方法上,而且一般用到@Transactional的地方都是增删改,而且只要涉及到增删改的请求(即使可能附带查询)都是要使用master数据源的。所以,最终我们将@Transactional注解指定到具体某个增删改的方法上,@TargetDataSource依然是在mapper的方法中。

这里写的东西不多,具体的还是参考人家的原文吧,如果有啥问题可以给我留言大家一起探讨。
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值