> Java > java지도 시간 > Spring mybatis 다중 데이터 소스 인스턴스에 대한 자세한 설명

Spring mybatis 다중 데이터 소스 인스턴스에 대한 자세한 설명

高洛峰
풀어 주다: 2017-01-24 10:24:00
원래의
1278명이 탐색했습니다.

동일한 프로젝트에 여러 데이터베이스, 즉 여러 데이터 소스가 포함되는 경우가 있습니다. 여러 데이터 소스는 두 가지 상황으로 나눌 수 있습니다.

1) 두 개 이상의 데이터베이스는 서로 관련이 없으며 실제로는 두 개의 프로젝트로 개발될 수 있습니다. 예를 들어, 게임 개발에서 하나의 데이터베이스는 플랫폼 데이터베이스이고 다른 데이터베이스는 플랫폼 아래의 게임에 해당합니다.

2) 두 개 이상의 데이터베이스가 다음과 같이 마스터-슬레이브 관계에 있습니다. mysql. 다중 슬레이브 또는 MHA를 사용하여 구축된 마스터-슬레이브 복제

현재 내가 알고 있는 다중 Spring 데이터 소스를 구축하는 방법은 대략 두 가지가 있습니다. 여러 데이터 소스를 기반으로 선택하세요.

1. 스프링 구성 파일을 이용하여 여러 데이터 소스를 직접 구성

예를 들어 두 데이터베이스 간에 상관 관계가 없는 경우 스프링 구성 파일에서 직접 여러 데이터 소스를 구성할 수 있습니다. . 그런 다음 다음과 같이 트랜잭션을 별도로 구성합니다.

<context:component-scan base-package="net.aazj.service,net.aazj.aop" />
<context:component-scan base-package="net.aazj.aop" />
<!-- 引入属性文件 -->
<context:property-placeholder location="classpath:config/db.properties" />
  
<!-- 配置数据源 -->
<bean name="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
  <property name="url" value="${jdbc_url}" />
  <property name="username" value="${jdbc_username}" />
  <property name="password" value="${jdbc_password}" />
  <!-- 初始化连接大小 -->
  <property name="initialSize" value="0" />
  <!-- 连接池最大使用连接数量 -->
  <property name="maxActive" value="20" />
  <!-- 连接池最大空闲 -->
  <property name="maxIdle" value="20" />
  <!-- 连接池最小空闲 -->
  <property name="minIdle" value="0" />
  <!-- 获取连接最大等待时间 -->
  <property name="maxWait" value="60000" />
</bean>
  
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 <property name="dataSource" ref="dataSource" />
 <property name="configLocation" value="classpath:config/mybatis-config.xml" />
 <property name="mapperLocations" value="classpath*:config/mappers/**/*.xml" />
</bean>
  
<!-- Transaction manager for a single JDBC DataSource -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
</bean>
  
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
  
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
 <property name="basePackage" value="net.aazj.mapper" />
 <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
  
<!-- Enables the use of the @AspectJ style of Spring AOP -->
<aop:aspectj-autoproxy/>
로그인 후 복사

두 번째 데이터 소스 구성

<bean name="dataSource_2" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
  <property name="url" value="${jdbc_url_2}" />
  <property name="username" value="${jdbc_username_2}" />
  <property name="password" value="${jdbc_password_2}" />
  <!-- 初始化连接大小 -->
  <property name="initialSize" value="0" />
  <!-- 连接池最大使用连接数量 -->
  <property name="maxActive" value="20" />
  <!-- 连接池最大空闲 -->
  <property name="maxIdle" value="20" />
  <!-- 连接池最小空闲 -->
  <property name="minIdle" value="0" />
  <!-- 获取连接最大等待时间 -->
  <property name="maxWait" value="60000" />
</bean>
  
