JPAでの複数のデータソースへの接続は、https://www.baeldung.com/spring-data-jpa-multiple-databases のようにConfiguration Classを設定することで実現できます。
しかし、Springのデフォルトのnaming strategyが適用されなくなるため、これを明示的に指定する必要があります。
なお、以下ではDBはMySQLを、JPAの実装にはHibernateを用いた場合を想定しています。
対応方法だけ知りたい方はnaming strategyを設定するを参照ください。
デフォルトのnaming strategy
Spring Bootでのnaming strategyは、spring.jpa.hibernate.naming.physical-strategyとspring.jpa.hibernate.naming.implicit-strategyで設定されます。
それぞれデフォルトではorg.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategyとorg.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategyが設定されており、
- .(ドット)を_(アンダースコア)に置換
- CamelCaseをsnake_caseに変換
- table名をLower caseに変換
となります。
例えば、このようなスキーマのテーブルに対しては、
mysql> desc user_master;
+------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+-------+
| id | varchar(255) | NO | PRI | NULL | |
| first_name | varchar(255) | YES | | NULL | |
| last_name | varchar(255) | YES | | NULL | |
+------------+--------------+------+-----+---------+-------+
以下のようなEntityを定義することで自動で対応されます。(逆も然りで、このようなEntityを定義してddl-auto=createを設定すれば上記のようなテーブルが作成されます。)
@Entity
public class UserMaster {
@Id
private String id;
private String firstName;
private String lastName;
}
実際に確認する
上記のEntityを定義し、spring.datasourceで接続先の情報を設定、さらに適当なJpaRepositoryを実装したクラスとControllerを実装してみます。
application.yml
spring:
datasource:
url: jdbc:mysql://your.mysql.host:3306/sample
username: user
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
repository
@Repository
public interface UserMasterRepository extends JpaRepository<UserMaster, String> {
}
controller
@RequestMapping("test")
@RestController
public class TestController {
private UserMasterRepository userMasterRepository;
public TestController(UserMasterRepository userMasterRepository) {
this.userMasterRepository = userMasterRepository;
}
@GetMapping("")
public UserMaster test() {
return userMasterRepository.findAll();
}
}
→ 実際にエンドポイントを叩いてみるとテーブル名・カラム名がいい感じに変換され、レコードを引っ張ってこれることが確認できます。
$ curl -s localhost:8080/test | jq
[
{
"id": "johndoe",
"firstName": "john",
"lastName": "doe"
}
]
自前で定義した場合は適用されない
spring.datasourceで設定していたものを以下のように自前で設定してみます。
application.yml
spring:
datasource:
sample:
url: jdbc:mysql://your.mysql.host:3306/sample
username: user
password: xxxx
driver-class-name: com.mysql.cj.jdbc.Driver
@Configuration
@EnableJpaRepositories(
basePackages = "com.example.demo.repository",
entityManagerFactoryRef = "myEntityManager",
transactionManagerRef = "myTransactionManager"
)
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.datasource.sample")
public DataSourceProperties myDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
@Autowired
public DataSource myDataSource(@Qualifier("myDataSourceProperties") DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().build();
}
@Bean
@Primary
@Autowired
public LocalContainerEntityManagerFactoryBean myEntityManager(EntityManagerFactoryBuilder builder, @Qualifier("myDataSource") DataSource dataSource){
return builder.dataSource(dataSource)
.packages("com.example.demo.model")
.build();
}
@Bean
@Primary
@Autowired
public JpaTransactionManager myTransactionManager(@Qualifier("myEntityManager") LocalContainerEntityManagerFactoryBean primaryEntityManager) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(primaryEntityManager.getObject());
return transactionManager;
}
}
先ほどのエンドポイントにリクエストを飛ばしてみると以下のエラーが発生しました。
java.sql.SQLSyntaxErrorException: Table 'sample.usermaster' doesn't exist
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120) ~[mysql-connector-java-8.0.18.jar:8.0.18]
...
ログの通り、"UserMaster"が"usermaster (not user_master)"に変換されており、SpringのデフォルトのNamingStrategyが適用されていないことがわかります。
naming strategyを設定する
EntityManagerFactoryBuilderでLocalContainerEntityManagerFactoryBeanをbuildする際にJpaのプロパティを渡せるので、デフォルトと同じ設定を反映させてみましょう。
public LocalContainerEntityManagerFactoryBean myEntityManager(EntityManagerFactoryBuilder builder, @Qualifier("myDataSource") DataSource dataSource){
return builder.dataSource(dataSource)
.packages("com.example.demo.model")
.properties(new HashMap<>(){
{
put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());
put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
}
})
.build();
}
これで再度叩いてみると、
$ curl -s localhost:8080/test | jq
[
{
"id": "johndoe",
"firstName": "john",
"lastName": "doe"
}
]
無事接続できました。
指摘・補足等あれば是非コメントいただければ。
参考
https://www.baeldung.com/spring-data-jpa-multiple-databases
https://www.baeldung.com/hibernate-field-naming-spring-boot
https://stackoverflow.com/questions/40509395/cant-set-jpa-naming-strategy-after-configuring-multiple-data-sources-spring-1