5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Spring Boot + JPAで複数DBに接続しようとするとnaming strategyが変わる

Posted at

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

5
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?