シーン:SaaSのサービス、同じサービスを使用してさまざまなプロジェクト、異なるライブラリに対応する異なるテナント
ドルイドを用いたフレームnutz、接続プールを使用してデータベース操作
問題:要求、対応する異なるデータベースの要請で異なるテナントの必要性、およびサポートの取引@Transactional
アイデア:1.複数のデータソースのコンテキストを維持し、ThreadLocalのを使用して
2.使用切面的方式切换上下文
3. 自定义AbstractRoutingDataSource的子类,持有数据库上下文的变量,根据当前数据库上下文返回需要的数据库
コード:
AbstractRoutingDataSourceサブクラスで定義するので1、及びデータベースは、コンテキスト変数を保持します
public class MultiDataSource extends AbstractRoutingDataSource {
//持有的数据库上下文,将在切面中设置当前请求的数据库上下文
private static ThreadLocal<String> datasourceHolder = new ThreadLocal();
@Override
protected Object determineCurrentLookupKey() {
return datasourceHolder.get();
}
public static void setDataSource(String dataSource){
datasourceHolder.set(dataSource);
}
public static String getDataSource(){
return datasourceHolder.get();
}
public static void remove(){
datasourceHolder.remove();
}
}
異なる鍵データソースから構築企業ID記載のカスタムカット、カット点及び全てのサブパケットサービスパッケージ、
@Slf4j
@Component
@Aspect
@Order(-1) //这个注解很重要,是个坑,跟了很久才决定加上
public class DataSourceSwitchAspect {
@Pointcut("execution(* com.logan.service..*.*(..))")
public void dataSourceSwitchPointCut(){
}
@Around("dataSourceSwitchPointCut()")
public Object before(ProceedingJoinPoint point) throws Throwable {
//设置DataSource
String companyId = HttpRequestUtils.getCurrentCompanyId();
if(StringUtils.isEmpty(companyId)){
MethodSignature ms = (MethodSignature) point.getSignature();
Method method = ms.getMethod();
//这里主要是处理在@Async的时候,MultiDataSource的数据源设置,只能通过参数获取
for (int i = 0; i < method.getParameters().length; i++) {
if (method.getParameters()[i].getName().equals(TO_COMPANY_ID)) {
Object toCompanyId = point.getArgs()[i];
if (toCompanyId != null) {
companyId = toCompanyId.toString();
}
break;
}
}
}
if(StringUtils.isNotEmpty(companyId)){
String dsName = String.format("ds%s",companyId);
MultiDataSource.setDataSource(dsName);
}
try{
return point.proceed();
}finally {
//最后一定要释放,否则MultiDataSource里面的对象会一直增长
MultiDataSource.remove();
}
}
}
注射マルチデータソースの構成情報を定義3.
@Configuration
public class DruidConfiguration {
@Bean
public ServletRegistrationBean druidStatViewServle2(){
//org.springframework.boot.context.embedded.ServletRegistrationBean提供类的进行注册.
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
//添加初始化参数:initParams
servletRegistrationBean.addInitParameter("loginUsername","account");
servletRegistrationBean.addInitParameter("loginPassword","123456");
//是否能够重置数据.
servletRegistrationBean.addInitParameter("resetEnable","false");
return servletRegistrationBean;
}
/**
* 注册一个:filterRegistrationBean
* @return
*/
@Bean
public FilterRegistrationBean druidStatFilter2(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
//添加过滤规则.
filterRegistrationBean.addUrlPatterns("/*");
//添加不需要忽略的格式信息.
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid2/*");
return filterRegistrationBean;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasources.ds1")
public DataSource ds1(){
DataSource dataSource = DataSourceBuilder.create().type(DruidDataSource.class).build();
return dataSource;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasources.ds2")
public DataSource ds2(){
DataSource dataSource = DataSourceBuilder.create().type(DruidDataSource.class).build();
return dataSource;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasources.ds3")
public DataSource ds3(){
DataSource dataSource = DataSourceBuilder.create().type(DruidDataSource.class).build();
return dataSource;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasources.ds4")
public DataSource ds4(){
DataSource dataSource = DataSourceBuilder.create().type(DruidDataSource.class).build();
return dataSource;
}
@Bean
public DataSource dataSource(){
MultiDataSource dynamicRoutingDataSource = new MultiDataSource();
//配置多数据源
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("ds1", ds1());
dataSourceMap.put("ds2", ds2());
dataSourceMap.put("ds3", ds3());
dataSourceMap.put("ds4", ds4());
// 将 ds1 数据源作为默认指定的数据源
dynamicRoutingDataSource.setDefaultTargetDataSource(ds1());
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
return dynamicRoutingDataSource;
}
@Bean
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
4.あなたがまたDaoRunnerを設定する必要があり、Spring管理のトランザクションを支払うために、nutzある
/ **
- 春の事項NutDao互換性のあるセットを作るためにDaoRunner
* /
@Repository
パブリッククラスSpringDaoRunnerForNutz実装DaoRunner {
@Override
public void run(DataSource dataSource, ConnCallback callback) {
Connection con = DataSourceUtils.getConnection(dataSource);
try {
callback.invoke(con);
}
catch (Exception e) {
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new RuntimeException(e);
}
} finally {
DataSourceUtils.releaseConnection(con, dataSource);
}
}
}
サンプル使用:
@Service
public class TransactionTest {
@Autowired
private UserDODao userDODao;
@Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public void testTransaction(Integer i,String toCompanyId){
System.out.println(String.format("Async DateContext:%s",MultiDataSource.getDataSource()));
UserDO userDO = userDODao.fetchByUserName("aaa");
String name = userDO.getName();
userDO.setName(name+"1次修改");
System.out.println(String.format("修改1次:%s",userDO.getName()));
userDODao.update(userDO);
if(!i.equals(0)) {
int j = 1 / 0;
}
userDO.setName(name +"2次修改");
System.out.println(String.format("修改2次:%s",userDO.getName()));
userDODao.update(userDO);
userDO = userDODao.fetchByUserName("aaa");
if(userDO != null){
System.out.println(userDO.getName());
}
}
}
段ピット:
私たちはセクションをコンテキスト切り替えてデータソースを行いますが、自分自身を見つける、それは仕事をしません見つけたが、TransactionalManagerを追跡する過程で(私たちは春のトランザクション管理を行う点である知っている)、後に@Transactionalよりも実行タイミング定義をカット私たちは事前にカットマン交換データソースに必要なので、私たちは@Orderコメントが上昇するカスタムセクションを持っている(-1)