Java架构技术进阶Java

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

2020-04-13  本文已影响0人  4d11ff5df74e
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

上一篇下一篇

猜你喜欢

热点阅读