blog

AbstractRoutingDataSourceを使って動的データソースを実装するSpring

デフォルトのデータソースを作成します。\n<bean id="" class="" init-method="init" d...

Feb 26, 2020 · 6 min. read
シェア


  • デフォルトのデータソースの作成

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
 <property name="driverClassName" value="${jdbc.driver}"/> 
 <property name="url" value="${jdbc.url}"/> 
 <property name="username" value="${jdbc.username}"/> 
 <property name="password" value="${jdbc.password}"/>
 <property ...../>
</bean>
  • AbstractRoutingDataSource を継承したカスタムデータソースルーティングクラスです。
<bean id="multiRouteDataSource" class="com.*.MultiRouteDataSource" > 
 <-- デフォルトのデータソースを初期化する>
 <property name="targetDataSources"> 
 <map>
 <entry key="defaultTargetDataSource" value-ref="dataSource" ></entry>
 </map>
 <property name="defaultTargetDataSource" ref="dataSource" />
</bean>
  • sqlSessionFactoryの作成
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<-- カスタムのmultiRouteDataSourceを導入する。>
 <property name="dataSource" ref="multiRouteDataSource"/>
 <property name="configLocation" value="classpath:mybatis-config.xml"/>
 <property name="mapperLocations" value="classpath*:**/xml/**/*Mapper.xml"/>
</bean>
  • jdbcTemplateの作成
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 
		<-- カスタムのmultiRouteDataSourceを導入する。>
 <property name="dataSource" ref="multiRouteDataSource"></property>
</bean>
  • マルチデータソーススイッチング実装クラス
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.stat.DruidDataSourceStatManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 複数データソース切り替え実装
 */
public class MultiRouteDataSource extends AbstractRoutingDataSource {
 private static final Logger logger = LoggerFactory.getLogger(MultiRouteDataSource.class);
 private Map<Object, Object> targetDataSources;
 /**
 * key : areaCode
 * value : dataSourceName
 */
 private static final Map <String,String> dbNameMapping = new ConcurrentHashMap <>();
 /**
 * データ・ソース名を返すことで複数のデータ・ソースを動的に切り替える
 * @return
 */
 @Override
 protected Object determineCurrentLookupKey() {
 String areaCode = AreaCodeHolder.getAreaCode();
 String dataSourceName = null;
 if(areaCode == null){
 dataSourceName = "defaultTargetDataSource";
 }else{
 dataSourceName = getDataSourceName(areaCode);
 }
 logger.debug("areaCodeによると:[{}]データ・ソースに切り替える:[{}]",areaCode,dataSourceName);
 return dataSourceName;
 }
 @Override
 public void setTargetDataSources(Map<Object, Object> targetDataSources) {
 super.setTargetDataSources(targetDataSources);
 this.targetDataSources = targetDataSources;
 }
 /**
 * データソースを作成する
 * @param dataSourceList データベース接続情報を持つデータソースのリスト
 * @return
 */
 protected boolean createDataSource(List <Map <String,Object>> dataSourceList ) {
 try{
 Map<Object, Object> targetDataSources = this.targetDataSources;
 //データ・ソースのリストを繰り返し、データ・ソースを作成し、データ・ソースのリストに追加する(AbstractRoutingDataSource.targetDataSources)  
 for(Map<String,Object> map : dataSourceList){
 //カスタム・エリアコードとカスタム・データソース名
 String areaCode = map.get("areaCode").toString();
 String dataSourceName = map.get("dataSourceName").toString();
 DruidDataSource druid = new DruidDataSource();
 for(String key : map.keySet()){
 //ここで、私は怠け者だ、キーの前に良いマップとドルイドセットメソッドを一貫して設定する必要があり、その後、直接変換するツールクラスを使用する。
 org.apache.commons.beanutils.BeanUtils.setProperty(druid,key,map.get(key));
 }
 druid.init();
 targetDataSources.put(dataSourceName, druid);
 //areaCodeとdataSourceNameのマッピング関係を維持し、areaCodeに従ってデータベースをカットする。
 dbNameMapping.put(areaCode,dataSourceName);
 }
 //TargetDataSourcesを割り当てる
 setTargetDataSources(targetDataSources);
 // TargetDataSourcesの接続情報をresolvedDataSourcesの管理に入れる。
 super.afterPropertiesSet();
 return true;
 }catch (Exception e){
 logger.error("createDataSource {}",e);
 }
 return false;
 }
 /**
 *  
 * データ・ソースを削除する
 * @param datasourceid データ・ソース一意識別子
 * @return
 */
 protected boolean deleteDatasource(String datasourceid) {
 Map<Object, Object> targetDataSources = this.targetDataSources;
 if (targetDataSources.containsKey(datasourceid)) {
 Set <DruidDataSource> druidDataSourceInstances = DruidDataSourceStatManager.getDruidDataSourceInstances();
 for (DruidDataSource druid : druidDataSourceInstances) {
 if (datasourceid.equals(druid.getName())) {
 targetDataSources.remove(datasourceid);
 DruidDataSourceStatManager.removeDataSource(druid);
 setTargetDataSources(targetDataSources);
 super.afterPropertiesSet();
 return true;
 }
 }
 return false;
 } else {
 return false;
 }
 }
 private String getDataSourceName(String areaCode){
 String dataSourceName = dbNameMapping.get(areaCode);
 if(dataSourceName == null){
 throw new RuntimeException("areaCodeによると:"+areaCode +"データベース接続を照会できない!");
 }
 return dataSourceName;
 }
}
/**
 * 現在のリクエスト・ユーザーの地域コードを保存する
 */
