やりたいこと
Spring JPAを使ってMaster-Slave構成の時にSELECTはSlave,その他はMasterサーバへアクセスする
ミドルウェア,フレームワーク
- ReplicationDriver
- MySQL 5.6
- Spring 3.2.8
- hibernate
テストの構成
- Customer.java エンティティクラス
- CustomerRepository.java JpaRepositoryを継承したクラス. findなどを行います
- CustomerService.java CustomerRepositoryを呼び出します
- Testクラス CustomerServiceを呼び出します
コードは下記に
https://github.com/hachi-eiji/spring-jpa-practice/
実現方法
ReplicationDriverはautoCommit=false, readOnly=trueのときにselectを発行するとslaveへアクセスします
よって、find処理を実行する前にインターセプターで設定を行います
(autoCommit=trueでも動きました。ここをfalseにする理由をご存じの方は教えてください)
また、CustomerService.javaのメソッドで @Transactional
をつけます
つけない場合は下記のエラーが発生します。これはインターセプターの中で entityManager.unwrap(SessionImpl.class);
を使うときはトランザクションが必須だからです。
-> PersistenceContextアノテーションがEXTENDEDの時は @Transactinal
はなくてもいけました
java.lang.IllegalStateException: No transactional EntityManager available
at org.springframework.orm.jpa.SharedEntityManagerCreator
$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:224)
最後に
実際に組み込むときは、エンティティクラスにマーカーアノテーションをつけたほうが、おそらく効率はいいはずです。
参考資料
ReplicationDriver
MySQL Master/Slave Load Balancing with JPA and Spring
コード
@Aspect
@Component
public class SlaveConnectionInterceptor {
private static final Logger LOGGER = LoggerFactory.getLogger(SlaveConnectionInterceptor.class);
private EntityManager entityManager;
@PersistenceContext
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Around("execution(public * com.hachiyae.jpa..*.find*(..)) || execution(public * com.hachiyae.jpa..*.count*(..))")
public Object proceed(ProceedingJoinPoint pjp) throws Throwable {
LOGGER.info("call SlaveConnectionInterceptor from {}", pjp.getSignature().toShortString());
SessionImpl session = entityManager.unwrap(SessionImpl.class);
Connection connection = session.connection();
boolean autoCommit = connection.getAutoCommit();
boolean readOnly = connection.isReadOnly();
connection.setAutoCommit(false);
connection.setReadOnly(true);
try {
return pjp.proceed();
} finally {
connection.setAutoCommit(autoCommit);
connection.setReadOnly(readOnly);
}
}
}