<bean id="sqlSessionFactory_slave" class="org.mybatis.spring.SqlSessionFactoryBean">
 <property name="dataSource" ref="dataSource_2" />
 <property name="configLocation" value="classpath:config/mybatis-config-2.xml" />
 <property name="mapperLocations" value="classpath*:config/mappers2/**/*.xml" />
</bean>
  
<!-- Transaction manager for a single JDBC DataSource -->
<bean id="transactionManager_2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource_2" />
</bean>
  
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager_2" />
  
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
 <property name="basePackage" value="net.aazj.mapper2" />
 <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory_2"/>
</bean>
로그인 후 복사

위와 같이 dataSource 2개, sqlSessionFactory 2개, transactionManager 2개를 각각 구성했는데, 핵심은 MapperScannerConfigurer 구성입니다. sqlSessionFactoryBeanName 속성을 사용하여 서로 다른 sqlSessionFactory 이름을 주입합니다. 다른 데이터베이스에 해당하는 매퍼 인터페이스로.

이러한 다중 데이터베이스 구성은 분산 트랜잭션을 지원하지 않습니다. 즉, 동일한 트랜잭션에서 여러 데이터베이스를 운영할 수 없다는 점에 유의해야 합니다. 이 구성 방법의 장점은 매우 간단하지만 유연성이 없다는 것입니다. 마스터-슬레이브 방식의 다중 데이터 소스 구성에는 적합하지 않습니다. 특히 마스터-슬레이브 다중 데이터 소스 구성은 유연성이 필요하며 업무 유형에 따라 세부적인 구성이 필요합니다. 예를 들어, 특히 시간이 많이 걸리는 일부 select 문의 경우 슬레이브에서 실행하기를 원하지만 업데이트 및 삭제와 같은 작업은 마스터에서만 실행할 수 있습니다. 또한 높은 실제 성능이 필요한 일부 select 문의 경우 시간 성능, 우리는 또한 마스터에서 실행해야 할 수도 있습니다. 예를 들어 무기를 구입하기 위해 쇼핑몰에 가는 시나리오에서는 구매 작업이 동시에 마스터에서 완료되어야 합니다. , 내가 소유한 무기와 금화를 다시 쿼리해야 하며, 그러면 이 쿼리가 슬레이브 대신 마스터에서 실행되는 것을 방지해야 합니다. 슬레이브에서는 지연이 발생할 수 없기 때문입니다. 플레이어가 구매에 성공한 후 배낭에서 무기를 찾을 수 없다는 사실을 알기를 바랍니다.

따라서 마스터-슬레이브 형태의 다중 데이터 소스 구성의 경우 슬레이브에 어떤 선택을 할 수 있고 어떤 선택을 할 수 없는지에 따라 유연한 구성이 필요합니다. 노예에. 따라서 위의 데이터 소스 구성은 적합하지 않습니다.

2. AbstractRoutingDataSource와 AOP 기반의 다중 데이터 소스 구성

기본 원칙은 AbstractRoutingDataSource를 상속하기 위해 DataSource 클래스 ThreadLocalRountingDataSource를 직접 정의한 후 구성에 ThreadLocalRountingDataSource를 추가하는 것입니다. 파일 마스터 및 슬레이브 데이터 소스를 삽입한 후 AOP를 사용하여 마스터 데이터 소스를 선택할 위치와 슬레이브 데이터 소스를 선택할 위치를 유연하게 구성합니다. 코드 구현을 살펴보겠습니다.

1) 먼저 다양한 데이터 소스를 나타내는 열거형을 정의합니다.

   
package net.aazj.enums;
  
/**
 * 数据源的类别:master/slave
 */
public enum DataSources {
  MASTER, SLAVE
}
로그인 후 복사

2) 각 데이터 소스를 저장합니다. through TheadLocal 스레드가 선택하는 데이터 소스의 플래그(키):

package net.aazj.util;
  
import net.aazj.enums.DataSources;
  
public class DataSourceTypeManager {
  private static final ThreadLocal<DataSources> dataSourceTypes = new ThreadLocal<DataSources>(){
    @Override
    protected DataSources initialValue(){
      return DataSources.MASTER;
    }
  };
    
