Heim  >  Artikel  >  Java  >  Detaillierte Erläuterung der mehreren Datenquelleninstanzen von Spring Mybatis

Detaillierte Erläuterung der mehreren Datenquelleninstanzen von Spring Mybatis

高洛峰
高洛峰Original
2017-01-24 10:24:001264Durchsuche

Das gleiche Projekt umfasst manchmal mehrere Datenbanken, also mehrere Datenquellen. Mehrere Datenquellen können in zwei Situationen unterteilt werden:

1) Zwei oder mehr Datenbanken sind nicht miteinander verbunden und unabhängig. Tatsächlich kann dies als zwei Projekte entwickelt werden. Bei der Spieleentwicklung ist beispielsweise eine Datenbank eine Plattformdatenbank, und andere Datenbanken entsprechen Spielen unter der Plattform

2) Zwei oder mehr Datenbanken stehen in einer Master-Slave-Beziehung, z. B. mit MySQL. Eine Master-Master-Replikation, gefolgt von einer Master-Slave-Replikation, die mit MHA erstellt wurde. Derzeit gibt es ungefähr zwei Möglichkeiten, mehrere Spring-Datenquellen zu erstellen Wählen Sie basierend auf mehreren Datenquellen.

1. Verwenden Sie die Spring-Konfigurationsdatei, um mehrere Datenquellen direkt zu konfigurieren.

Wenn beispielsweise keine Korrelation zwischen den beiden Datenbanken besteht, können Sie mehrere Datenquellen direkt in der Spring-Konfigurationsdatei konfigurieren . Konfigurieren Sie dann die Transaktionen separat wie folgt:

<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/>

Konfiguration der zweiten Datenquelle

<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>

Wie oben gezeigt, haben wir zwei DataSource, zwei SQLSessionFactory und zwei TransactionManager konfiguriert, und der entscheidende Punkt ist die Konfiguration von MapperScannerConfigurer – verwenden Sie das Attribut sqlSessionFactoryBeanName, um verschiedene sqlSessionFactory-Namen einzufügen, in diesem Fall die entsprechende sqlSessionFactory wird in die Mapper-Schnittstelle entsprechend verschiedenen Datenbanken injiziert.

Es ist zu beachten, dass diese Konfiguration mehrerer Datenbanken keine verteilten Transaktionen unterstützt, d. h. mehrere Datenbanken können nicht in derselben Transaktion betrieben werden. Der Vorteil dieser Konfigurationsmethode besteht darin, dass sie sehr einfach, aber nicht flexibel ist. Es eignet sich nicht für die Master-Slave-Konfiguration mit mehreren Datenquellen. Die Konfiguration mit mehreren Datenquellen muss besonders flexibel sein und erfordert eine detaillierte Konfiguration entsprechend der Art des Unternehmens. Beispielsweise hoffen wir, dass einige ausgewählte Anweisungen, die besonders zeitaufwändig sind, auf dem Slave ausgeführt werden können, während Vorgänge wie Aktualisieren und Löschen nur auf dem Master ausgeführt werden können. Darüber hinaus erfordern einige ausgewählte Anweisungen eine hohe Realzeit. Zeitleistung, wir müssen es möglicherweise auch auf dem Master ausführen. In einem Szenario, in dem ich zum Beispiel ins Einkaufszentrum gehe, um eine Waffe zu kaufen, muss der Kaufvorgang gleichzeitig auf dem Master erfolgen, nachdem der Kauf abgeschlossen ist , muss ich die Waffen und Goldmünzen, die ich besitze, erneut abfragen, dann muss diese Abfrage möglicherweise auch verhindert werden, dass sie auf dem Master und nicht auf dem Slave ausgeführt wird, da es auf dem Slave zu einer Verzögerung kommen kann Ich möchte, dass Spieler nach erfolgreichem Kauf feststellen, dass sie die Waffe nicht in ihrem Rucksack finden können.

Für die Konfiguration von Multi-Datenquellen vom Typ Master-Slave muss daher eine flexible Konfiguration entsprechend dem Unternehmen durchgeführt werden, welche Auswahlen auf dem Slave platziert werden können und welche nicht der Sklave. Daher ist die obige Konfiguration der Datenquelle nicht geeignet.

2. Konfiguration mehrerer Datenquellen basierend auf AbstractRoutingDataSource und AOP

Das Grundprinzip besteht darin, dass wir selbst eine DataSource-Klasse ThreadLocalRountingDataSource definieren, um AbstractRoutingDataSource zu erben, und dann ThreadLocalRountingDataSource in die Konfiguration einfügen file Injizieren Sie die Master- und Slave-Datenquellen und verwenden Sie dann AOP, um flexibel zu konfigurieren, wo die Master-Datenquelle und wo die Slave-Datenquelle ausgewählt werden soll. Schauen wir uns die Code-Implementierung an:

1) Definieren Sie zunächst eine Aufzählung zur Darstellung verschiedener Datenquellen:

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

2) Über TheadLocal Speichern Sie das Flag (Schlüssel), welche Datenquelle jeder Thread auswählt:

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) Definieren Sie ThreadLocalRountingDataSource und erben Sie 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) Injizieren Sie die Master- und Slave-Datenquellen in ThreadLocalRountingDataSource in der Konfigurationsdatei:

<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>

In der obigen Spring-Konfigurationsdatei zielen wir auf den Master Datenbank Die dataSource- und Slave-Datenbank definieren jeweils zwei dataSources, dataSourceMaster und dataSourceSlave, und fügen sie dann in 9729085eed78f0c4c7bde45d97215f14 ein, sodass unsere dataSource darauf basieren kann unterschiedliche Schlüssel. Wählen wir dataSourceMaster und dataSourceSlave.

5) Verwenden Sie Spring AOP, um den Schlüssel von dataSource anzugeben, sodass dataSource dataSourceMaster und dataSourceSlave basierend auf dem Schlüssel auswählt:

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);
  }
  // ... ...
}

Hier definieren wir eine Aspect-Klasse und verwenden @Before, um den DataSourceTypeManager aufzurufen, bevor die Methode in @Pointcut("execution(public * net.aazj.service..*.getUser(..))") ist aufgerufen. .set(DataSources.SLAVE) setzt den Schlüsseltyp auf DataSources.SLAVE, sodass die dataSource die dataSourceSlave-dataSource basierend auf key=DataSources.SLAVE auswählt. Daher wird die SQL-Anweisung für diese Methode in der Slave-Datenbank ausgeführt.


Wir können den DataSourceInterceptor-Aspekt weiter erweitern und darin verschiedene Definitionen vornehmen, um die entsprechende Datenquelle anzugeben, die einer Methode eines Dienstes entspricht.

Auf diese Weise können wir die leistungsstarken Funktionen von Spring AOP nutzen, um es sehr flexibel zu konfigurieren.

6) Analyse des Prinzips von 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中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn