Mysql
 sql >> Database >  >> RDS >> Mysql

Come configurare Hibernate per leggere/scrivere su diverse origini dati?

Un esempio può essere trovato qui:https://github.com/afedulov/routing-data- fonte .

Spring fornisce una variazione di DataSource, chiamata AbstractRoutingDatasource . Può essere utilizzato al posto delle implementazioni DataSource standard e abilita un meccanismo per determinare quale DataSource concreto utilizzare per ogni operazione in fase di esecuzione. Tutto quello che devi fare è estenderlo e fornire un'implementazione di una determineCurrentLookupKey astratta metodo. Questo è il luogo in cui implementare la tua logica personalizzata per determinare il DataSource concreto. Oggetto restituito funge da chiave di ricerca. È tipicamente una stringa o en Enum, usata come qualificatore nella configurazione Spring (seguiranno i dettagli).

package website.fedulov.routing.RoutingDataSource

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

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

Ti starai chiedendo cos'è quell'oggetto DbContextHolder e come fa a sapere quale identificatore DataSource restituire? Tieni presente che determineCurrentLookupKey verrà chiamato ogni volta che TransactionsManager richiede una connessione. È importante ricordare che ogni transazione è "associata" a un thread separato. Più precisamente, TransactionsManager associa Connection al thread corrente. Pertanto, al fine di inviare transazioni diverse a DataSource target differenti, dobbiamo assicurarci che ogni thread possa identificare in modo affidabile quale DataSource è destinato ad essere utilizzato. Ciò rende naturale l'utilizzo di variabili ThreadLocal per associare DataSource specifici a un thread e quindi a una transazione. Ecco come si fa:

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();
   }
}

Come vedi, puoi anche usare un enum come chiave e Spring si occuperà di risolverlo correttamente in base al nome. La configurazione e le chiavi di DataSource associate potrebbero avere il seguente aspetto:

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

A questo punto potresti trovarti a fare qualcosa del genere:

@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

Ora possiamo controllare quale DataSource verrà utilizzato e inoltrare le richieste a nostro piacimento. Sembra buono!

...O no? Prima di tutto, quelle chiamate di metodi statici a un magico DbContextHolder sporgono davvero. Sembrano che non appartengano alla logica aziendale. E non lo fanno. Non solo non comunicano lo scopo, ma sembrano fragili e soggetti a errori (che ne dici di dimenticare di pulire il dbType). E se viene generata un'eccezione tra setDbType e cleanDbType? Non possiamo semplicemente ignorarlo. Dobbiamo essere assolutamente sicuri di reimpostare il dbType, altrimenti il ​​thread restituito al ThreadPool potrebbe trovarsi in uno stato "rotto", tentando di scrivere su una replica nella chiamata successiva. Quindi abbiamo bisogno di questo:

  @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());
  }

Yikes >_< ! Questo sicuramente non sembra qualcosa che vorrei inserire in ogni metodo di sola lettura. Possiamo fare di meglio? Ovviamente! Questo schema di "fai qualcosa all'inizio di un metodo, poi fai qualcosa alla fine" dovrebbe suonare un campanello. Aspetti in soccorso!

Sfortunatamente questo post è già diventato troppo lungo per trattare l'argomento degli aspetti personalizzati. Puoi seguire i dettagli sull'utilizzo degli aspetti utilizzando questo collegamento .