前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring多数据源实现

spring多数据源实现

作者头像
lyb-geek
发布2018-07-26 10:42:26
7970
发布2018-07-26 10:42:26
举报
文章被收录于专栏:Linyb极客之路Linyb极客之路

多数据源应用场景

1、读写分离 2、分库存储

Spring多数据源管理实现

实现数据源切换的功能就是自定义一个类扩展AbstractRoutingDataSource抽象类,其实该相当于数据源DataSourcer的路由中介,可以实现当项目运行时根据相应key值切换到对应的数据源DataSource上

AbstractRoutingDataSource源码分析

代码语言:javascript
复制
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

    private Map<Object, Object> targetDataSources;

    private Object defaultTargetDataSource;

    private boolean lenientFallback = true;

    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

    private Map<Object, DataSource> resolvedDataSources;

    private DataSource resolvedDefaultDataSource;

通过源码可以看出该类是一个抽象类,定义了6个属性。 targetDataSources:是一个map类型该属性正是用来维护项目中多个数据源 defaultTargetDataSource:通过属性名很直观的可以理解它的作用(默认数据源) lenientFallback:默认为true,无需改动 dataSourceLookup:查找数据源接口的名称 resolvedDataSources:改变后的数据源 resolvedDefaultDataSource:如果该字段没有赋值,就是targetDataSources

获取数据源的本质其实就是获取数据源的连接,我们看下该类获取连接的具体代码片段

代码语言:javascript
复制
@Override
    public Connection getConnection() throws SQLException {
        return determineTargetDataSource().getConnection();
    }

直接进入determineTargetDataSource方法

代码语言:javascript
复制
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;
    }

由该代码片段我们可以很直观的发现,这个方法的作用就是用来实现查找目标数据源,通过代码我们可以查找数据源是根据determineCurrentLookupKey()这个方法。因此我们只需重写determineCurrentLookupKey()方法即可

代码实现

1.动态配置多数据源

创建设置上下文环境的类(DataSourceContextHolder),主要负责改变上下文数据源的名称。通过ThreadLocal类使每个线程获取独立的数据源,防止并发访问时获取错误的数据源

代码语言:javascript
复制
public class DataSourceContextHolder {

    @SuppressWarnings("rawtypes")
    private static final ThreadLocal contextHolder = new ThreadLocal();

    @SuppressWarnings("unchecked")
    public static void setDataSourceType(String dataSourceType) {
        contextHolder.set(dataSourceType);
    }

    public static String getDataSourceType() {
        return (String) contextHolder.get();
    }

    public static void clearDataSourceType() {
        contextHolder.remove();
    }

}

2.建立动态数据源类

实现AbstractRoutingDataSource重写determineCurrentLookupKey。该方法返回一个Object,一般是返回字符串

代码语言:javascript
复制
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        // TODO Auto-generated method stub
        return DataSourceContextHolder.getDataSourceType();
    }

}

3.spring配置文件配置多个数据源

代码语言:javascript
复制
<bean id="masterDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
        destroy-method="close">
        <property name="driverClassName" value="${master.jdbc.driverClassName}" />
        <property name="url" value="${master.jdbc.url}" />
        <property name="username" value="${master.jdbc.username}" />
        <property name="password" value="${master.jdbc.password}" />


        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${jdbc.initialSize}" />
        <property name="minIdle" value="${jdbc.minIdle}" />
        <property name="maxActive" value="${jdbc.maxActive}" />

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="${jdbc.maxWait}" />

        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />

        <property name="validationQuery" value="${jdbc.validationQuery}" />
        <property name="testWhileIdle" value="${jdbc.testWhileIdle}" />
        <property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
        <property name="testOnReturn" value="${jdbc.testOnReturn}" />

        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.maxPoolPreparedStatementPerConnectionSize}" />

        <!-- 配置监控统计拦截的filters,去掉后监控界面sql无法统计 -->
        <property name="filters" value="${jdbc.filters}" />
    </bean>



    <bean id="slaveDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init"
        destroy-method="close">
        <property name="driverClassName" value="${slave.jdbc.driverClassName}" />
        <property name="url" value="${slave.jdbc.url}" />
        <property name="username" value="${slave.jdbc.username}" />
        <property name="password" value="${slave.jdbc.password}" />


        <!-- 配置初始化大小、最小、最大 -->
        <property name="initialSize" value="${jdbc.initialSize}" />
        <property name="minIdle" value="${jdbc.minIdle}" />
        <property name="maxActive" value="${jdbc.maxActive}" />

        <!-- 配置获取连接等待超时的时间 -->
        <property name="maxWait" value="${jdbc.maxWait}" />

        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />

        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />

        <property name="validationQuery" value="${jdbc.validationQuery}" />
        <property name="testWhileIdle" value="${jdbc.testWhileIdle}" />
        <property name="testOnBorrow" value="${jdbc.testOnBorrow}" />
        <property name="testOnReturn" value="${jdbc.testOnReturn}" />

        <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
        <property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.maxPoolPreparedStatementPerConnectionSize}" />

        <!-- 配置监控统计拦截的filters,去掉后监控界面sql无法统计 -->
        <property name="filters" value="${jdbc.filters}" />
    </bean>


    <bean id="dataSource" class="com.demo.dynamic.helpper.DynamicDataSource">
      <property name="targetDataSources">
           <map key-type="java.lang.String"> 
             <entry key="master" value-ref="masterDataSource"/>     
             <entry key="slave" value-ref="slaveDataSource"/>      
         </map>
      </property>
      <property name="defaultTargetDataSource" ref="masterDataSource"></property>
    </bean>

