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ır3. İki tane PlatformTransactionManager yaratılır. Bir tanesi @Primary olarak işaretlenir. Her birisine ilgili LocalContainerEntityManagerFactoryBean atanır4. @EnableJpaRepositories anotasyonunda ilgili LocalContainerEntityManagerFactoryBean ve PlatformTransactionManager belirtilir5. @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