  public static DataSources get(){
    return dataSourceTypes.get();
  }
    
  public static void set(DataSources dataSourceType){
    dataSourceTypes.set(dataSourceType);
  }
    
  public static void reset(){
    dataSourceTypes.set(DataSources.MASTER0);
  }
}
로그인 후 복사

3) ThreadLocalRountingDataSource 정의 및 AbstractRoutingDataSource 상속:

package net.aazj.util;
  
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
  
public class ThreadLocalRountingDataSource extends AbstractRoutingDataSource {
  @Override
  protected Object determineCurrentLookupKey() {
    return DataSourceTypeManager.get();
  }
}
로그인 후 복사

4 ) 파일에서 마스터와 슬레이브의 데이터 소스는 ThreadLocalRountingDataSource에 주입됩니다:

<context:component-scan base-package="net.aazj.service,net.aazj.aop" />
<context:component-scan base-package="net.aazj.aop" />
<!-- 引入属性文件 -->
<context:property-placeholder location="classpath:config/db.properties" /> 
<!-- 配置数据源Master -->
<bean name="dataSourceMaster" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
  <property name="url" value="${jdbc_url}" />
  <property name="username" value="${jdbc_username}" />
  <property name="password" value="${jdbc_password}" />
  <!-- 初始化连接大小 -->
  <property name="initialSize" value="0" />
  <!-- 连接池最大使用连接数量 -->
  <property name="maxActive" value="20" />
  <!-- 连接池最大空闲 -->
  <property name="maxIdle" value="20" />
  <!-- 连接池最小空闲 -->
  <property name="minIdle" value="0" />
  <!-- 获取连接最大等待时间 -->
  <property name="maxWait" value="60000" />
</bean> 
<!-- 配置数据源Slave -->
<bean name="dataSourceSlave" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
  <property name="url" value="${jdbc_url_slave}" />
  <property name="username" value="${jdbc_username_slave}" />
  <property name="password" value="${jdbc_password_slave}" />
  <!-- 初始化连接大小 -->
  <property name="initialSize" value="0" />
  <!-- 连接池最大使用连接数量 -->
  <property name="maxActive" value="20" />
  <!-- 连接池最大空闲 -->
  <property name="maxIdle" value="20" />
  <!-- 连接池最小空闲 -->
  <property name="minIdle" value="0" />
  <!-- 获取连接最大等待时间 -->
  <property name="maxWait" value="60000" />
</bean> 
<bean id="dataSource" class="net.aazj.util.ThreadLocalRountingDataSource">
  <property name="defaultTargetDataSource" ref="dataSourceMaster" />
  <property name="targetDataSources">
    <map key-type="net.aazj.enums.DataSources">
      <entry key="MASTER" value-ref="dataSourceMaster"/>
      <entry key="SLAVE" value-ref="dataSourceSlave"/>
      <!-- 这里还可以加多个dataSource -->
    </map>
  </property>
</bean> 
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 <property name="dataSource" ref="dataSource" />
 <property name="configLocation" value="classpath:config/mybatis-config.xml" />
 <property name="mapperLocations" value="classpath*:config/mappers/**/*.xml" />
</bean> 
<!-- Transaction manager for a single JDBC DataSource -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource" />
</bean> 
<!-- 使用annotation定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
 <property name="basePackage" value="net.aazj.mapper" />
 <!-- <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> -->
</bean>
로그인 후 복사

위의 스프링 구성 파일에서는 dataSourceMaster와 master 데이터베이스와 슬레이브 데이터베이스에 대해 각각 dataSourceSlave 두 개의 dataSource를 만든 다음 에 삽입하여 dataSource가 dataSourceMaster 및 dataSourceSlave는 다양한 키를 기반으로 합니다.

5) Spring AOP를 사용하여 dataSource의 키를 지정하면 dataSource는 키를 기반으로 dataSourceMaster 및 dataSourceSlave를 선택합니다.

