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

Posted cutter_point

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了sping揭秘21Spring动态数据源的切换相关的知识,希望对你有一定的参考价值。

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

 

 

当然我们可以直接定义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();
//            }
//        });
        
    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

以上是关于sping揭秘21Spring动态数据源的切换的主要内容,如果未能解决你的问题,请参考以下文章

sping揭秘3Spring容器中bean默认是保持一个实例

sping揭秘20spring的orm

sping揭秘25Spring远程方案

sping揭秘2关于spring配置文件

sping揭秘23Spring框架内的JNDI支持

sping揭秘4某些无法注册到IOC容器的对象如何交给spring托管