Mysql
 sql >> Teknologi Basis Data >  >> RDS >> Mysql

Bagaimana cara mengatur Hibernate untuk membaca/menulis ke sumber data yang berbeda?

Contoh dapat ditemukan di sini:https://github.com/afedulov/routing-data- sumber .

Spring menyediakan variasi DataSource, yang disebut AbstractRoutingDatasource . Ini dapat digunakan sebagai pengganti implementasi DataSource standar dan memungkinkan mekanisme untuk menentukan DataSource konkret mana yang akan digunakan untuk setiap operasi saat runtime. Yang perlu Anda lakukan adalah memperluasnya dan menyediakan implementasi determineCurrentLookupKey abstrak metode. Ini adalah tempat untuk menerapkan logika kustom Anda untuk menentukan DataSource konkret. Objek yang Dikembalikan berfungsi sebagai kunci pencarian. Biasanya berupa String atau en Enum, digunakan sebagai qualifier dalam konfigurasi Spring (detailnya akan mengikuti).

package website.fedulov.routing.RoutingDataSource

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

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

Anda mungkin bertanya-tanya apa itu objek DbContextHolder dan bagaimana cara mengetahui pengenal DataSource mana yang akan dikembalikan? Ingatlah bahwa determineCurrentLookupKey metode akan dipanggil setiap kali TransactionsManager meminta koneksi. Penting untuk diingat bahwa setiap transaksi "terkait" dengan utas terpisah. Lebih tepatnya, TransactionsManager mengikat Koneksi ke utas saat ini. Oleh karena itu, untuk mengirimkan transaksi yang berbeda ke Sumber Data target yang berbeda, kami harus memastikan bahwa setiap utas dapat dengan andal mengidentifikasi Sumber Data mana yang ditujukan untuk digunakan. Ini membuatnya alami untuk menggunakan variabel ThreadLocal untuk mengikat DataSource tertentu ke Thread dan karenanya ke Transaksi. Begini caranya:

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

Seperti yang Anda lihat, Anda juga dapat menggunakan enum sebagai kunci dan Spring akan menyelesaikannya dengan benar berdasarkan namanya. Konfigurasi dan kunci Sumber Data terkait mungkin terlihat seperti ini:

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

Pada titik ini Anda mungkin menemukan diri Anda melakukan sesuatu seperti ini:

@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

Sekarang kita dapat mengontrol Sumber Data mana yang akan digunakan dan meneruskan permintaan sesuka kita. Kelihatan bagus!

...Atau apakah itu? Pertama-tama, panggilan metode statis ke DbContextHolder ajaib itu benar-benar menonjol. Mereka tampak seperti mereka tidak termasuk dalam logika bisnis. Dan mereka tidak. Mereka tidak hanya tidak mengkomunikasikan tujuannya, tetapi juga tampak rapuh dan rawan kesalahan (bagaimana kalau lupa membersihkan dbType). Dan bagaimana jika pengecualian dilemparkan antara setDbType dan cleanDbType? Kita tidak bisa mengabaikannya begitu saja. Kita harus benar-benar yakin bahwa kita mereset dbType, jika tidak, Thread yang dikembalikan ke ThreadPool mungkin berada dalam status "rusak", mencoba menulis ke replika di panggilan berikutnya. Jadi kita membutuhkan ini:

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

Astaga >_< ! Ini jelas tidak terlihat seperti sesuatu yang ingin saya masukkan ke dalam setiap metode hanya-baca. Bisakah kita melakukan yang lebih baik? Tentu saja! Pola "lakukan sesuatu di awal suatu metode, lalu lakukan sesuatu di akhir" ini harus membunyikan lonceng. Aspek untuk menyelamatkan!

Sayangnya posting ini sudah terlalu panjang untuk membahas topik aspek kustom. Anda dapat menindaklanjuti detail penggunaan aspek menggunakan ini tautan .



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Arti n:m dan 1:n dalam desain database

  2. Konektor MySQL menambahkan enum dengan nilai satu kurang dari yang sebenarnya

  3. Bagaimana Anda menggunakan klausa MySql IN?

  4. Python+MySQLdb masalah aneh

  5. Buat objek PHP secara dinamis berdasarkan string