SpringDataJpaフレームワークのマルチデータソースサポート機能実装について
17931 ワード
実际のプロジェクトの中で、多くの人はすべて多くのデータソースの需要があって、実はJPAの中でこれも简単に実现して、本文は実现の原理と细部を探求しに来ます!
私が書いたSpringDataJPAのデータソースの読み書き分離を実現した友人を見たことがありますが、実は本稿で実現するには一定の考え方があります.両者の原理は実は似ている.
基本原理:
1.デフォルトのデータソースを初期化する.
2.デフォルトのデータソースからデータソース情報を読み出し、初期化してコンテナに入れる.
3.データソースの代わりにエージェントを使用し、データソースのルーティングを構成すればよい.
具体的なコード実装:
動的データソース注記:
デフォルトではデフォルトのデータソースが使用され、valueに値がある場合はvalueの値が使用されます.もちろん、値がlastの場合、ターゲットの方数の最後のパラメータを値として取得し、動的なデータソースの効果を実現します.
列挙を使用してハードコーディングを回避するには、次の手順に従います.
トランザクション・サブクラスをカスタマイズして、データ・ソースの選択を確認します.
カスタム・データ・ソースの所有者.初期化されたすべてのデータ・ソースを格納します.
ここでの管理は実際の管理ではありませんが、フレームワーク内のデータ・ソースはリフレッシュ可能であり、管理可能と同等です.
データ・ソースのコンテキスト管理は、ここを変更することによって使用するデータ・ソースを変更します.
ダイナミックデータソース注記の断面実装:
上のコードから、注釈のデフォルト値はdefaultであることがわかります.すなわち、注釈の値が書かないとデフォルトのデータソースが使用されます.#lastと書いてある場合は、ターゲットメソッドの最後のパラメータをデータソースのkeyとしてクエリーします.注記の値が空でなく、lastでない場合は、注記の値が使用されます.
動的データ・ソースのキー構成、ルーティング構成:
総構成:
データ・テーブルのデータ・ソースを初期化するには、次の手順に従います.
使用例:
私が書いたSpringDataJPAのデータソースの読み書き分離を実現した友人を見たことがありますが、実は本稿で実現するには一定の考え方があります.両者の原理は実は似ている.
基本原理:
1.デフォルトのデータソースを初期化する.
2.デフォルトのデータソースからデータソース情報を読み出し、初期化してコンテナに入れる.
3.データソースの代わりにエージェントを使用し、データソースのルーティングを構成すればよい.
具体的なコード実装:
動的データソース注記:
package vip.efactory.idc.common.dsjpa.annotation;
import java.lang.annotation.*;
/**
* Description:
*
* @author dbdu
* @date 2020-7-16
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DynamicDataSource {
/**
* groupName or specific database name or spring SPEL name.
*
* @return the database you want to switch
*/
String value() default "default";
}
デフォルトではデフォルトのデータソースが使用され、valueに値がある場合はvalueの値が使用されます.もちろん、値がlastの場合、ターゲットの方数の最後のパラメータを値として取得し、動的なデータソースの効果を実現します.
列挙を使用してハードコーディングを回避するには、次の手順に従います.
package vip.efactory.idc.common.dsjpa.enums;
/**
*
*/
public enum DynamicDataSourceEnum {
DEFAULT("default");
DynamicDataSourceEnum(String name) {
this.name = name;
}
private String name;
}
トランザクション・サブクラスをカスタマイズして、データ・ソースの選択を確認します.
package vip.efactory.idc.common.dsjpa.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.DefaultTransactionStatus;
/**
* , !
* , JpaTransactionManager !
*/
@SuppressWarnings("serial")
@Slf4j
public class MyJpaTransactionManager extends JpaTransactionManager {
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
log.debug("jpa-transaction:begin-----now dataSource use is [" + DataSourceContextHolder.getDataSource() + "]");
super.doBegin(transaction, definition);
}
@Override
protected void doCommit(DefaultTransactionStatus status) {
log.debug("jpa-transaction:commit-----now dataSource use is [" + DataSourceContextHolder.getDataSource() + "]");
super.doCommit(status);
}
}
カスタム・データ・ソースの所有者.初期化されたすべてのデータ・ソースを格納します.
package vip.efactory.idc.common.dsjpa.config;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import vip.efactory.idc.common.core.exception.DataSourceNotFoundException;
import vip.efactory.idc.common.core.util.StringUtils;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* Description: ,
*
* @author dbdu
* @date 2020-7-16
*/
@Configuration
@Slf4j
public class DynamicDataSourceProvider {
// map Key , dataSourceMap ,
// key determineCurrentLookupKey() ,
public static Map
ここでの管理は実際の管理ではありませんが、フレームワーク内のデータ・ソースはリフレッシュ可能であり、管理可能と同等です.
データ・ソースのコンテキスト管理は、ここを変更することによって使用するデータ・ソースを変更します.
package vip.efactory.idc.common.dsjpa.config;
import lombok.extern.slf4j.Slf4j;
/**
* Description: ,
*
* @author dbdu
* @date 2020-07-16
*/
@Slf4j
public class DataSourceContextHolder {
//
private static final ThreadLocal local = new ThreadLocal();
public static ThreadLocal getLocal() {
return local;
}
/**
*
*/
public static void setDataSource(String dsId) {
local.set(dsId);
log.debug(" :" + dsId);
}
/**
*
*
* @return
*/
public static String getDataSource() {
log.debug(" :" + local.get());
return local.get();
}
/**
*
*/
public static void clear() {
log.debug(" :" + local.get());
local.remove();
}
}
ダイナミックデータソース注記の断面実装:
package vip.efactory.idc.common.dsjpa.config;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.PriorityOrdered;
import org.springframework.stereotype.Component;
import vip.efactory.idc.common.core.util.StringUtils;
import vip.efactory.idc.common.dsjpa.annotation.DynamicDataSource;
import vip.efactory.idc.common.dsjpa.enums.DynamicDataSourceEnum;
/**
* Description: service
* AOP , Ordered,order ,
* @DynamicDataSource !!
*
* @author dbdu
* @date 2020-7-16
*/
@Aspect
@EnableAspectJAutoProxy(exposeProxy = true, proxyTargetClass = true)
@Component
public class DataSourceAopInService implements PriorityOrdered {
private static final String LAST_PREFIX = "#last";
/**
*
*
* @param dds
*/
@Before("@annotation(dds)")
public void setDynamicDataSource(JoinPoint point, DynamicDataSource dds) {
String key = dds.value();
if (!StringUtils.isEmpty(key) && key.startsWith(LAST_PREFIX)) { // key
Object[] arguments = point.getArgs();
// , !
String dynamicKey = (arguments[arguments.length - 1] == null || StringUtils.isEmpty(String.valueOf(arguments[arguments.length - 1]))) ? DynamicDataSourceEnum.DEFAULT.name() : String.valueOf(arguments[arguments.length - 1]);
DataSourceContextHolder.setDataSource(dynamicKey);
return;
}
DataSourceContextHolder.setDataSource(dds.value());
}
@Override
public int getOrder() {
/**
* ,
*
* @EnableTransactionManagement(order = 10)
*/
return 1;
}
}
上のコードから、注釈のデフォルト値はdefaultであることがわかります.すなわち、注釈の値が書かないとデフォルトのデータソースが使用されます.#lastと書いてある場合は、ターゲットメソッドの最後のパラメータをデータソースのkeyとしてクエリーします.注記の値が空でなく、lastでない場合は、注記の値が使用されます.
動的データ・ソースのキー構成、ルーティング構成:
package vip.efactory.idc.common.dsjpa.config;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.StringUtils;
import javax.sql.DataSource;
/**
* Description:
* Created at:2020-07-16
* by dbdu
*/
@Getter
@Setter
@AllArgsConstructor
public class MyAbstractRoutingDataSource extends AbstractRoutingDataSource {
/**
* , key ,
* key, , , , key !!!
* , , DynamicDataSourceProvider,
*/
@Override
protected DataSource determineTargetDataSource() {
Object lookupKey = determineCurrentLookupKey();
if (!StringUtils.isEmpty(lookupKey)) {
// ,
DataSource dataSource = DynamicDataSourceProvider.getDataSourceByKey((String) lookupKey);
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
}
return super.determineTargetDataSource();
}
/**
* AbstractRoutingDataSource ,
* dataSource key , key ,
* targetDataSources DataSource, , 。
*/
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
総構成:
package vip.efactory.idc.generator.config;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Environment;
import org.hibernate.tool.schema.Action;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import vip.efactory.idc.common.dsjpa.config.DynamicDataSourceProvider;
import vip.efactory.idc.common.dsjpa.config.MyAbstractRoutingDataSource;
import vip.efactory.idc.common.dsjpa.config.MyJpaTransactionManager;
import vip.efactory.idc.common.dspublic.config.DataSourceProperties;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
@EnableConfigurationProperties({JpaProperties.class})
@EnableTransactionManagement
// @EnableJpaRepositories(basePackages = {"vip.efactory"})
@Slf4j
@AllArgsConstructor
public class DynamicDataSourceJpaConfiguration {
private final JpaProperties jpaProperties; // yml jpa
private DataSourceProperties dataSourceProperties;
/**
*
*/
@Bean
public DataSource defaultDataSource() {
// , , :DataSourceBeanPostProcessor
DruidDataSource defaultDataSource = new DruidDataSource();
defaultDataSource.setUsername(dataSourceProperties.getUsername());
defaultDataSource.setPassword(dataSourceProperties.getPassword());
defaultDataSource.setUrl(dataSourceProperties.getUrl());
defaultDataSource.setDriverClassName(dataSourceProperties.getDriverClassName());
try {
defaultDataSource.init();
} catch (SQLException e) {
log.error(" !", e);
log.error(" !");
System.exit(1);
}
return defaultDataSource;
}
/**
*
* roundRobinDataSouceProxy() , AbstractRoutingDataSource ,
* determineCurrentLookupKey() 。
*
* @return
*/
@Bean(name = "roundRobinDataSouceProxy")
public AbstractRoutingDataSource roundRobinDataSouceProxy(DataSource defaultDataSource) {
MyAbstractRoutingDataSource proxy = new MyAbstractRoutingDataSource();
proxy.setTargetDataSources(DynamicDataSourceProvider.dataSourceMap);
//
//proxy.setDefaultTargetDataSource(DynamicDataSourceProvider.dataSourceMap.get(DynamicDataSourceEnum.DEFAULT.name()));
proxy.setDefaultTargetDataSource(defaultDataSource);
return proxy;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(AbstractRoutingDataSource roundRobinDataSouceProxy) {
Map hibernateProps = new LinkedHashMap<>();
hibernateProps.putAll(this.jpaProperties.getProperties());
hibernateProps.put(Environment.DIALECT, "org.hibernate.dialect.MySQL8Dialect");
hibernateProps.put(Environment.PHYSICAL_NAMING_STRATEGY, "org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy"); // column
hibernateProps.put(Environment.HBM2DDL_AUTO, Action.UPDATE); // , !
// hibernateProps.put(Environment.SHOW_SQL, true); // SQL,
// hibernateProps.put(Environment.FORMAT_SQL, true); // SQL,
// No dataSource is set to resulting entityManagerFactoryBean
LocalContainerEntityManagerFactoryBean result = new LocalContainerEntityManagerFactoryBean();
result.setPackagesToScan("vip.efactory");
result.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
result.setJpaPropertyMap(hibernateProps);
// ,---- !!!
result.setDataSource(roundRobinDataSouceProxy);
return result;
}
@Bean
@Primary // bean, ,
public EntityManagerFactory entityManagerFactory(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
return entityManagerFactoryBean.getObject();
}
@Bean(name = "transactionManager")
@Primary // bean, ,
public PlatformTransactionManager txManager(EntityManagerFactory entityManagerFactory, AbstractRoutingDataSource roundRobinDataSouceProxy) {
SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
// SpringDataJpa hibernate , log save
// HibernateTransactionManager result = new HibernateTransactionManager();
// result.setAutodetectDataSource(false); //
// result.setSessionFactory(sessionFactory);
// result.setRollbackOnCommitFailure(true);
// return result;
// JpaTransactionManager txManager = new JpaTransactionManager(); // ,
JpaTransactionManager txManager = new MyJpaTransactionManager(); //
// ,---- !!!
txManager.setDataSource(roundRobinDataSouceProxy);
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
データ・テーブルのデータ・ソースを初期化するには、次の手順に従います.
package vip.efactory.idc.generator.config;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jasypt.encryption.StringEncryptor;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import vip.efactory.idc.common.dsjpa.config.DynamicDataSourceProvider;
import vip.efactory.idc.common.dsjpa.enums.DynamicDataSourceEnum;
import vip.efactory.idc.generator.domain.GenDataSourceConf;
import vip.efactory.idc.generator.service.GenDataSourceConfService;
import javax.annotation.PostConstruct;
import java.sql.SQLException;
import java.util.List;
/**
*
*/
@AllArgsConstructor
@Component
@Slf4j
public class DataSourceBeanPostProcessor {
private DruidDataSource defaultDataSource; // druid
private GenDataSourceConfService genDataSourceConfService;
private final StringEncryptor stringEncryptor; //
private AbstractRoutingDataSource roundRobinDataSouceProxy;
@PostConstruct
public void init() {
//
DynamicDataSourceProvider.addDataSource(DynamicDataSourceEnum.DEFAULT.name(), defaultDataSource); //
log.info(" ...");
List dataSourceConfs = (List) genDataSourceConfService.findAll();
//
if (dataSourceConfs != null && dataSourceConfs.size() > 0) {
dataSourceConfs.forEach(dataSourceConf -> {
try {
DruidDataSource newDataSource = defaultDataSource.cloneDruidDataSource(); //
//
newDataSource.setUsername(dataSourceConf.getUserName());
newDataSource.setPassword(stringEncryptor.decrypt(dataSourceConf.getPwd()));
newDataSource.setUrl(dataSourceConf.getJdbcUrl());
// newDataSource.setDriverClassName(dataSourceConf.getDriverClassName()); //
newDataSource.init(); //
DynamicDataSourceProvider.addDataSource(dataSourceConf.getName(), newDataSource); //
log.info(" {} !", dataSourceConf.getName());
} catch (SQLException throwables) {
log.error(" {} ! :{}", dataSourceConf.getName(), throwables.getMessage());
throwables.printStackTrace();
}
});
// , key , 。
roundRobinDataSouceProxy.setTargetDataSources(DynamicDataSourceProvider.dataSourceMap);
roundRobinDataSouceProxy.afterPropertiesSet(); // , , !!
}
log.info(" ");
}
/**
* , , !
*
* @param dataSourceConf
*/
public void addNewDataSource(GenDataSourceConf dataSourceConf) {
try {
DruidDataSource newDataSource = defaultDataSource.cloneDruidDataSource(); //
//
newDataSource.setUsername(dataSourceConf.getUserName());
newDataSource.setPassword(stringEncryptor.decrypt(dataSourceConf.getPwd()));
newDataSource.setUrl(dataSourceConf.getJdbcUrl());
// newDataSource.setDriverClassName(dataSourceConf.getDriverClassName()); //
newDataSource.init(); //
DynamicDataSourceProvider.addDataSource(dataSourceConf.getName(), newDataSource); //
//
roundRobinDataSouceProxy.setTargetDataSources(DynamicDataSourceProvider.dataSourceMap);
roundRobinDataSouceProxy.afterPropertiesSet();
log.info(" {} !", dataSourceConf.getName());
} catch (SQLException throwables) {
log.error(" {} ! :{}", dataSourceConf.getName(), throwables.getMessage());
throwables.printStackTrace();
}
}
}
使用例:
@Override
@DynamicDataSource("#last")
public Object getTables(String name, int[] startEnd, String dsName) {
... ...
}