public final class AreaCodeHolder {
 private static final ThreadLocal<String> local = new ThreadLocal <>();
 public static void setAreaCode(String var){
 local.set(var);
 }
 public static String getAreaCode(){
 return local.get();
 }
}
  • スプリングがロードされた後、データソースのリストをインスタンス化するためにこのメソッドを呼び出すようにリスナーを設定します。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * IOCコンテナの起動処理中、すべてのBeanが処理されると、spring iocコンテナはイベントを発行するアクションを持つことになる。
 */
@Component
public class DataSourceInitListener implements ApplicationListener<ContextRefreshedEvent> {
 private static final Logger logger = LoggerFactory.getLogger(DataSourceInitListener.class);
 @Override
 public void onApplicationEvent(ContextRefreshedEvent event) {
 long time = System.currentTimeMillis();
 boolean bool = false;
 try{
 logger.info("データ・ソースの初期化を開始する...");
 MultiRouteDataSource route = event.getApplicationContext().getBean(MultiRouteDataSource.class);
 List <Map <String,Object>> datasourcelist = this.getDataSourceList();
 bool = route.createDataSource(datasourcelist);
 }catch (Exception e){
 logger.error("データ・ソース例外の初期化:{}",e);
 }
 logger.info("データソースの初期化結果:{}  :{}  ,bool,(System.currentTimeMillis()-time));
 }
 
<!-- データ・ソースの取得をシミュレートする,
 例:Configuration Centre経由で取得する,
 リモート・インターフェースを呼び出して,
 デフォルトのデータベースに照会して -- を得る>
 public List <Map <String,Object>> getDataSourceList(){
 List <Map <String,Object>> dataSourceList = new ArrayList <>();
 {
 Map <String,Object> map = new HashMap <>();
 map.put("areaCode","0");
 map.put("dataSourceName","dataSource_1");
 map.put("username","username");
 map.put("password","password");
 map.put("url","jdbcUrl");
 map.put("driverClassName","driverClassName");
 dataSourceList.add(map);
 }
 {
 Map <String,Object> map = new HashMap <>();
 map.put("areaCode","1");
 map.put("dataSourceName","dataSource_2");
 map.put("username","username");
 map.put("password","password");
 map.put("url","jdbcUrl");
 map.put("driverClassName","driverClassName");
 dataSourceList.add(map);
 }
 return dataSourceList;
 }
}
  • AreaCodeHolderの情報を保存

  • 例えば、ユーザが一度ログインしたときに、ユーザの地域コードをAreaCodeHolderに保存するフィルタをカスタマイズします。

  • 私のプロジェクトではduboを使用しており、duboの暗黙的なpassパラメータを使用してコンシューマサイドからサーバサイドに情報を渡しています。duboのRpcContext.getContext()はすでにThreadLocalにあるので、AreaCodeHolderを使う必要はありません。

コンシューマー
 RpcContext.getContext().setAttachment("areaCode" );
プロデューサー
 RpcContext.getContext().getAttachment("areaCode" );
Read next

SDN レイヤーレスネットワークと TCP/IP レイヤーネットワークの比較

SDN はフォワーディングという意味ではレイヤーレスです。つまり、フォワーディングロジックはもはやパケットを階層的に解析しませんし、フォワーディングデバイスはもはや階層レベルを区別しません。

Feb 26, 2020 · 3 min read