[Java][Spring Boot] 複数のデータベースに接続する。

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

異なるデータベース間でレコードを移行したい場合、アプリ内でそれぞれのデータベースに接続する訳ですが、Springではどうやって実現するのかを調べたので備忘録として記しておきます。

環境

Mac OSX 10.10.5 Yosemite
Java 1.8.0_91
Spring Boot 1.3.7
PostgreSQL 9.5.1
Eclipse Mars 2

目標

ローカルのPostgreSQLの対象テーブルから全レコードを取得し、ローカルのMySQLに挿入する。

テーブル用意

移行元:PostgreSQL

CREATE TABLE fruit (
  id INTEGER PRIMARY KEY, 
  name VARCHAR(15), 
  price INTEGER);

INSERT INTO fruit VALUES
  ('1','apple',300),
  ('2','orange',200),
  ('3','banana',100);
postgres=# select * from fruit order by id;
 id |  name  | price 
----+--------+-------
  1 | apple  |   300
  2 | orange |   200
  3 | banana |   100
(3 rows)

移行先:MySQL

CREATE TABLE fruit (
  id INTEGER PRIMARY KEY, 
  name VARCHAR(15), 
  price INTEGER);
mysql> select * from fruit;
Empty set (0.00 sec)

コード

依存関係

dependencies {
	compile('org.springframework.boot:spring-boot-starter-data-jpa')
	compile('org.springframework.boot:spring-boot-starter-jdbc')
	compile('org.projectlombok:lombok:1.16.6')
	runtime('mysql:mysql-connector-java')
	runtime('org.postgresql:postgresql')
	testCompile('org.springframework.boot:spring-boot-starter-test')
}

build.gradleの依存関係のみ抜粋。

データベース接続情報

spring: 
  datasource:
    primary:
      url : jdbc:postgresql://localhost:5432/postgres
      username : XXXXX
      password : YYYYY
      driverClassName : org.postgresql.Driver
    secondary:
      url : jdbc:mysql://localhost:3306/blog?useSSL=false
      username : XXXXX
      password : YYYYY
      driverClassName : com.mysql.jdbc.Driver

ファイルの配置はsrc/main/resources/config/application.yml。
上記はキャメルケースですが、スネークケース・チェインケースでもOKです。

エンティティ

package com.multi.database;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table(name="fruit")
public class Fruit {

	@Id
	private Integer id;
	private String name;
	private Integer price;

}

データソースの設定1(PostgreSQL)

package com.multi.database.primary;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import lombok.Getter;
import lombok.Setter;

@Getter 
@Setter
@Component
@ConfigurationProperties(prefix="spring.datasource.primary")
public class PrimaryConfiguration {
	
	private String driverClassName;
	private String url;
	private String username;
	private String password;

	@Bean @Primary
	public DataSource createDataSource() {
		return DataSourceBuilder
				.create()
				.driverClassName(driverClassName)
				.url(url)
				.username(username)
				.password(password)
				.build();
	}

	@Bean @Primary
	public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
		return new JdbcTemplate(dataSource);
	}

}

19行目、@ConfigurationProperties()
application.ymlのプレフィックスを指定すると、フィールドの変数に接続情報を設定できます。
ただし、対象の変数にSetterが必要なので、17行目でlombokの@Setterを付けています。

22〜25行目、変数
接続情報を設定する変数名はapplication.ymlと合わせます。
キャメルケース・スネークケースのどちらでも大丈夫の様です。

27〜36行目、createDataSource()
DataSourceを作成するメソッド。
27行目、@Primaryでメインの接続情報に設定。
29〜35行目、application.ymlから取得した情報をDataSourceBuilderに設定しています。

38〜41行目、createJdbcTemplate()
JdbcTemplateを作成するメソッド。
38行目、ここでもメインのJdbcTemplateと設定。
上記で作成したデータソースをJdbcTemplateにセットしています。

データソースの設定2(MySQL)

package com.multi.database.secondary;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Component
@ConfigurationProperties(prefix="spring.datasource.secondary")
public class SecondaryConfiguration {
	
	private String driverClassName;
	private String url;
	private String username;
	private String password;
	
	@Bean(name="secondaryds")
	public DataSource createDataSource() {
		return DataSourceBuilder
				.create()
				.driverClassName(driverClassName)
				.url(url)
				.username(username)
				.password(password)
				.build();
	}
	
	@Bean(name="secondaryjdbc")
	public JdbcTemplate createJdbcTemplate(@Qualifier("secondaryds") DataSource dataSource) {
		return new JdbcTemplate(dataSource);
	}

}

基本的にはPostgreSQLの場合と同じですが、DataSourceとJdbcTemplateを作成する各メソッドのBean名を設定しています。
これが無いと、PostgreSQLのDataSourceとJdbcTemplateが接続情報が上書きされてしまいます。

データ移行クラス

package com.multi.database;

import java.util.List;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class DatabaseMigration {

	@Autowired
	private JdbcTemplate postgresql;

	@Autowired @Qualifier("secondaryjdbc")
	private JdbcTemplate mysql;
	
	@Bean
	public String dataSourceCheck() {
		System.out.println("PostgreSQL : " + postgresql.getDataSource());
		System.out.println("MYSQL : " + mysql.getDataSource());
		return "check";
	}

	public List<Map<String, Object>> getAllFromPostgresql() {
		return postgresql.queryForList("SELECT * FROM fruit");
	}
	
	public void insertToMysql(List<Map<String, Object>> list) {
		int id, price;
		String name;
		
		for (int i = 0; i < list.size(); i++) {
			id = Integer.parseInt(list.get(i).get("id").toString());
			name = list.get(i).get("name").toString().toUpperCase();
			price = Integer.parseInt(list.get(i).get("price").toString());
			mysql.update("INSERT INTO fruit VALUES (?, ?, ?)", id, name, price);
		}
	}

	@Bean
	public String execute() {
		List<Map<String, Object>> list = getAllFromPostgresql();
		insertToMysql(list);
		return "execute";
	}
	
}

15〜16行目、18〜19行目、それぞれでPostgreSQLとMySQLのJdbcTemplateを接続していますが、MySQLはセカンダリとしているので@QualifierでBean名を指定する必要が有ります。

21〜26行目、dataSourceCheck()
各データソース情報を出力確認するだけのメソッド。

28〜30行目、getAllFromPostgrsql()
PostgreSQLから全レコードを取得してListで返すメソッド。

32〜42行目、insertToMysql()
作成したListをMySQLに挿入するメソッド。
forループでListから取得してPreparedStatementのINSERT文に設定しています。

44〜49行目、execute()
PostgreSQLとMySQLの処理を繋げるメソッド。

起動クラス

package com.multi.database;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MultiDatabaseApplication {

	public static void main(String[] args) {
		SpringApplication.run(MultiDatabaseApplication.class, args);
	}
	
}

実行結果

mysql> select * from fruit;
+----+--------+-------+
| id | name   | price |
+----+--------+-------+
|  1 | APPLE  |   300 |
|  2 | ORANGE |   200 |
|  3 | BANANA |   100 |
+----+--------+-------+
3 rows in set (0.00 sec)

MySQLのテーブルfruitに取得レコードが登録されました。

さいごに

DIコンテナに登録されているBeanを呼び出すには必要に応じてBean名を指定するのが有効だと分かりました。
特に、今回のような同一クラスを複数使用する場合には必須なのかもしれません。