【sping揭秘】21、Spring动态数据源的切换

对于多个数据源的时候,我们如何切换不同的数据源进行数据库的操作呢?

当然我们可以直接定义2个DataSource,然后在每次获取connection的时候,从不同的DataSource中获取connection,类似如下

这种情况可以是2个数据库存放的数据性质是不同的,DataSource1存放1种数据,DataSource2存放另一种数据,每个数据库承担不同的数据访问请求,这2个是完全相互独立不相干的

这种就比较简单,那就是直接定义不同的jdbctemplate,设置不同的DataSource就可以了,但是要注意代码编写的时候入库操作

还有一种就是数据性质是一样的,不同的数据源失去了独立自主的地位,这样所有的数据访问我们需要通过“盟主”进行。

这里我们可以借助spring的AbstractRoutingDataSource进行分发

后面几个数据库之间的数据共享,我们可以进行数据库数据的主从复制

定义数据源

package cn.cutter.start.database;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

/**
 * 自定义DataSource
 * @author xiaof
 *
 */
@Component
public class LiferayDataSource1 implements FactoryBean<DataSource> {

    @Override
    public DataSource getObject() throws Exception {
        BasicDataSource dataSource = new BasicDataSource();

        //设置相应的参数
        //1、数据库驱动类
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        //2、url,用户名,密码
        dataSource.setUrl("jdbc:mysql://localhost:3306/liferay?characterEncoding=utf-8");
        dataSource.setUsername("liferay"); dataSource.setPassword("xiaofeng2017");
        //3、初始化连接大小
        dataSource.setInitialSize(1);
        //4、连接池最大数据量
        dataSource.setMaxTotal(500);
        //5、连接池最大小空闲
        dataSource.setMinIdle(1);
        dataSource.setMaxIdle(20);
        //6、最大等待时间 单位毫秒
        dataSource.setMaxWaitMillis(20 * 1000);
        //7、指明连接是否被空闲连接回收器(如果有)进行检验
        dataSource.setPoolPreparedStatements(true);
        //8、运行一次空闲连接回收器的时间间隔(60秒)
        dataSource.setTimeBetweenEvictionRunsMillis(60 * 1000);
        //9、验证时使用的SQL语句
        dataSource.setValidationQuery("SELECT 1 FROM DUAL");
        //10、借出连接时不要测试,否则很影响性能
        //11、申请连接的时候检测,如果空闲时间大于  timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效
        dataSource.setTestWhileIdle(false);

        return dataSource;
    }

    @Override
    public Class<?> getObjectType() {
        // TODO Auto-generated method stub
        return DataSource.class;
    }

}
package cn.cutter.start.database;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.stereotype.Component;

@Component
public class LiferayDataSource2 implements FactoryBean<DataSource> {

    @Override
    public DataSource getObject() throws Exception {
        BasicDataSource dataSource = new BasicDataSource();

        //设置相应的参数
        //1、数据库驱动类
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        //2、url,用户名,密码
        dataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8");
        dataSource.setUsername("liferay"); dataSource.setPassword("xiaofeng2017");
        //3、初始化连接大小
        dataSource.setInitialSize(1);
        //4、连接池最大数据量
        dataSource.setMaxTotal(500);
        //5、连接池最大小空闲
        dataSource.setMinIdle(1);
        dataSource.setMaxIdle(20);
        //6、最大等待时间 单位毫秒
        dataSource.setMaxWaitMillis(20 * 1000);
        //7、指明连接是否被空闲连接回收器(如果有)进行检验
        dataSource.setPoolPreparedStatements(true);
        //8、运行一次空闲连接回收器的时间间隔(60秒)
        dataSource.setTimeBetweenEvictionRunsMillis(60 * 1000);
        //9、验证时使用的SQL语句
        dataSource.setValidationQuery("SELECT 1 FROM DUAL");
        //10、借出连接时不要测试,否则很影响性能
        //11、申请连接的时候检测,如果空闲时间大于  timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效
        dataSource.setTestWhileIdle(false);

        return dataSource;
    }

    @Override
    public Class<?> getObjectType() {
        // TODO Auto-generated method stub
        return DataSource.class;
    }

}
package cn.cutter.start.database;

import java.util.HashMap;

import javax.annotation.PostConstruct;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

