22 Ocak 2021 Cuma

SpringData JPA Multiple Databases Kullanımı

Giriş
Yöntem 1 - İki Tane TransactionManager Yaratmak
Amaç @Transactional anotasyonu içinde istenilen TransactionManager nesnesini yani istenilen veri tabanını belirtebilmek. 
İzlenmesi gereken adımların sırası şöyle
1. İki tane DataSource yaratılır. Bir tanesi @Primary olarak işaretlenir.
2. İki tane LocalContainerEntityManagerFactoryBean yaratılır. Bir tanesi @Primary olarak işaretlenir. Her birisine ilgili DataSource atanır
3. İki tane PlatformTransactionManager yaratılır. Bir tanesi @Primary olarak işaretlenir. Her birisine ilgili LocalContainerEntityManagerFactoryBean atanır
4. @EnableJpaRepositories anotasyonunda ilgili LocalContainerEntityManagerFactoryBean ve PlatformTransactionManager belirtilir
5. @Transactional anotasyonunda hangi PlatformTransactionManager'ın kullanılmak istendiği belirtilir.
- Bir örnek burada. Burada iki tane birbirinden tamamen farklı veri tabanı var.

Örnek - replica
Burada birbirinin aynısı ancak birisi salt okunur bir veri tabanı isteniyor. Dolayısıyla iki tane DataSource yaratılıyor ancak 
- tek bir LocalContainerEntityManagerFactoryBean  ve 
- tek bir PlatformTransactionManager yeterli. 

PlatformTransactionManager nesnesi AbstractRoutingDataSource nesnesini kullanarak doğru veri tabanına bağlanabiliyor. Bu aslında daha çok Multitenant yapıya benziyor.

Örnek
Burada amaç  @Transaction(readOnly=true) ile istenilen TransactionManager nesnesini elde etmek. İki tane DataSource için şöyle yaparız
@Configuration
public class DatabaseConfig {

  @Bean
  @ConfigurationProperties(prefix = "db.master")
  public DataSource readWriteConfiguration() {
    return DataSourceBuilder.create().build();
  }

  @Bean
  @ConfigurationProperties(prefix = "db.slave")
  public DataSource readOnlyConfiguration() {
    return DataSourceBuilder.create().build();
  }
}
LocalContainerEntityManagerFactoryBean  için şöyle yaparız. Her iki veri tabanı da aynı paketler tarayacak
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
  EntityManagerFactoryBuilder builder) {

  return builder.dataSource(routingDataSource())
    .packages("org.rcvaram.demo.entity")
    .build();
}
Bir tane PlatformTransactionManager yaratırız. İki taneye gerek yok, çünkü sadece transaction'ını readOnly özelliğini atayacağız. Şöyle yaparız
@Bean
@Primary
public PlatformTransactionManager transactionManager(
  @Qualifier("jpaTxManager") PlatformTransactionManager wrapped) {
  return new ReplicaAwareTransactionManager(wrapped);
}

@Bean(name = "jpaTxManager")
public PlatformTransactionManager jpaTransactionManager(EntityManagerFactory emf) {
  return new JpaTransactionManager(emf);
}
PlatformTransactionManager sınıfımızın içi şöyle. Koddaki @Transaction anotasyonu readOnly ise AbstractRoutingDataSource nesnesi de ona göre işaretlenir.
public class ReplicaAwareTransactionManager implements 
  PlatformTransactionManager {

  private final PlatformTransactionManager wrapped;

  public ReplicaAwareTransactionManager(
    PlatformTransactionManager platformTransactionManager) {

    wrapped = platformTransactionManager;
  }

  @Override
  public @NotNull TransactionStatus getTransaction(
    TransactionDefinition definition) throws TransactionException {
    //TransactionRoutingDataSource aslında AbstractRoutingDataSource 
TransactionRoutingDataSource .setReadonlyDataSource(definition != null && definition.isReadOnly()); return wrapped.getTransaction(definition); } @Override public void commit(@NotNull TransactionStatus status) throws TransactionException { wrapped.commit(status); } @Override public void rollback(@NotNull TransactionStatus status) throws TransactionException { wrapped.rollback(status); } }
DataSource sınıfımız AbstractRoutingDataSource sınıfından kalıtıyor. İçi şöyle. Böylece setReadonlyDataSource() ile ne işaretli ise o DataSource döndürülür
public class TransactionRoutingDataSource extends 
  AbstractRoutingDataSource {

  private static final ThreadLocal<DataSourceType> currentDataSource = 
    new ThreadLocal<>();

  public TransactionRoutingDataSource(DataSource master, DataSource slave) {
    Map<Object, Object> dataSources = new HashMap<>();
    dataSources.put(DataSourceType.READ_WRITE, master);
    dataSources.put(DataSourceType.READ_ONLY, slave);

    super.setTargetDataSources(dataSources);
    super.setDefaultTargetDataSource(master);
  }

  static void setReadonlyDataSource(boolean isReadonly) {
    currentDataSource.set(isReadonly ? DataSourceType.READ_ONLY : 
                                       DataSourceType.READ_WRITE);
  }

  public static void unload() {
    currentDataSource.remove();
  }

  @Override
  protected Object determineCurrentLookupKey() {
    return currentDataSource.get();
  }

  private enum DataSourceType {
    READ_ONLY,
    READ_WRITE;
  }
}
Örnek
Şöyle yaparız. Burada DataSource ve LocalContainerEntityManagerFactoryBean yaratılması gösterilmiyor
@Configuration
@EnableTransactionManagement
public class DatabaseConfig {

  @Bean(name = "transactionManager1")
  public PlatformTransactionManager transactionManager1(EntityManagerFactory emf) {
     JpaTransactionManager transactionManager = new JpaTransactionManager();
     transactionManager.setEntityManagerFactory(emf);
     return transactionManager;
  }

  @Bean(name = "transactionManager2")
  public PlatformTransactionManager transactionManager2(EntityManagerFactory emf) {
    JpaTransactionManager transactionManager = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(emf);
    return transactionManager;
  }
}
Kullanmak için şöyle yaparız
@Service
public class MyService {

  @Autowired
  @Qualifier("transactionManager1")
  private PlatformTransactionManager transactionManager1;

  @Autowired
  @Qualifier("transactionManager2")
  private PlatformTransactionManager transactionManager2;

  @Transactional("transactionManager1")
  public void doSomethingInDatabase1() {
    // perform database operations on database 1
  }

  @Transactional("transactionManager2")
  public void doSomethingInDatabase2() {
    // perform database operations on database 2
  }
}
Yöntem 2
@EnableJpaRepositories İle yazısına taşıdım

Hiç yorum yok:

Yorum Gönder