4.基于自定义注解+aop实现数据源动态切换

自定义注解

代码语言:javascript
复制
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
    String value() default "";
}

定义aop实现类:

代码语言:javascript
复制
@Aspect
@Component
@Order(1)
public class DynamicDataSourceAspect {
    private static Logger logger = org.slf4j.LoggerFactory.getLogger(DynamicDataSourceAspect.class);

    @Before("@annotation(com.demo.dynamic.helpper.DataSource)")
    public void before(JoinPoint point) {
        operatePointCut(point, false);
    }

    @After("@annotation(com.demo.dynamic.helpper.DataSource)")
    public void after(JoinPoint point) {
        operatePointCut(point, true);
    }

    private void operatePointCut(JoinPoint point, boolean isReset) {
        Class<?> target = point.getTarget().getClass();
        MethodSignature signature = (MethodSignature) point.getSignature();
        // 默认使用目标类型的注解,如果没有则使用其实现接口的注解类
        for (Class<?> cls : target.getInterfaces()) {
            operateDataSource(cls, signature.getMethod(), isReset);
        }
        operateDataSource(target, signature.getMethod(), isReset);
    }

    private void operateDataSource(Class<?> cls, Method method, boolean isReset) {
        try {
            Class<?>[] types = method.getParameterTypes();
            if (!isReset) {
                if (cls.isAnnotationPresent(DataSource.class)) {
                    DataSource source = cls.getAnnotation(DataSource.class);
                    DataSourceContextHolder.setDataSourceType(source.value());
                    
                }
            }

            // 方法注解可以覆盖类注解
            Method m = cls.getMethod(method.getName(), types);
            if (m != null && m.isAnnotationPresent(DataSource.class)) {
                DataSource source = m.getAnnotation(DataSource.class);
                if (isReset) {
                    DataSourceContextHolder.clearDataSourceType();
                } else {
                    DataSourceContextHolder.setDataSourceType(source.value());
                }
               
            }

        } catch (Exception e) {
            logger.error(cls + ":" + e.getMessage(), e);
        }
    }
}

开启aop自动代理

代码语言:javascript
复制
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>

多数据源测试

1.测试数据准备

a、master_db t_user

b、slave_db t_user

2.测试service编写

代码语言:javascript
复制
@Service
@Transactional
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @DataSource("master")
    public User getUserMasterById(Long id) {

        return userMapper.selectByPrimaryKey(id);
    }

    @DataSource("slave")
    public User getUserSlaveById(Long id) {

        return userMapper.selectByPrimaryKey(id);
    }



}

3.单元测试

代码语言:javascript
复制
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({ "classpath:spring.xml" })
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void testQuery() {

        User masterUser = userService.getUserMasterById(1L);
        System.out.println(masterUser);

        System.out.println("-------------------------------分割线---------------------------------");

        User slaveUser = userService.getUserSlaveById(1L);
        System.out.println(slaveUser);

    }

}

4.运行结果

代码语言:javascript
复制
User [id=1, userName=zhangsan, password=1, age=10]
-------------------------------分割线---------------------------------
User [id=1, userName=张三, password=zs, age=12]

说明多数据源配置成功

demo地址

https://github.com/lyb-geek/spring-dynamic-db

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-07-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linyb极客之路 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 多数据源应用场景
  • Spring多数据源管理实现
    • AbstractRoutingDataSource源码分析
      • 代码实现
      • 多数据源测试
      • demo地址
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档