/**
 * 集合所有的数据源
 * @author xiaof
 *
 */
@Component
public class DataSources extends HashMap<Integer, DataSource> {

    @Autowired
    @Qualifier("liferayDataSource1")
    private DataSource liferayDataSource1;

    @Autowired
    @Qualifier("liferayDataSource2")
    private DataSource liferayDataSource2;

    @PostConstruct //创建对象之前进行注入
    public void initDataSource() {
        this.put(0, liferayDataSource1);
        this.put(1, liferayDataSource2);
    }

}

动态路由类设置

package cn.cutter.start.database;

import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.annotation.Resource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;

/**
 * spring 中提供多数据源 高可用支持,合纵连横 多数据源
 * @author xiaof
 *
 */
@Component("multipleDataSource")
public class MultipleDataSource extends AbstractRoutingDataSource {

    private static final Log logger = LogFactory.getLog(MultipleDataSource.class);

    private Lock lock = new ReentrantLock();
    private int counter = 0;
    private int dataSourceNumber = 2;

    //对这个类的继承过来的对象进行分化
    //1、注入defaultTargetDataSource
    @Resource(name="liferayDataSource1")
    public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
        // 对于父类私有成员,就只能通过super访问
        super.setDefaultTargetDataSource(defaultTargetDataSource);
    }

    //2、注入所有数据源对象map  targetDataSources
    @Resource(name="dataSources")
    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        super.setTargetDataSources(targetDataSources);
    }

    @Override
    protected Object determineCurrentLookupKey() {

        lock.lock();

        try {

            ++counter;
            //根据取余来进行取数对应的数据库源, 可以自定义自己的规则 来判断如何切换数据库
            int lookupKey = counter % getDataSourceNumber();
            logger.info("获取第:" + lookupKey + " 数据源");
            return new Integer(lookupKey);

        } finally {
            lock.unlock();
        }
    }

    public Lock getLock() {
        return lock;
    }

    public void setLock(Lock lock) {
        this.lock = lock;
    }

    public int getCounter() {
        return counter;
    }

    public void setCounter(int counter) {
        this.counter = counter;
    }

    public int getDataSourceNumber() {
        return dataSourceNumber;
    }

    public void setDataSourceNumber(int dataSourceNumber) {
        this.dataSourceNumber = dataSourceNumber;
    }
}

最后设置我们的动态jdbctemplate

package cn.cutter.start.database;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

/**
 * 多数据源切换,jdbctemplate使用
 * @author xiaof
 *
 */
@Component
public class MultipleJdbcTemplate implements FactoryBean<JdbcTemplate>{

    @Autowired
    @Qualifier("multipleDataSource")
    private MultipleDataSource multipleDataSource;

    @Override
    public JdbcTemplate getObject() throws Exception {

        JdbcTemplate jdbcTemplate = new JdbcTemplate(multipleDataSource);
        return jdbcTemplate;
    }

    @Override
    public Class<?> getObjectType() {
        // TODO Auto-generated method stub
        return JdbcTemplate.class;
    }

}

读取数据

package spring.vo;

public class DataVo {
    private int num;
    private String name = "cutter_point";
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

}

测试

@Test
    public void testMultipleDataSource() {
        ApplicationContext ctx = this.before();

        //循环向数据库插入数据
        String sql = "insert into multipleDataSourceTestTable values (?, ?)";

        for(int i = 0; i < 10; ++i) {
            //获取相应的数据连接 模拟项目中不同的业务场景获取jdbctemplate对象
            JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("multipleJdbcTemplate");
            DataVo dataVo = new DataVo();
            dataVo.setNum(i);
            jdbcTemplate.update(sql, new PreparedStatementSetter() {

                @Override
                public void setValues(PreparedStatement ps) throws SQLException {
                    ps.setInt(1, dataVo.getNum());
                    ps.setString(2, dataVo.getName());
                }
            });
        }

//        List<DataVo> datas = new ArrayList<DataVo>();
//        for(int i = 0; i < 10; ++i) {
//            DataVo dataVo = new DataVo();
//            dataVo.setNum(i);
//            datas.add(dataVo);
//        }
//
//        jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
//
//            @Override
//            public void setValues(PreparedStatement ps, int i) throws SQLException {
//                DataVo dataVo = datas.get(i);
//                ps.setInt(1, dataVo.getNum());
//                ps.setString(2, dataVo.getName());
//            }
//
//            @Override
//            public int getBatchSize() {
//                return datas.size();
//            }
//        });

    }

