Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Como configurar o Hibernate para ler/gravar em diferentes fontes de dados?


Um exemplo pode ser encontrado aqui:https://github.com/afedulov/routing-data- fonte .



Spring fornece uma variação de DataSource, chamada AbstractRoutingDatasource . Ele pode ser usado no lugar de implementações de DataSource padrão e habilita um mecanismo para determinar qual DataSource concreta usar para cada operação em tempo de execução. Tudo o que você precisa fazer é estendê-lo e fornecer uma implementação de um determineCurrentLookupKey abstrato método. Este é o local para implementar sua lógica personalizada para determinar o DataSource concreto. O Objeto Retornado serve como uma chave de pesquisa. Normalmente é um String ou enum, usado como qualificador na configuração do Spring (os detalhes seguirão).
package website.fedulov.routing.RoutingDataSource

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

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

Você pode estar se perguntando o que é esse objeto DbContextHolder e como ele sabe qual identificador DataSource retornar? Lembre-se de que determineCurrentLookupKey será chamado sempre que o TransactionsManager solicitar uma conexão. É importante lembrar que cada transação é "associada" a uma thread separada. Mais precisamente, TransactionsManager liga Connection ao thread atual. Portanto, para despachar diferentes transações para diferentes DataSources de destino, temos que garantir que cada thread possa identificar com segurança qual DataSource é destinado a ser usado. Isso torna natural utilizar variáveis ​​ThreadLocal para vincular DataSource específico a um Thread e, portanto, a uma transação. É assim que se faz:
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 você vê, você também pode usar um enum como chave e o Spring se encarregará de resolvê-lo corretamente com base no nome. A configuração e as chaves do DataSource associadas podem ter esta aparência:
  ....
<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>

Neste ponto, você pode se encontrar fazendo algo assim:
@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

Agora podemos controlar qual DataSource será usado e encaminhar solicitações como quisermos. Parece bom!

...Ou não é? Em primeiro lugar, essas chamadas de método estático para um DbContextHolder mágico realmente se destacam. Parece que não pertencem à lógica de negócios. E eles não. Não apenas eles não comunicam o propósito, mas parecem frágeis e propensos a erros (que tal esquecer de limpar o dbType). E se uma exceção for lançada entre setDbType e cleanDbType? Não podemos simplesmente ignorá-lo. Precisamos ter certeza absoluta de que redefinimos o dbType, caso contrário, o Thread retornado ao ThreadPool pode estar em um estado "quebrado", tentando gravar em uma réplica na próxima chamada. Então precisamos disso:
  @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());
  }

Caramba >_< ! Isso definitivamente não se parece com algo que eu gostaria de colocar em todos os métodos somente leitura. Podemos fazer melhor? É claro! Esse padrão de "faça algo no início de um método, depois faça algo no final" deve soar um sino. Aspectos para o resgate!

Infelizmente, este post já ficou muito longo para cobrir o tópico de aspectos personalizados. Você pode acompanhar os detalhes do uso de aspectos usando este ligação .