spring-txでReplication対応
以下の環境において、DBにmasterとslaveがある場合にリクエストを振り分ける
- Transaction宣言: spring-tx
- Datasource: commons-dbcp, BoneCP
- ORM: spring-jpa, MyBatis
MySQLレプリケーションをVagrantで試してみる。 - Qiita で作ったReplication環境で、general_logを見ながらリクエストがどちらに飛んでいるかを確認出来る。
基本: spring-jpa編
spring-bootでさっと確かめてる
application.prperties
- replication driverでmasterとslaveを指定
spring.datasource.url=jdbc:mysql:replication://192.168.33.10,192.168.33.11/sample spring.datasource.driverClassName=com.mysql.jdbc.Driver
repository
層 ⇒service
層 ⇒controller
層 という仕組みを作る
UserRepository.java
(サンプルなのでJpaRepository)
@Repository public interface UserRepository extends JpaRepository<User, Integer>{}
UserService.java
@Service public class UserService { @Autowired private UserRepository userRepository; public List<User> findAll() { return userRepository.findAll(); // slaveに投げられる } @Transactional(readOnly = false) public User create(String name, int age) { return userRepository.saveAndFlush(new User(name, age)); // masterに投げられる } }
UserController
はUserService
を利用して、如何なるrepositoryも触らない。create内で例外が投げられた際にはrollbackされる。
spring-jpaの場合、repositoryのメソッドの中でコネクションをDataSourceから獲得しようとする。その前にTransactionalにより明示的にSessionが作成されていたら、その情報を元にコネクションを取りに行くため、ReplicationDriverにより振り分けられたmasterとslaveへのコネクションがDataSourceからreadOnly(default=true)
の値を元に渡される
DataSourceがDBCPからHikariCPなどになっていても挙動は変わらない。試しにこちらを参考にBoneCPに差し替えても特に問題なく、master/slaveへリクエストが振り分けられた。
MyBatis編
pom.xml
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> </dependency>
MyBatisConfig.java
@MapperScan("com.krrrr38.sample.repository") public class MyBatisConfig { ...datasource.... @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dataSource()); sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("/sql/mapper-config.xml")); sqlSessionFactoryBean.setFailFast(true); return sqlSessionFactoryBean.getObject(); } @Bean public DataSourceTransactionManager transactionManager() { DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource()); return transactionManager; }
resources/sql/mapper-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <mappers> <mapper resource="sql/UserRepository.xml"/> </mappers> </configuration>
UserService.java
@Service @Transactional(readOnly = true) // !!!!!!!!!基本はslaveに投げるようにこれが必要!!!!!!!!! public class UserService { @Autowired private UserRepository userRepository; public List<User> findAll() { return userRepository.findAll(); // slaveに投げられる } @Transactional(readOnly = false) // masterに投げたいとこだけ、readOnly=falseなTransactionalを付ける public User create(String name, int age) { return userRepository.saveAndFlush(new User(name, age)); // masterに投げられる } }
MyBatisもreplicationDriver使ってる時はデフォルトでreadOnly=trueになって欲しいけど、そもそもレイアが違うのにどうなってんだjpa
AOPで対応したい時
@Transactional
使うとautocommit=onになってしまうので、AOPの方が良いかも
このあたりが参考になる