sql >> Base de Datos >  >> RDS >> Mysql

¿Cómo configurar Hibernate para leer/escribir en diferentes fuentes de datos?

Puede encontrar un ejemplo aquí:https://github.com/afedulov/routing-data- fuente .

Spring proporciona una variación de DataSource, llamada AbstractRoutingDatasource . Se puede usar en lugar de las implementaciones estándar de DataSource y habilita un mecanismo para determinar qué DataSource concreto usar para cada operación en tiempo de ejecución. Todo lo que necesita hacer es extenderlo y proporcionar una implementación de un determineCurrentLookupKey abstracto. método. Este es el lugar para implementar su lógica personalizada para determinar el origen de datos concreto. Objeto devuelto sirve como clave de búsqueda. Por lo general, es una cadena o una enumeración, que se usa como calificador en la configuración de Spring (los detalles seguirán).

package website.fedulov.routing.RoutingDataSource

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class RoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DbContextHolder.getDbType();
    }
}

Quizás se pregunte qué es ese objeto DbContextHolder y cómo sabe qué identificador de fuente de datos devolver. Tenga en cuenta que determineCurrentLookupKey se llamará al método cada vez que TransactionsManager solicite una conexión. Es importante recordar que cada transacción está "asociada" con un hilo separado. Más precisamente, TransactionsManager vincula Connection al subproceso actual. Por lo tanto, para enviar diferentes transacciones a diferentes fuentes de datos de destino, debemos asegurarnos de que cada subproceso pueda identificar de manera confiable qué fuente de datos está destinada a ser utilizada. Esto hace que sea natural utilizar variables ThreadLocal para vincular un origen de datos específico a un subproceso y, por lo tanto, a una transacción. Así es como se hace:

public enum DbType {
   MASTER,
   REPLICA1,
}

public class DbContextHolder {

   private static final ThreadLocal<DbType> contextHolder = new ThreadLocal<DbType>();

   public static void setDbType(DbType dbType) {
       if(dbType == null){
           throw new NullPointerException();
       }
      contextHolder.set(dbType);
   }

   public static DbType getDbType() {
      return (DbType) contextHolder.get();
   }

   public static void clearDbType() {
      contextHolder.remove();
   }
}

Como ves, también puedes usar un enum como clave y Spring se encargará de resolverlo correctamente en base al nombre. La configuración y las claves de la fuente de datos asociada podrían tener este aspecto:

  ....
<bean id="dataSource" class="website.fedulov.routing.RoutingDataSource">
 <property name="targetDataSources">
   <map key-type="com.sabienzia.routing.DbType">
     <entry key="MASTER" value-ref="dataSourceMaster"/>
     <entry key="REPLICA1" value-ref="dataSourceReplica"/>
   </map>
 </property>
 <property name="defaultTargetDataSource" ref="dataSourceMaster"/>
</bean>

<bean id="dataSourceMaster" class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  <property name="url" value="${db.master.url}"/>
  <property name="username" value="${db.username}"/>
  <property name="password" value="${db.password}"/>
</bean>
<bean id="dataSourceReplica" class="org.apache.commons.dbcp.BasicDataSource">
  <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
  <property name="url" value="${db.replica.url}"/>
  <property name="username" value="${db.username}"/>
  <property name="password" value="${db.password}"/>
</bean>

En este punto, es posible que te encuentres haciendo algo como esto:

@Service
public class BookService {

  private final BookRepository bookRepository;
  private final Mapper               mapper;

  @Inject
  public BookService(BookRepository bookRepository, Mapper mapper) {
    this.bookRepository = bookRepository;
    this.mapper = mapper;
  }

  @Transactional(readOnly = true)
  public Page<BookDTO> getBooks(Pageable p) {
    DbContextHolder.setDbType(DbType.REPLICA1);   // <----- set ThreadLocal DataSource lookup key
                                                  // all connection from here will go to REPLICA1
    Page<Book> booksPage = callActionRepo.findAll(p);
    List<BookDTO> pContent = CollectionMapper.map(mapper, callActionsPage.getContent(), BookDTO.class);
    DbContextHolder.clearDbType();               // <----- clear ThreadLocal setting
    return new PageImpl<BookDTO>(pContent, p, callActionsPage.getTotalElements());
  }

  ...//other methods

Ahora podemos controlar qué fuente de datos se utilizará y reenviar las solicitudes como queramos. ¡Se ve bien!

... ¿O sí? En primer lugar, esas llamadas de método estático a un DbContextHolder mágico realmente sobresalen. Parece que no pertenecen a la lógica empresarial. Y no lo hacen. No solo no comunican el propósito, sino que parecen frágiles y propensos a errores (¿qué hay de olvidarse de limpiar el dbType?). ¿Y si se lanza una excepción entre setDbType y cleanDbType? No podemos simplemente ignorarlo. Necesitamos estar absolutamente seguros de que reiniciamos el dbType, de lo contrario, el subproceso devuelto a ThreadPool podría estar en un estado "roto", tratando de escribir en una réplica en la próxima llamada. Así que necesitamos esto:

  @Transactional(readOnly = true)
  public Page<BookDTO> getBooks(Pageable p) {
    try{
      DbContextHolder.setDbType(DbType.REPLICA1);   // <----- set ThreadLocal DataSource lookup key
                                                    // all connection from here will go to REPLICA1
      Page<Book> booksPage = callActionRepo.findAll(p);
      List<BookDTO> pContent = CollectionMapper.map(mapper, callActionsPage.getContent(), BookDTO.class);
       DbContextHolder.clearDbType();               // <----- clear ThreadLocal setting
    } catch (Exception e){
      throw new RuntimeException(e);
    } finally {
       DbContextHolder.clearDbType();               // <----- make sure ThreadLocal setting is cleared         
    }
    return new PageImpl<BookDTO>(pContent, p, callActionsPage.getTotalElements());
  }

Vaya >_< ! Esto definitivamente no parece algo que me gustaría poner en cada método de solo lectura. ¿Podemos hacerlo mejor? ¡Por supuesto! Este patrón de "hacer algo al comienzo de un método, luego hacer algo al final" debería sonar familiar. Aspectos al rescate!

Desafortunadamente, esta publicación ya se ha hecho demasiado larga para cubrir el tema de los aspectos personalizados. Puede realizar un seguimiento de los detalles del uso de aspectos con este enlace .