Maison >Java >javaDidacticiel >Explication détaillée des instances de sources de données multiples Spring Mybatis

Explication détaillée des instances de sources de données multiples Spring Mybatis

高洛峰
高洛峰original
2017-01-24 10:24:001277parcourir

Un même projet implique parfois plusieurs bases de données, c'est-à-dire plusieurs sources de données. Les sources de données multiples peuvent être divisées en deux situations :

1) Deux ou plusieurs bases de données ne sont pas liées et sont indépendantes. En fait, cela peut être développé comme deux projets. Par exemple, dans le développement de jeux, une base de données est une base de données de plate-forme, et d'autres bases de données correspondent aux jeux sous la plate-forme

2) Deux bases de données ou plus sont dans une relation maître-esclave, comme avec ; mysql. Un maître-maître, suivi de plusieurs esclaves ; ou une réplication maître-esclave construite à l'aide de MHA

Actuellement, il existe environ deux façons de créer plusieurs sources de données Spring que je connais. choisissez en fonction de plusieurs sources de données.

1. Utilisez le fichier de configuration Spring pour configurer directement plusieurs sources de données

Par exemple, si les deux bases de données ne sont pas liées, vous pouvez configurer directement plusieurs sources de données dans le fichier de configuration Spring. configurer les transactions séparément, comme suit :

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

Configuration de la deuxième source de données

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

Comme indiqué ci-dessus, nous avons configuré deux dataSource, deux sqlSessionFactory, deux transactionManager, et le point clé est la configuration de MapperScannerConfigurer - utilisez l'attribut sqlSessionFactoryBeanName pour injecter différents noms sqlSessionFactory, dans ce cas, le sqlSessionFactory correspondant est injecté dans l'interface du mappeur correspondant aux différentes bases de données.

Il convient de noter que cette configuration de plusieurs bases de données ne prend pas en charge les transactions distribuées, c'est-à-dire que plusieurs bases de données ne peuvent pas être exploitées dans la même transaction. L’avantage de cette méthode de configuration est qu’elle est très simple, mais elle n’est pas flexible. Il n'est pas adapté à une configuration multi-sources de données de type maître-esclave. La configuration multi-sources de données maître-esclave doit être particulièrement flexible et nécessite une configuration détaillée en fonction du type d'entreprise. Par exemple, pour certaines instructions select qui prennent particulièrement du temps, nous espérons les exécuter sur l'esclave, tandis que des opérations telles que la mise à jour et la suppression ne peuvent être exécutées que sur le maître. De plus, pour certaines instructions select qui nécessitent un niveau de réalité élevé. performance dans le temps, nous devrons peut-être également l'exécuter sur le maître - par exemple, dans un scénario où je vais au centre commercial pour acheter une arme, l'opération d'achat doit être effectuée par le maître en même temps, une fois l'achat terminé. , je dois réinterroger les armes et les pièces d'or que je possède, alors cette requête peut. Nous devons également empêcher qu'elle soit exécutée sur le maître au lieu de l'esclave, car il peut y avoir un retard sur l'esclave. Je veux que les joueurs découvrent qu'ils ne trouvent pas l'arme dans leur sac à dos une fois l'achat réussi.

Ainsi, pour la configuration de sources multi-données de type maître-esclave, une configuration flexible doit être effectuée en fonction de l'entreprise, quelles sélections peuvent être placées sur l'esclave et lesquelles ne peuvent pas être placées. l'esclave. Par conséquent, la configuration ci-dessus de la source de données ne convient pas.

2. Configuration de plusieurs sources de données basées sur AbstractRoutingDataSource et AOP

Le principe de base est que nous définissons nous-mêmes une classe DataSource ThreadLocalRountingDataSource pour hériter de AbstractRoutingDataSource, puis ajoutons ThreadLocalRountingDataSource dans la configuration. fichier Injectez les sources de données maître et esclave, puis utilisez AOP pour configurer de manière flexible où sélectionner la source de données maître et où sélectionner la source de données esclave. Regardons l'implémentation du code :

1) Définissez d'abord une énumération pour représenter différentes sources de données :

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

2) Via TheadLocal Enregistrez l'indicateur (clé) de la source de données choisie par chaque thread :

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) Définissez ThreadLocalRountingDataSource et héritez de 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) Injectez les sources de données maître et esclave dans ThreadLocalRountingDataSource dans le fichier de configuration :

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

Dans le fichier de configuration Spring ci-dessus, nous ciblons le maître La base de données et la base de données esclave définissent respectivement deux dataSources, dataSourceMaster et dataSourceSlave, puis les injectent dans 9729085eed78f0c4c7bde45d97215f14, afin que notre dataSource puisse être basée sur différentes clés .Choisissons dataSourceMaster et dataSourceSlave.

5) Utilisez Spring AOP pour spécifier la clé de dataSource, afin que dataSource sélectionne dataSourceMaster et dataSourceSlave en fonction de la clé :

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

Ici, nous définissons une classe Aspect, et nous utilisons @Before pour appeler le DataSourceTypeManager avant que la méthode dans @Pointcut("execution(public * net.aazj.service..*.getUser(..))") soit appelé. .set (DataSources.SLAVE) définit le type de clé sur DataSources.SLAVE, de sorte que dataSource sélectionnera la dataSource dataSourceSlave en fonction de key = DataSources.SLAVE. Par conséquent, l'instruction SQL pour cette méthode sera exécutée sur la base de données esclave.

Nous pouvons continuer à étendre l'aspect DataSourceInterceptor et y faire diverses définitions pour spécifier la source de données appropriée correspondant à une méthode d'un service.

De cette façon, nous pouvons utiliser les fonctions puissantes de Spring AOP pour le configurer de manière très flexible.

6) Analyse du principe de 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中文网!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn