Spring Boot + Spring Data JPA + Hibernateで複数データベースにアクセス

開発

(追記)
以下の説明はバグを誘発するので役に立ちません(トランザクションがうまくコミットできなかったりする)
ここのサンプルを参考にしたほうがうまくいきます。

Spring BootとSpring Data JPAを使ってNo XMLでコーディングしているのですが、
最初1つのデータベースのみで作業していたのが最近もう一つのデータベースにアクセスしなければならなくなりました。

コンフィグをSpring Boot任せにしていたのとそもそもHibernateをあまり知らないおかげでかなり詰まってしまい、
大変な思いをしたので作ったコンフィグを書き留めておこうと思います。

1つ目のデータベース

データベースA(Primary)のコンフィグがこちら。
EntityManagerFactoryはBean登録しなくてもSpring Bootが勝手にやってくれているようなので、
dataSourceの登録を@Primaryとprefixを付与しつつ行えばよいです。

@Configuration
@EnableJpaRepositories("com.tono-n-chi.persistence.adb.repository")
@EntityScan("com.tono-n-chi.persistence.adb.domain")
@PropertySource("classpath:application.properties")
public class PersistenceConfigA {

    private static final String DRIVER = "spring.datasource.driverClassName";
    private static final String URL = "spring.datasource.url";
    private static final String USERNAME = "spring.datasource.username";
    private static final String PASSWORD = "spring.datasource.password";
    private static final String MIN_IDLE = "spring.datasource.min-idle";
    private static final String MAX_IDLE = "spring.datasource.max-idle";
    private static final String MAX_ACTIVE = "spring.datasource.max-active";
    private static final String EVICTABLE_IDLE_TIME = "spring.datasource.min-evictable-idle-time-millis";
    @Autowired
    private Environment env;

    @Bean(destroyMethod = "")
    @Primary
    @ConfigurationProperties(prefix = "datasource.adb")
    public DataSource dataSourceAdb() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(env.getRequiredProperty(DRIVER));
        dataSource.setUrl(env.getRequiredProperty(URL));
        dataSource.setUsername(env.getRequiredProperty(USERNAME));
        dataSource.setPassword(env.getRequiredProperty(PASSWORD));
        dataSource.setMinIdle(Integer.valueOf(env.getRequiredProperty(MIN_IDLE)));
        dataSource.setMaxIdle(Integer.valueOf(env.getRequiredProperty(MAX_IDLE)));
        dataSource.setMaxActive(Integer.valueOf(env.getRequiredProperty(MAX_ACTIVE)));
        dataSource.setMinEvictableIdleTimeMillis(Integer.valueOf(env.getRequiredProperty(EVICTABLE_IDLE_TIME)));
        return dataSource;
    }
}

2つ目のデータベース

データベースBのコンフィグ、こちらはSpring Bootの力を借りずに、むしろ避けつつ行わないといけないので厄介です。
主な違いは、

  • EntityManagerFactoryを登録してentityManagerFactoryRefの指定
  • JpaTransactionManagerの登録
  • データベースAと被らないよう@Qualifierの追加

です。

Hibernateのプロパティhibernate.ejb.naming_strategyにorg.springframework.boot.orm.jpa.hibernate.SpringNamingStrategyを指定しないといけないというのが厄介でした。
これを指定しないとEntityクラスの変数をキャメルケースで命名していてもSQL実行時にカラム名としてアンダーバーを付与した読み替えを行ってくれません。

@Configuration
@EnableJpaRepositories(basePackages = "com.tono-n-chi.persistence.bdb.repository", entityManagerFactoryRef = "entityManagerFactoryBdb")
@PropertySource("classpath:application.properties")
public class PersistenceConfigBdb {

    private static final String DRIVER = "spring.datasource.driverClassName";
    private static final String URL_BDB = "com.tono-n-chi.datasource.url.bdb";
    private static final String USERNAME_BDB = "com.tono-n-chi.datasource.username.bdb";
    private static final String PASSWORD_BDB = "com.tono-n-chi.datasource.password.bdb";
    private static final String MIN_IDLE_BDB = "com.tono-n-chi.datasource.min-idle.bdb";
    private static final String MAX_IDLE_BDB = "com.tono-n-chi.datasource.max-idle.bdb";
    private static final String MAX_ACTIVE_BDB = "com.tono-n-chi.datasource.max-active.bdb";
    private static final String EVICTABLE_IDLE_TIME = "spring.datasource.min-evictable-idle-time-millis";
    @Autowired
    private Environment env;

    @Autowired
    @Qualifier(value = "entityManagerFactoryBdb")
    @Bean(name = "entityManagerFactorBdb")
    public EntityManagerFactory entityManagerFactoryBdb(JpaVendorAdapter jpaVendorAdapter, @Qualifier("dataSourceBdb") DataSource dataSourceBdb) {
        LocalContainerEntityManagerFactoryBean bean = new LocalContainerEntityManagerFactoryBean();
        bean.setDataSource(dataSourceBdb);
        bean.setJpaVendorAdapter(jpaVendorAdapter);
        bean.setPackagesToScan("com.tono-n-chi.persistence.bdb.domain");

        Map properties = new HashMap<>();
        properties.put("hibernate.ejb.naming_strategy", "org.springframework.boot.orm.jpa.hibernate.SpringNamingStrategy");
        bean.setJpaPropertyMap(properties);

        bean.afterPropertiesSet();
        return bean.getObject();
    }

    @Autowired
    @Bean
    public JpaTransactionManager transactionManager(@Qualifier("entityManagerFactoryBdb") EntityManagerFactory entityManagerFactoryBdb,  @Qualifier("dataSourceBdb") DataSource dataSourceBdb) {
        JpaTransactionManager bean = new JpaTransactionManager();
        bean.setEntityManagerFactory(entityManagerFactoryBdb);
        bean.setDataSource(dataSourceBdb);
        bean.afterPropertiesSet();
        return bean;
    }

    @Bean(destroyMethod = "")
    @Qualifier(value = "dataSourceBdb")
    @ConfigurationProperties(prefix = "datasource.bdb")
    public DataSource dataSourceBdb() {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(env.getRequiredProperty(DRIVER));
        dataSource.setUrl(env.getRequiredProperty(URL_BDB));
        dataSource.setUsername(env.getRequiredProperty(USERNAME_BDB));
        dataSource.setPassword(env.getRequiredProperty(PASSWORD_BDB));
        dataSource.setMinIdle(Integer.valueOf(env.getRequiredProperty(MIN_IDLE_BDB)));
        dataSource.setMaxIdle(Integer.valueOf(env.getRequiredProperty(MAX_ACTIVE_BDB)));
        dataSource.setMaxActive(Integer.valueOf(env.getRequiredProperty(MAX_IDLE_BDB)));
        dataSource.setMinEvictableIdleTimeMillis(Integer.valueOf(env.getRequiredProperty(EVICTABLE_IDLE_TIME)));
        return dataSource;
    }
}

EntityManagerの呼び分け

JpaRepositoryを使用する場合は@EnableJpaRepositoriesのbasePackagesに指定したパッケージ以下にそれぞれのデータベースにアクセスするJpaRepositoryクラスの実装を追加していけばいいだけですが、
複雑な条件分岐がありCriteriaBuilderを使用したい場合は@PersistenceContextを指定して使用するEntityManagerを呼び分けます。

    // データベースAにアクセスする場合
    @PersistenceContext(unitName = "entityManagerFactory")
    private EntityManager entityManagerA;

    // データベースBにアクセスする場合
    @PersistenceContext(unitName = "entityManagerFactoryBdb")
    private EntityManager entityManagerB;

    // example
    CriteriaBuilder cb = entityManagerA.getCriteriaBuilder();
    CriteriaQuery cq = cb.createQuery(User.class);
    Root root = cq.from(User.class);

    Path userIdAttr = root.get("userId");
    cq.select(root);
    List condList = new ArrayList<>();
    condList.add(cb.like(userIdAttr, user.getUserId()));
    cq.where(condList.toArray(new Predicate[]{}));
    return entityManager.createQuery(cq).getResultList();

色々試行錯誤しながら作ったのでいらない情報などもあるかと思いますが、
「とりあえず動いた」レベルのソースであることを前提に、お気づきの点あれば指摘してください。

コメント

タイトルとURLをコピーしました