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