Edited at

Spring JPA でSELECT時はSlaveサーバへアクセスする

More than 5 years have passed since last update.


やりたいこと

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


コード

https://github.com/hachi-eiji/spring-jpa-practice/


インターセプターのコード

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