Spring+Mybatisダイナミックにデータソースを切り替える方法
機能の需要は会社が大きな運営プラットフォームを作ることです。
1、運営プラットフォームには独自のデータベースがあり、ユーザー、役割、メニュー、部分及び権限などの基本機能を維持する。
2、運営プラットフォームは他の異なるサービス(サービスA、サービスB)のバックグラウンド運営を提供する必要があり、サービスA、サービスBのデータベースは独立している。
したがって、運営プラットフォームは少なくとも3つのライブラリに接続します。運営ライブラリ、Aライブラリ、Bライブラリ、そして各機能要求に対して自動的に対応するデータソースに切り替えることができるようにしたいです。我々のシステムの機能は相互に独立している)。
第一歩:マルチデータソースの設定
1、データソースを定義する:
私が採用しているデータのソースはアリのドルチェ・ソースです。設定は以下の通りです
2、マルチプレックスDataSourceを配置する
multiple DataSourceは以上の3つのデータソースの1つのプロキシに相当しています。本当にSpring/Mybatisと結合する時multiple DataSourceと単独で構成されたDataSourceは使用していません。
発想はSpringのAOP思想を利用して、毎回のService方法を遮断して呼び出し、そして方法の全体パス名によって、multileDataSourceのデータを動的に切り替えるkeyである。私たちのプロジェクトは、異なるサービスすなわち異なるデータベースの操作に対して、互いに独立しているので、同じサービスで異なるデータソースを呼び出すことはあまり推奨されていません。このようにすると、ダイナミックに切り替えが必要かどうかを判断する頻度をDAOレベル、つまりSQLレベルに置く必要があります。また、事務管理もできません。
ダイナミックスイッチングデータソースのAOP実装を見てきました。
その他:
最初のプロジェクトには事務が導入されていませんので、すべてOKです。毎回正しいデータソースにアクセスできます。SPringの事務管理に加入すると、データソースを動的に切り替えることができなくなりました。
orderが小さいほど、先に実行されます。これにより、データソースを動的に切り換えることができ、また、トランザクション(同一のデータソース)を成功的に使用することができる。
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。
1、運営プラットフォームには独自のデータベースがあり、ユーザー、役割、メニュー、部分及び権限などの基本機能を維持する。
2、運営プラットフォームは他の異なるサービス(サービスA、サービスB)のバックグラウンド運営を提供する必要があり、サービスA、サービスBのデータベースは独立している。
したがって、運営プラットフォームは少なくとも3つのライブラリに接続します。運営ライブラリ、Aライブラリ、Bライブラリ、そして各機能要求に対して自動的に対応するデータソースに切り替えることができるようにしたいです。我々のシステムの機能は相互に独立している)。
第一歩:マルチデータソースの設定
1、データソースを定義する:
私が採用しているデータのソースはアリのドルチェ・ソースです。設定は以下の通りです
<!-- op dataSource -->
<bean id="opDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${db.master.url}" />
<property name="username" value="${db.master.user}" />
<property name="password" value="${db.master.password}" />
<property name="driverClassName" value="${db.master.driver}" />
<property name="initialSize" value="5" />
<property name="maxActive" value="100" />
<property name="minIdle" value="10" />
<property name="maxWait" value="60000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="1800" />
<property name="logAbandoned" value="true" />
<!-- filters -->
<property name="filters" value="config,mergeStat,wall,log4j2" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean>
<!-- serverA dataSource -->
<bean id="serverADataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${db.serverA.master.url}" />
<property name="username" value="${db.serverA.master.user}" />
<property name="password" value="${db.serverA.master.password}" />
<property name="driverClassName" value="${db.serverA.master.driver}" />
<property name="initialSize" value="5" />
<property name="maxActive" value="100" />
<property name="minIdle" value="10" />
<property name="maxWait" value="60000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="1800" />
<property name="logAbandoned" value="true" />
<!-- filters -->
<property name="filters" value="config,mergeStat,wall,log4j2" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean>
<!-- serverB dataSource -->
<bean id="serverBDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${db.serverB.master.url}" />
<property name="username" value="${db.serverB.master.user}" />
<property name="password" value="${db.serverB.master.password}" />
<property name="driverClassName" value="${db.serverB.master.driver}" />
<property name="initialSize" value="5" />
<property name="maxActive" value="100" />
<property name="minIdle" value="10" />
<property name="maxWait" value="60000" />
<property name="validationQuery" value="SELECT 'x'" />
<property name="testOnBorrow" value="false" />
<property name="testOnReturn" value="false" />
<property name="testWhileIdle" value="true" />
<property name="timeBetweenEvictionRunsMillis" value="600000" />
<property name="minEvictableIdleTimeMillis" value="300000" />
<property name="removeAbandoned" value="true" />
<property name="removeAbandonedTimeout" value="1800" />
<property name="logAbandoned" value="true" />
<!-- filters -->
<property name="filters" value="config,mergeStat,wall,log4j2" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean>
私は三つのデータソースを配置しました。oPDatorSource(運営プラットフォーム自体のデータソース)、serverADaSource、serverBRADWA Source。2、マルチプレックスDataSourceを配置する
multiple DataSourceは以上の3つのデータソースの1つのプロキシに相当しています。本当にSpring/Mybatisと結合する時multiple DataSourceと単独で構成されたDataSourceは使用していません。
<!-- Spring Mybatis: multipleDatasource -->
<bean id="sqlSessionFactory"
class="com.baomidou.mybatisplus.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="multipleDataSource" />
<!-- Mapping.xml -->
<property name="mapperLocations">
<list>
<value>classpath*:/sqlMapperXml/*.xml</value>
<value>classpath*:/sqlMapperXml/*/*.xml</value>
</list>
</property>
<property name="configLocation" value="classpath:xml/mybatis-config.xml"></property>
<property name="typeAliasesPackage" value="com.XXX.platform.model" />
<property name="globalConfig" ref="globalConfig" />
<property name="plugins">
<array>
<!-- -->
<bean id="paginationInterceptor"
class="com.baomidou.mybatisplus.plugins.PaginationInterceptor">
<property name="dialectType" value="mysql" />
<property name="optimizeType" value="aliDruid" />
</bean>
</array>
</property>
</bean>
<!-- MyBatis -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- Dao , -->
<property name="basePackage" value="com.XXX.platform.mapper" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
<!-- MP -->
<bean id="globalConfig" class="com.baomidou.mybatisplus.entity.GlobalConfiguration">
<property name="idType" value="0" />
<property name="dbColumnUnderline" value="true" />
</bean>
<!-- multipleDataSource -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="multipleDataSource"></property>
</bean>
multileDataSourceの位置を把握した後、次にmultileDataSourceがどのように実現されるかを重点的に見てみます。配置ファイルは以下の通りです。
<bean id="multipleDataSource" class="com.xxxx.platform.commons.db.MultipleDataSource">
<property name="defaultTargetDataSource" ref="opDataSource" />
<property name="targetDataSources">
<map>
<entry key="opDataSource" value-ref="opDataSource" />
<entry key="serverADataSource" value-ref="serverADataSource" />
<entry key="serverBDataSource" value-ref="serverBDataSource" />
</map>
</property>
</bean>
実現されたJavaコードは以下の通りです。説明が必要ではないので、一目瞭然です。
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
*
* @ClassName: MultipleDataSource
* @Description: <br>
* @author: yuzhu.peng
* @date: 2018 1 12 4:37:25
*/
public class MultipleDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<String>();
public static void setDataSourceKey(String dataSource) {
dataSourceKey.set(dataSource);
}
@Override
protected Object determineCurrentLookupKey() {
return dataSourceKey.get();
}
public static void removeDataSourceKey() {
dataSourceKey.remove();
}
}
springからAbstractRoutingDatasourceを継承し、抽象的な方法determine Curent LookupKeyを実現します。この方法は毎回データベース接続Connectionを獲得する時までに、今回の接続のデータソースDatasourceを決定します。Springのコードを見ることができます。
/* */
public Connection getConnection()
throws SQLException {
return determineTargetDataSource().getConnection();
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
/* determineCurrentLookupKey , */
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if ((dataSource == null) && (((this.lenientFallback) || (lookupKey == null)))) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
/* : multipleDataSource */
protected abstract Object determineCurrentLookupKey();
第二ステップ:各要求(Serviceメソッドレベル)は、データソースを動的に切り替える発想はSpringのAOP思想を利用して、毎回のService方法を遮断して呼び出し、そして方法の全体パス名によって、multileDataSourceのデータを動的に切り替えるkeyである。私たちのプロジェクトは、異なるサービスすなわち異なるデータベースの操作に対して、互いに独立しているので、同じサービスで異なるデータソースを呼び出すことはあまり推奨されていません。このようにすると、ダイナミックに切り替えが必要かどうかを判断する頻度をDAOレベル、つまりSQLレベルに置く必要があります。また、事務管理もできません。
ダイナミックスイッチングデータソースのAOP実装を見てきました。
import java.lang.reflect.Proxy;
import org.apache.commons.lang.ClassUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
/**
* AOP
*
* @author yuzhu.peng
* @since 2018-01-15
*/
@Aspect
@Order(1)
public class MultipleDataSourceInterceptor {
/**
* , ,Mapper *ServiceImpl, ,
*
* @param joinPoint
* @throws Throwable
*/
@Before("execution(* com.xxxx.platform.service..*.*ServiceImpl.*(..))")
public void setDataSoruce(JoinPoint joinPoint)
throws Throwable {
Class<?> clazz = joinPoint.getTarget().getClass();
String className = clazz.getName();
if (ClassUtils.isAssignable(clazz, Proxy.class)) {
className = joinPoint.getSignature().getDeclaringTypeName();
}
// serverA serverA ,
if (className.contains(".serverA.")) {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverA);
}
else if (className.contains(".serverB.")) {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_serverB);
}
else {
MultipleDataSource.setDataSourceKey(DBConstant.DATA_SOURCE_OP);
}
}
/**
* , , , , ,
*
* @param joinPoint
* @throws Throwable
*/
@After("execution(* com.xxxx.service..*.*ServiceImpl.*(..))")
public void removeDataSoruce(JoinPoint joinPoint)
throws Throwable {
MultipleDataSource.removeDataSourceKey();
}
}
すべてのServiceImplメソッドをブロックし、そのデータソースの機能をメソッドの全制限によって判断し、該当するデータソースを選択し、実行後に現在のデータソースをリリースします。注意私はSpringの@Orderを使っています。注釈は次のように述べます。複数のAOPを定義する時、orderはとても役に立ちます。その他:
最初のプロジェクトには事務が導入されていませんので、すべてOKです。毎回正しいデータソースにアクセスできます。SPringの事務管理に加入すると、データソースを動的に切り替えることができなくなりました。
orderが小さいほど、先に実行されます。これにより、データソースを動的に切り換えることができ、また、トランザクション(同一のデータソース)を成功的に使用することができる。
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。