技術選定とversion
Spring Bootで複数のDataSourceを扱う方法は、使用するORMによって変わってくる。
ここではMyBatisの方法を書く。
検証version
・Spring Boot 1.3.3
・MyBatis 3.3.1
・MyBatis-Spring 1.2.4
今回の設定における前提
DBは二つ接続したい。
一つは自システム専用のDBで、もう一つは他システムのDB。
それぞれownとotherというDB名とする。ownにはCustomerテーブル、otherにはOrderテーブルがある。
pom.xml
Spring BootにMyBatisを組み込むためのライブラリの設定。
pom.xml
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.0.1</version>
</dependency>
mybatis-spring-boot-starterが提供されているので、これだけでMyBatis本体とMyBatis-Springが入ってくれる。
ディレクトリ構造
DB接続部分のディレクトリ構造の抜粋
src/main/java/jp/co/sample
├ domain
│ ├ Customer.java
│ └ Order.java
├ mapper
│ ├ handler
│ ├ other
│ │ └ OrderMapper.java
│ └ own
│ └ CustomerMapper.java
├ service
│ ├ CustomerService.java
│ └ OrderService.java
├ system/config/db
│ ├ DataSource.java
│ ├ OtherDataSourceConfiguration.java
│ ├ OtherDBProperties.java
│ ├ OwnDataSourceConfiguration.java
│ └ OwnDBProperties.java
├ web
└ Application.java
src/main/resources
├ jp/co/sample
│ ├ other
│ │ └ OrderMapper.xml
│ └ own
│ └ CustomerMapper.xml
├ application.properties
├ data_other.sql
├ data_own.sql
├ mybatis-config.xml
├ schema_other.sql
└ schema_own.sql
ディレクトリごとの役割解説
ディレクトリ | 役割 |
---|---|
domain | 検索結果に相当するJavaBeansをおく。 |
mapper | 直下にDBごとのディレクトリを作成し、SQLを記載したxmlを呼び出すためのinterfaceをおく。 ディレクトリを分けるということ以外、Javaソース上でDBの違いは意識しなくて済む。 |
service | mapper interfaceを呼び出したり、ビジネスロジックを書く。DBの違いはここでは意識しなくて済む。 |
system/config/db | DBに関係する設定。パラメータ設定をapplication.propertiesで行えるようにするためのコードが書かれている。 |
ソースコード解説
ディレクトリごとの役割解説で記載した通り、DBごとの違いが現れるのはsystem/config/dbのソースだけであり、あとは単一DBで使用するときと変わらない。
※ただし、Application.javaにはAnnotationの変更が入る。
まずはsystem/config/dbの各クラスについて全文掲載していく。
DataSource.java
import lombok.Getter;
import lombok.Setter;
public class DataSource extends org.apache.tomcat.jdbc.pool.DataSource {
/**
* Schema (DDL) script resource reference.
*/
@Getter @Setter
private String schema;
/**
* Data (DML) script resource reference.
*/
@Getter @Setter
private String data;
}
Tomcatのorg.apache.tomcat.jdbc.pool.DataSourceを独自拡張している。
Spring Bootでは/src/main/resourcesにschema.sqlとdata.sqlを配置することで、アプリの起動時に自動でSQLを実行してくれる機能がある。また以下のようにファイル名を指定することで、デフォルトのファイル名から変更することもできる。
spring.datasource.schema=classpath:schema_for_development.sql
spring.datasource.data=classpath:data_for_development.sql
しかしorg.apache.tomcat.jdbc.pool.DataSourceには同パラメータがないため、schemaとdataというフィールドを持つように拡張している。
OwnDBProperties.java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import lombok.Getter;
import lombok.Setter;
@ConfigurationProperties("spring.own")
public class OwnDBProperties {
@Getter @Setter
@NestedConfigurationProperty
private DataSource datasource;
}
ownDB用にapplication.properiesでspring.ownをkeyに各種設定をできるようにしている。
@ConfigurationPropertiesをクラスに付与すると、@ConfigurationPropertiesの引数で指定した値がapplication.properiesでkeyとして使える。
DataSourceクラスで定義されているものを、各種keyとして使用したいので、@NestedConfigurationPropertyをDataSourceに付与することで、spring.own.datasourceというprefixで各種項目にアクセスできるようになる。
OtherDBProperties.java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.NestedConfigurationProperty;
import lombok.Getter;
import lombok.Setter;
@ConfigurationProperties("spring.other")
public class OtherProperties {
@Getter @Setter
@NestedConfigurationProperty
private DataSource datasource;
}
spring.otherをapplication.propertiesで使用できるようにする。基本はOwnDBPropertiesと同じ。
OwnDataSourceConfiguration.java
import java.util.Optional;
import javax.sql.DataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
@Configuration
@MapperScan(basePackages = "jp.co.sample.mapper.own", sqlSessionTemplateRef = "ownSqlSessionTemplate")
public class OwnDataSourceConfiguration {
@Autowired
private OwnDBProperties ownDBProperties;
@Autowired
private ApplicationContext context;
@Bean(name = { "dataSource", "ownDataSource" })
@ConfigurationProperties(prefix = "spring.own.datasource")
@Primary
public DataSource dataSource() {
return ownDBProperties.getDatasource();
}
@Bean(name = "ownSqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("ownDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// MyBatis のコンフィグレーションファイル
bean.setConfigLocation(context.getResource(context.getEnvironment().getProperty("mybatis.config")));
return new SqlSessionTemplate(bean.getObject());
}
@Bean(name = "ownDataSourceInitializer")
public DataSourceInitializer dataSourceInitializer(@Qualifier("ownDataSource") DataSource dataSource) {
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
dataSourceInitializer.setDataSource(dataSource);
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
// schema,dataをpropertiesファイルで指定しない場合、Location must not be nullが出るため、nullの場合は処理を行わないように修正(2016/06/28)
// databasePopulator.addScripts(
// context.getResource(ownDBProperties.getDatasource().getSchema()),
// context.getResource(ownDBProperties.getDatasource().getData()));
Optional.ofNullable(ownDBProperties.getDatasource().getSchema())
.map(schema -> context.getResource(schema))
.ifPresent(resource -> databasePopulator.addScript(resource));
Optional.ofNullable(ownDBProperties.getDatasource().getData())
.map(data -> context.getResource(data))
.ifPresent(resource -> databasePopulator.addScript(resource));
dataSourceInitializer.setDatabasePopulator(databasePopulator);
dataSourceInitializer.setEnabled(true);
return dataSourceInitializer;
}
}
ownDB用に以下のクラスを構築するJavaConfig。
- DataSource
- MyBatisが使用するSqlSessionTemplate
- Spring Boot起動時のSQLファイル自動実行用のDataSourceInitializer
JavaConfigなので、@Configurationをクラスに付与する。
また@MapperScanで、ownDBにSQLアクセスするmapper interface群が格納されているpackageの指定と、SqlSessionTemplateがどのインスタンスを参照すればいいかを指定する。
OtherDataSourceConfiguration.java
import java.util.Optional;
import javax.sql.DataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.init.DataSourceInitializer;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
@Configuration
@MapperScan(basePackages = "jp.co.sample.mapper.other", sqlSessionTemplateRef = "otherSqlSessionTemplate")
public class OtherDataSourceConfiguration {
@Autowired
private OtherProperties otherProperties;
@Autowired
private ApplicationContext context;
@Bean(name = "otherDataSource")
@ConfigurationProperties(prefix = "spring.other.datasource")
public DataSource dataSource() {
return otherProperties.getDatasource();
}
@Bean(name = "otherSqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("otherDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
// MyBatis のコンフィグレーションファイル
bean.setConfigLocation(context.getResource(context.getEnvironment().getProperty("mybatis.config")));
return new SqlSessionTemplate(bean.getObject());
}
@Bean(name = "otherDataSourceInitializer")
public DataSourceInitializer dataSourceInitializer(@Qualifier("otherDataSource") DataSource dataSource) {
DataSourceInitializer dataSourceInitializer = new DataSourceInitializer();
dataSourceInitializer.setDataSource(dataSource);
ResourceDatabasePopulator databasePopulator = new ResourceDatabasePopulator();
// schema,dataをpropertiesファイルで指定しない場合、Location must not be nullが出るため、nullの場合は処理を行わないように修正(2016/06/28)
// databasePopulator.addScripts(
// context.getResource(otherProperties.getDatasource().getSchema()),
// context.getResource(otherProperties.getDatasource().getData()));
Optional.ofNullable(otherProperties.getDatasource().getSchema())
.map(schema -> context.getResource(schema))
.ifPresent(resource -> databasePopulator.addScript(resource));
Optional.ofNullable(otherProperties.getDatasource().getData())
.map(data -> context.getResource(data))
.ifPresent(resource -> databasePopulator.addScript(resource));
dataSourceInitializer.setDatabasePopulator(databasePopulator);
dataSourceInitializer.setEnabled(true);
return dataSourceInitializer;
}
}
otherDB用。各種名称、参照がownからotherになっている以外はOwnDataSourceConfiguration.javaと同じ。
以上で、application.propertiesで複数DataSourceの設定が行えるようになった。
次はApplication.javaでのAnnotationの要変更箇所を見ていく。
Application.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import jp.co.sample.system.config.db.OwnDBProperties;
import jp.co.sample.system.config.db.OtherDBProperties;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableConfigurationProperties({DataSourceProperties.class, OwnDBProperties.class, OtherDBProperties.class})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Spring Bootアプリとして実行するために、Application.javaで@SpringBootApplicationを付与しているが、@SpringBootApplicationが付与されていると自動的にDataSource関連の設定を行ってくれる。
しかし、その設定を行ってくれるクラスDataSourceAutoConfiguration.classは単一DBの設定を前提としているため、@SpringBootApplicationのexcludeに設定することで、DataSourceAutoConfiguration.classが動かないようにしている。
@EnableConfigurationPropertiesは、@ConfigurationPropertiesを付与されたbeansが働くようにするために必要な設定である。
今回独自に追加したOwnDBProperties.class, OtherDBProperties.classを設定するのに加え、本来DataSourceAutoConfiguration.classで設定されていたデフォルトのDataSourceProperties.classも設定する。
Javaのコード部分は以上で終わりとなる。
最後に/src/main/resourcesを見ていく。
CustomerMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="jp.co.sample.mapper.own.CustomerMapper">
<select id="selectAll" resultMap="Customer">
SELECT *
FROM customer
</select>
</mapper>
xmlファイルで注意する点はが指定するmapper interfaceがどのディレクトリにあるかということだけ。
jp.co.sample.mapper.own.CustomerMapperが正解で、jp.co.sample.mapper.CustomerMapperやjp.co.sample.mapper.other.CustomerMapperというように格納されているディレクトリ名をコピペで処理して間違えないようにしなければいけない。
mybatis-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>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
<typeAliases>
<package name="jp.co.sample.domain" />
</typeAliases>
<typeHandlers>
<typeHandler handler="jp.co.sample.mapper.handler.LocalDateTypeHandler" />
<typeHandler handler="jp.co.sample.mapper.handler.LocalDateTimeTypeHandler" />
</typeHandlers>
</configuration>
mybatis-config.xmlは複数DBになったことによる影響はない。理由は今回についてはdomainディレクトリ以下をownとotherで分けなかったため、typeAliasesのpackage nameに影響がなかったから。
domain以下もディレクトリを分けるのであれば、mybatis-config-own.xmlとmybatis-config-other.xmlの二つに分けるのが良いと思う。そしてmybatis-configのファイルパス指定をapplication.propertiesで行うのではなく、OwnDataSourceConfiguration、OtherDataSourceConfigurationで直指定すればいい。
application.properties
#Mybatis設定
mybatis.config=classpath:mybatis-config.xml
#接続情報
spring.own.datasource.url=jdbc:h2:mem:own;
#初期設定
spring.own.datasource.schema=classpath:schema_own.sql
spring.own.datasource.data=classpath:data_own.sql
#コネクションプール設定
spring.own.datasource.max-active=100
spring.own.datasource.max-idle=8
spring.own.datasource.min-idle=8
spring.own.datasource.initial-size=8
#コネクションを利用する際に検証を行う。DBが再起動していてもこの処理を挟むことでtomcatを再起動しなくても済む
spring.own.datasource.test-on-borrow=true
spring.own.datasource.validation-query=SELECT 1
#コミットされずに残ったコネクションは60秒後に破棄される。
spring.own.datasource.remove-abandoned=true
spring.own.datasource.remove-abandoned-timeout=60
#auto commit
spring.own.datasource.default-auto-commit=true
#接続情報
spring.other.datasource.url=jdbc:h2:mem:other;
#初期設定
spring.other.datasource.schema=classpath:schema_other.sql
spring.other.datasource.data=classpath:data_other.sql
#コネクションプール設定
spring.other.datasource.max-active=100
spring.other.datasource.max-idle=8
spring.other.datasource.min-idle=8
spring.other.datasource.initial-size=8
#コネクションを利用する際に検証を行う。DBが再起動していてもこの処理を挟むことでtomcatを再起動しなくても済む
spring.other.datasource.test-on-borrow=true
spring.other.datasource.validation-query=SELECT 1
#コミットされずに残ったコネクションは60秒後に破棄される。
spring.other.datasource.remove-abandoned=true
spring.other.datasource.remove-abandoned-timeout=60
#auto commit
spring.other.datasource.default-auto-commit=true