原文地址:https://www.cnblogs.com/cutter-point/p/9147805.html

时间: 2024-11-02 15:09:02

【sping揭秘】21、Spring动态数据源的切换的相关文章

Spring动态数据源实现读写分离

一.创建基于ThreadLocal的动态数据源容器,保证数据源的线程安全性 package com.bounter.mybatis.extension; /** * 基于ThreadLocal实现的动态数据源容器,保证DynamicDataSource的线程安全性 * @author simon * */ public class DynamicDataSourceHolder { private static final ThreadLocal<String> dataSourceHolde

spring动态数据源+事务

今天在尝试配置spring的动态数据源和事务管理的时候,遇到了几处配置上的问题,在此记录下: 1.使用了spring的aop思想,实现了动态数据源的切换. 2.spring的事务管理,是基于数据源的,也就是说Transaction是基于SessionFactory的, 所以如果要实现动态数据源切换,而且在同一个数据源中保证事务是起作用的话,就需要注意二者的顺序问题,即:在事务起作用之前就要把数据源切换回来. 3.application-mvc.xml文件扫描时不需要扫描services,避免事务

spring 动态数据源

1.动态数据源:  在一个项目中,有时候需要用到多个数据库,比如读写分离,数据库的分布式存储等等,这时我们要在项目中配置多个数据库. 2.原理:   (1).spring 单数据源获取数据连接过程: DataSource --> SessionFactory --> Session  DataSouce   实现javax.sql.DateSource接口的数据源,  DataSource  注入SessionFactory, 从sessionFactory 获取 Session,实现数据库的

dubbo服务+spring动态数据源出错

1:问题描述,以及分析 项目用了spring数据源动态切换,服务用的是dubbo.在运行一段时间后程序异常,更新操作没有切换到主库上. 这个问题在先调用读操作后再调用写操作会出现. 经日志分析原因: 第一:当程序运行一段时间后调用duboo服务时..([DubboServerHandler-192.168.1.106:20880-thread-199] [DubboServerHandler-192.168.1.106:20880-thread-200]) dubbo服务默认最大200线程(超过

AbstractRoutingDataSource+AOP+JNDI实现spring动态数据源

参考:https://www.cnblogs.com/wyb628/p/7240061.html 背景: 系统已有数据源1(主要数据源),数据源2(只有一个目录的xml使用该数据源),由于这2个数据源分别扫描不同的包,相互不打扰,所以一直用的好好的. 直到,需要新增一个数据源3,跟数据源2用法一模一样的,但是需要在程序中具体用到的时候才能决定具体使用哪一个.所以,基于此,针对数据源2和3实现了动态数据源. 思路: sessionFactory的dataSource属性设置成能从代码中动态读取,继

@Transactional导致无法动态数据源切换

公司目前数据源为主从模式:主库可读写,从库只负责读.使用spring-jdbc提供的AbstractRoutingDataSource结合ThreadLocal存储key,实现数据源动态切换. 最近项目加入数据源切换后,偶尔会报出read-only异常,百思不得其解...... <!--数据源--> <bean id="dsCrm" class="cn.mwee.framework.commons.utils.datasource.RoutingDataSo

Spring Boot:实现MyBatis动态数据源

综合概述 在很多具体应用场景中,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库.又比如业务A要访问A数据库,业务B要访问B数据库等,都可以使用动态数据源方案进行解决.接下来,我们就来讲解如何实现动态数据源,以及在过程中剖析动态数据源背后的实现原理. 实现案例 本教程案例基于 Spring Boot + Mybatis + MySQL 实现. 生成项目模板 为方便我们初始化项目,Spring Boot给我们提供一个项目模板生成网站. 1.  打开浏

spring动态切换数据源(一)

介绍下spring数据源连接的源码类:| 1 spring动态切换连接池需要类AbstractRoutingDataSource的源码 2 /* 3 * Copyright 2002-2017 the original author or authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in comp

spring AbstractRoutingDataSource实现动态数据源切换

使用Spring 提供的 AbstractRoutingDataSource 实现 创建 AbstractRoutingDataSource 实现类,负责保存所有数据源与切换数据源策略:public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.ge