package net.aazj.aop;
  
import net.aazj.enums.DataSources;
import net.aazj.util.DataSourceTypeManager;
  
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
  
@Aspect  // for aop
@Component // for auto scan
public class DataSourceInterceptor { 
  @Pointcut("execution(public * net.aazj.service..*.getUser(..))")
  public void dataSourceSlave(){};
    
  @Before("dataSourceSlave()")
  public void before(JoinPoint jp) {
    DataSourceTypeManager.set(DataSources.SLAVE);
  }
  // ... ...
}
로그인 후 복사

여기서 Aspect 클래스를 정의하고 @Pointcut("execution(public * net.aazj.service..*.getUser(..))")의 메서드가 호출되기 전에 @Before를 사용하여 DataSourceTypeManager.set를 호출합니다. (DataSources.SLAVE) 키 유형은 DataSources.SLAVE로 설정되므로 dataSource는 key=DataSources.SLAVE를 기반으로 dataSourceSlave 데이터 소스를 선택합니다. 따라서 이 메소드에 대한 sql 문은 슬레이브 데이터베이스에서 실행됩니다.

DataSourceInterceptor 측면을 계속 확장하고 그 안에 다양한 정의를 만들어 서비스 메서드에 해당하는 적절한 데이터 소스를 지정할 수 있습니다.

이러한 방식으로 Spring AOP의 강력한 기능을 사용하여 매우 유연하게 구성할 수 있습니다.

6) AbstractRoutingDataSource 원리 분석

ThreadLocalRountingDataSource 继承了 AbstractRoutingDataSource, 实现其抽象方法 protected abstract Object determineCurrentLookupKey(); 从而实现对不同数据源的路由功能。我们从源码入手分析下其中原理:

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean
AbstractRoutingDataSource 实现了 InitializingBean 那么spring在初始化该bean时,会调用InitializingBean的接口
void afterPropertiesSet() throws Exception; 我们看下AbstractRoutingDataSource是如何实现这个接口的:
  
  @Override
  public void afterPropertiesSet() {
    if (this.targetDataSources == null) {
      throw new IllegalArgumentException("Property &#39;targetDataSources&#39; is required");
    }
    this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
    for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
      Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
      DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
      this.resolvedDataSources.put(lookupKey, dataSource);
    }
    if (this.defaultTargetDataSource != null) {
      this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
    }
  }
로그인 후 복사

targetDataSources 是我们在xml配置文件中注入的 dataSourceMaster 和 dataSourceSlave. afterPropertiesSet方法就是使用注入的。

dataSourceMaster 和 dataSourceSlave来构造一个HashMap——resolvedDataSources。方便后面根据 key 从该map 中取得对应的dataSource。

我们在看下 AbstractDataSource 接口中的 Connection getConnection() throws SQLException; 是如何实现的:

@Override
  public Connection getConnection() throws SQLException {
    return determineTargetDataSource().getConnection();
  }
로그인 후 복사

关键在于 determineTargetDataSource(),根据方法名就可以看出,应该此处就决定了使用哪个 dataSource :

protected DataSource determineTargetDataSource() {
  Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
  Object lookupKey = determineCurrentLookupKey();
  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;
}
로그인 후 복사

   

 Object lookupKey = determineCurrentLookupKey(); 该方法是我们实现的,在其中获取ThreadLocal中保存的 key 值。获得了key之后,在从afterPropertiesSet()中初始化好了的resolvedDataSources这个map中获得key对应的dataSource。而ThreadLocal中保存的 key 值 是通过AOP的方式在调用service中相关方法之前设置好的。OK,到此搞定!

3. 总结

从本文中我们可以体会到AOP的强大和灵活。

以上就是sping,mybatis 多数据源处理的资料整理,希望能帮助有需要的朋友

更多spring mybatis多数据源实例详解相关文章请关注PHP中文网!

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