Java Spring實現多數據源的動態切換

    在實際的項目開發過程中我們經常會遇到一個項目需要使用多個數據源的情況,而多數據源又可分爲固定多數據源和動態多數據源兩種情況。

    固定多數據源是指在項目中需要使用多個數據源,但數據源的個數是確定的,不會改變,如我們的項目需要使用訂單庫和商品庫這兩個數據源,項目中所有的業務邏輯都只需要操作這兩個庫。動態多數據源是指在項目需要使用多數據源,但是數據源的個數不確定,可能會隨着項目的需要動態的新增或刪除數據源。下面我將會對這兩中情況分別說明如何通過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;
    }
 }

通過以上步驟即可實現動態多數據源的動態切換


由於篇幅有限完整的源碼信息可關注微信公衆號 布衣暖 回覆java獲取

buyinuan.jpg














發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章