美文网首页Java架构技术进阶Java
Java+Spring+MyBatis实现多数据源的动态切换

Java+Spring+MyBatis实现多数据源的动态切换

作者: 4d11ff5df74e | 来源:发表于2020-04-13 17:23 被阅读0次
    timg.jpg

    在实际的项目开发过程中,我们经常会遇到一个项目需要使用多个数据源的情况,而多数据源又可分为固定多数据源和动态多数据源。

    固定多数据源:

    是指在项目中需要使用多个数据源,但数据源的个数是确定的,不会改变,如我们的项目需要使用订单库和商品库这两个数据源,项目中所有的业务逻辑都只需要操作这两个库。

    动态多数据源:

    是指在项目需要使用多数据源,但是数据源的个数不确定,可能会随着项目的需要动态的新增或删除数据源。

    下面我将会对这两种情况分别说明如何通过Java + Spring实现多数据源的动态切换。

    多数据源的动态切换都是通过重载Spring中的AbstractRoutingDataSource类来实现的。

    一、固定多数据源切换

    固定多数据源的动态切换,通过自定义注解实现切换,这样在切换数据源时比较灵活,具体的实现方式如下:

    1、配置多数据源

     <!--定义数据源1-->
        <bean id="oracledataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
            <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
            <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1522:neworcl" />
            <property name="username" value="emspdadev" />
            <property name="password" value="emspdadev" />
            <!-- 初始化连接大小 -->
            <property name="initialSize" value="0"></property>
            <!-- 连接池最大数量 -->
            <property name="maxActive" value="20"></property>
            <!-- 连接池最大空闲 -->
            <property name="maxIdle" value="20"></property>
            <!-- 连接池最小空闲 -->
            <property name="minIdle" value="1"></property>
            <!-- 获取连接最大等待时间 -->
            <property name="maxWait" value="60000"></property>
        </bean>
    
        <!--定义数据源2-->
        <bean id="mysqldataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
            <property name="driverClassName" value="com.mysql.jdbc.Driver" />
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/jbpmdb" />
            <property name="username" value="root" />
            <property name="password" value="123456" />
            <!-- 初始化连接大小 -->
            <property name="initialSize" value="0"></property>
            <!-- 连接池最大数量 -->
            <property name="maxActive" value="20"></property>
            <!-- 连接池最大空闲 -->
            <property name="maxIdle" value="20"></property>
            <!-- 连接池最小空闲 -->
            <property name="minIdle" value="1"></property>
            <!-- 获取连接最大等待时间 -->
            <property name="maxWait" value="60000"></property>
        </bean>
    
        <!--动态数据源配置-->
        <bean id="dataSource" class="com.ssm.datasource.DynamicDataSource">
        <!--引入定义好的数据源-->
            <property  name="targetDataSources">
                <map  key-type="java.lang.String">
                  <entry key="oracle" value-ref="oracledataSource" />
                  <entry key="mysql" value-ref="mysqldataSource" />
                </map>
            </property>
        <!--定义默认数据源-->
            <property name="defaultTargetDataSource" ref="oracledataSource" />
        </bean>
    
        <!--spring和mybatis整合-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="mapperLocations" value="classpath:mapping/*.xml" />
        </bean>
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.ssm.dao" />
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        </bean>
    

    2、定义注解(注解名为DataSource),用于切换数据源,注解的值只能为上述配置中定义的key(对应于上面配置中定义的oracle、mysql)

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface DataSource {
        String value();
    }
    

    3、根据Sping切面编程,当调用指定的切面类时,解释注解,并根据注解的定义使用对应的数据库

    public class DataSourceAspect {
     
        /**
        * 定义切面,当调用com.ssm.service下的所有类的所有方法前都会执行beforeInvoke方法
        */
        @Pointcut("execution(* com.ssm.service.*.*(..))")
        public void pointCut(){};
     
        @Before(value = "pointCut()")
        public void beforeInvoke(JoinPoint joinpoint) {
            try {
                String clazzName = joinpoint.getTarget().getClass().getName();
                String methodName = joinpoint.getSignature().getName();
                Class targetClazz = Class.forName(clazzName);
                Method[] methods = targetClazz.getMethods();
                for(Method method : methods) {
                    if(method.getName().equals(methodName)) {
                        // 首先查看方法是否使用注解
                        // 如果使用注解,则获取注解定义的值,并根据注解的值设置访问数据库的key
                        if(method.isAnnotationPresent(DataSource.class)) {
                            DataSource dataSource = method.getAnnotation(DataSource.class);
                            DatasourceHolder.setDataType(dataSource.value());
                        }
                        break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
     
        }
    }
    

    4、定义动态切换数据源(继承Spring的AbstractRoutingDataSource)

    public class DynamicDataSource extends AbstractRoutingDataSource {
        /**
        * 根据DatasourceHolder中DataType的值获取具体的数据源
        */
        @Override
        protected Object determineCurrentLookupKey() {
            return DatasourceHolder.getDataType();
        }
    }
    

    5、数据源切换的使用

    @Service
    public class IdxServiceImpl implements IIdxSevice {
     
        @Autowired
        private IdxMapper idxMapper;
     
        @Override
        public List<Idx> listIdxInfo() {
            return null;
        }
     
        /**
        * 根据注解的配置,会访问oracle对应的数据源
        */
        @Override
        @DataSource("oracle")
        public Map<String,Object> getIdxById(int idxId) {
            return idxMapper.getIdxById(idxId);
        }
     
        /**
        * 根据注解的配置,会访问mysql对应的数据源
        */
        @Override
        @DataSource("mysql")
        public Map<String, Object> getJobInfo(int dbId) {
            return idxMapper.getJobInfo(dbId);
        }
    }
    

    通过以上的步骤即实现了数据源的动态切换。

    二、动态多数据源切换

    对于动态的多数据源,数据源的配置一般不放在配置文件中,因为如果放在配置文件中,每次新增或删除数据源,都需要重启项目,这样的实现方式非常不友好;通常情况向数据源的配置放在数据库中。实现方式如下:

    1、配置数据源,这里配置的数据源用于保存其他数据源的配置信息,今后数据的新增、删除、修改均在该数据库中操作,配置如下:

    <!--定义数据源-->
        <bean id="oracledataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
            <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
            <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1522:neworcl" />
            <property name="username" value="cfgmanage" />
            <property name="password" value="cfgmanage" />
            <!-- 初始化连接大小 -->
            <property name="initialSize" value="0"></property>
            <!-- 连接池最大数量 -->
            <property name="maxActive" value="20"></property>
            <!-- 连接池最大空闲 -->
            <property name="maxIdle" value="20"></property>
            <!-- 连接池最小空闲 -->
            <property name="minIdle" value="1"></property>
            <!-- 获取连接最大等待时间 -->
            <property name="maxWait" value="60000"></property>
        </bean>
    
        <!--查询动态配置的数据库连接信息-->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="oracledataSource" />
        </bean>
        <bean id="dbConfigService" class="com.teamsun.datasource.DBConfigService">
            <property name="jdbcTemplate" ref="jdbcTemplate" />
        </bean>
    
        <!--定义动态数据源-->
        <bean id="dataSource" class="com.teamsun.datasource.DynamicDataSource">
            <property name="masterDataSource" ref="oracledataSource" />
            <property name="dbConfigService" ref="dbConfigService" />
        </bean>
    
        <!--spring和mybatis整合-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource" />
            <property name="mapperLocations" value="classpath:mapper/*.xml" />
            <!--<property name="mapperLocations" value="classpath:mapping/*.xml" />-->
        </bean>
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="com.teamsun.mapper" />
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        </bean>
    

    2、实现查询数据源配置信息的类

    public class DBConfigService {
     
        private JdbcTemplate jdbcTemplate;
     
        /**
         * 查询数据库配置信息
         * @param dbName  数据库名称
         * @return 数据库配置信息
         */
        public DBCfg getDBCfg(String dbName) throws Exception {
            String querySql = "select\n" +
                    "          t.db_type as \"dbType\",\n" +
                    "           t.db_name as \"dbName\",\n" +
                    "           t.db_comment as \"dbCommment\",\n" +
                    "           t.db_driver as \"driverClass\",\n" +
                    "           t.db_username as \"userName\",\n" +
                    "           t.db_password as \"passworld\",\n" +
                    "           t.db_url as \"jdbcURL\"" +
                    "          from TB_RPT_DBCFG t\n" +
                    "          where t.db_name = '" + dbName + "'";
     
            RowMapper<DBCfg> rowMapper = ParameterizedBeanPropertyRowMapper.newInstance(DBCfg.class);
            DBCfg dbCfg = (DBCfg) jdbcTemplate.queryForObject(querySql, rowMapper);
            return dbCfg;
        }
     
        public JdbcTemplate getJdbcTemplate() {
            return jdbcTemplate;
        }
     
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    

    3、实现动态切换数据源

    /**
     * <p>动态创建及访问多数据源</p>
     */
    public class DynamicDataSource extends AbstractRoutingDataSource{
     
        private DBConfigService dbConfigService;
     
        private DataSource masterDataSource;
     
        private Map<Object, Object> targetDataSource = new HashMap<Object, Object>();
     
        private static final String DEFAULT_DB_NAME = "dataSource";  // 默认数据库名
     
        private static final Logger LOGGER = Logger.getLogger(DynamicDataSource.class);
     
        /**
         * 创建并获取数据源
         * @return
         */
        @Override
        protected DataSource determineTargetDataSource() {
            // 获取数据源名称
            String dbName = (String) determineCurrentLookupKey();
     
            // 获取默认数据源
            if(DEFAULT_DB_NAME.equals(dbName)) {
                return masterDataSource;
            }
     
            // 创建数据源
            DataSource dataSource = (DataSource) targetDataSource.get(dbName);
            try {
                if (dataSource == null) {
                    dataSource = getDataSourceByName(dbName);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return dataSource;
        }
     
        /**
         * 获取数据库名称,可根据获取的数据库名称查询数据库配置信息,
         * 通过配置信息动态创建数据源
         * @return
         */
        @Override
        protected Object determineCurrentLookupKey() {
            String dbName = DatasourceHolder.getDBName();
            if(StringUtils.isEmpty(dbName)) {
                dbName = DEFAULT_DB_NAME;
            }
     
            DatasourceHolder.remove();
            return dbName;
        }
     
        @Override
        public void afterPropertiesSet() {
     
        }
     
        /**
         * 通过数据库的配置信息获取数据源
         * @param dbName 数据库名称
         * @return
         */
        public synchronized DataSource getDataSourceByName(String dbName) throws Exception {
            
            // 创建数据源
            BasicDataSource dataSource = createDataSource(dbName);
            
            // 如果创建数据源成功则缓存数据源,避免重复创建相同的数据源
            if(dataSource != null) {
                targetDataSource.put(dbName, dataSource);
            }
            return  dataSource;
        }
     
        /**
         * 通过数据库的配置创建数据源
         * @param dbName 数据库名称
         * @return
         */
        public BasicDataSource createDataSource(String dbName) throws Exception {
            
            // 查询动态数据源配置信息
            String oriDBName = DatasourceHolder.getDBName();
     
            if(dbConfigService == null) {
                System.out.println("创建数据源失败[dbCfgService is null......]");
                LOGGER.debug("创建数据源失败[dbCfgService is null......]");
            }
     
            // 通过数据库名称查询相关的数据库配置信息
            DatasourceHolder.setDBName(DEFAULT_DB_NAME);
            DBCfg dbCfg = dbConfigService.getDBCfg(dbName);
            DatasourceHolder.setDBName(oriDBName);
     
            String driver = dbCfg.getDriverClass();  // 数据库驱动
            String url = dbCfg.getJdbcURL();  // 数据库连接地址
            String username = dbCfg.getUserName();  // 数据库用户名
            String password = dbCfg.getPassworld();  // 数据库密码
     
            LOGGER.debug("动态连接的数据库为[" + url + "|" + username + "]");
     
            // 创建数据源
            BasicDataSource basicDataSource = new BasicDataSource();
            basicDataSource.setDriverClassName(driver);
            basicDataSource.setUrl(url);
            basicDataSource.setUsername(username);
            basicDataSource.setPassword(password);
            basicDataSource.setTestWhileIdle(true);
     
            return basicDataSource;
        }
     
        /**
         * 如果修改或删除数据源的配置,则需要同步删除缓存的数据源
         * @param dbName
         */
        public void removeDataSource(String dbName) {
            this.targetDataSource.remove(dbName);
        }
     
        public DataSource getMasterDataSource() {
            return masterDataSource;
        }
     
        public void setMasterDataSource(DataSource masterDataSource) {
            this.masterDataSource = masterDataSource;
        }
     
        public DBConfigService getDbConfigService() {
            return dbConfigService;
        }
     
        public void setDbConfigService(DBConfigService dbConfigService) {
            this.dbConfigService = dbConfigService;
        }
    }
    

    4、使用动态切换数据源

    public class ShowRptServiceImpl implements IShowRptService {
     
        private static final Logger LOGGER = Logger.getLogger(ShowRptServiceImpl.class);
     
        @Autowired
        private DBCfgMapper dbCfgMapper;
     
        @Autowired
        private ShowRptInfoMapper showRptInfoMapper;
     
        @Override
        public RptResult queryRptInfo(BaseRpt baseRpt, Map<String, String> params) {
            // 在调用Mybatis执行数据库之前先选择数据源
            DatasourceHolder.setDBName(dbCfg.getDbName());
            // 查询报表数据
            List<Map<String,Object>> resultList = showRptInfoMapper.queryRptData(querySQL);
     
     
            // 选择数据源
            DatasourceHolder.setDBName(dbCfg.getDbName());
            // 查询数据数据量
            int totalCount = showRptInfoMapper.queryTotalCount(countSQL);
     
            RptResult rptResult = new RptResult();
            return rptResult;
        }
     }
    

    通过以上步骤即可实现动态多数据源的动态切换

    原文链接:https://blog.csdn.net/weixin_33853794/article/details/92166242

    相关文章

      网友评论

        本文标题:Java+Spring+MyBatis实现多数据源的动态切换

        本文链接:https://www.haomeiwen.com/subject/kssnmhtx.html