DB接続設定をパラメータで指定するSpring Bootアプリケーション
Spring BootでDB接続を行うコマンドラインアプリケーションを作ってみた。
それ自体はよくあるアプリケーションなのだが、DBの接続情報はコマンドのパラメータで指定できるように実装されているのはあまり見かけないので実装方法を纏めておこうと思う。
環境
- OpenJDK 13.0.2
- Gradle 6.2.1
- Sprinig Boot 2.2.5
- Apache Commons CLI 1.4
初期処理
org.springframework.boot.CommandLineRunner
インタフェースを実装したクラスを実装するとSpring Bootさんが起動処理後に呼び出してくれるらしい。
- examples.cli.MainRunner
@Component
@Lazy(false)
public class MainRunner implements CommandLineRunner {
private static String HELP_MESSAGE = "java -jar cli-examples.jar -k <key> [option]";
@Autowired
ApplicationContext context;
@Override
public void run(String... args) throws Exception {
Options options = new Options();
options.addOption("?", "help", false, "ヘルプメッセージを表示します。");
options.addOption("h", "host", true, "DBのホスト名を指定してください。この値デフォルト値は\"localhost\"です。");
options.addOption("p", "port", true, "DBのポート番号を指定してください。この値デフォルト値は\"5432\"です。");
options.addOption("U", "dbUserId", true, "DBのユーザIDを指定してください。この値デフォルト値は\"postgres\"です。");
options.addOption("P", "dbPassword", true, "DBのパスワードを指定してください。この値デフォルト値は\"postgres\"です。");
options.addOption("d", "dbName", true, "データベース名を指定してください。この値デフォルト値は\"postgres\"です。");
options.addOption("k", "key", true, "データ検索時のキーを指定してください。この値は必須です。");
// オプションの解析
CommandLine cl = null;
try {
cl = new DefaultParser().parse(options, args);
} catch (UnrecognizedOptionException e) {
System.err.println("不明なオプションが指定されています。[" + e.getMessage() + "]");
} catch (MissingArgumentException e) {
System.err.println("オプション引数が入力されていません。[" + e.getMessage() + "]");
}
if (cl == null || cl.hasOption("?")) {
new HelpFormatter().printHelp(HELP_MESSAGE, options);
return;
}
// データベースの接続情報を取得する
DataBaseInfo.create(cl);
if (DataBaseInfo.instance == null) {
new HelpFormatter().printHelp(HELP_MESSAGE, options);
return;
}
// データ検索用のデータを取得する
ParamInfo paramInfo = ParamInfo.create(cl);
if (paramInfo == null) {
new HelpFormatter().printHelp(HELP_MESSAGE, options);
return;
}
// サービスを実行する
MainService service = context.getBean(MainService.class);
long count = service.execute(paramInfo);
System.out.println("キー(" + paramInfo.key + ")の検索結果:" + count + "件");
}
}
こんな感じで実装してみた。
@Lazy(false)
については後述するとして、パラメータの解析にはApache Commons CLIを使用した。
ヘルプを表示するとこんな感じになる。
usage: java -jar cli-examples.jar -k <key> [option]
-?,--help ヘルプメッセージを表示します。
-d,--dbName <arg> データベース名を指定してください。この値デフォルト値は"postgres"です。
-h,--host <arg> DBのホスト名を指定してください。この値デフォルト値は"localhost"です。
-k,--key <arg> データ検索時のキーを指定してください。この値は必須です。
-p,--port <arg> DBのポート番号を指定してください。この値デフォルト値は"5432"です。
-P,--dbPassword <arg> DBのパスワードを指定してください。この値デフォルト値は"postgres"です。
-U,--dbUserId <arg> DBのユーザIDを指定してください。この値デフォルト値は"postgres"です。
Pythonとかだと標準ライブラリにパラメータ解析クラスが組み込まれてるのでJavaでも組み込んでほしい。
DB接続設定の初期化
入力パラメータの解析処理は上記ソース内の以下の箇所。
DataBaseInfo.create(cl);
Spring Bootさんは起動時コンポーネントの初期化などを行うため、登録しているコンポーネントにDBアクセス関連のクラスをDIしているとデータソースの初期化などを行ってしまい、エラーが発生する。
そのため、Spring Boot v2.2.0から実装されたLazy Initializationでデータソース初期化処理をコンポーネント取得時に行うようにする。
設定方法はapplication.yml
にspring.main.lazy-initialization
の設定を追加するだけ。
- application.yml
spring:
main:
banner-mode: log
lazy-initialization: true
で、データソースのインスタンス生成処理は以下の通り。
- examples.cli.App
@SpringBootApplication
@Configuration
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Bean
public DataSource dataSource() {
if (DataBaseInfo.instance == null) {
throw new IllegalStateException("DB接続情報の初期化が完了していません。");
}
StringBuilder builder = new StringBuilder();
builder.append("jdbc:postgresql://");
builder.append(DataBaseInfo.instance.host).append(":").append(DataBaseInfo.instance.port);
builder.append("/").append(DataBaseInfo.instance.dbName);
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(org.postgresql.Driver.class);
dataSource.setUrl(builder.toString());
dataSource.setUsername(DataBaseInfo.instance.userId);
dataSource.setPassword(DataBaseInfo.instance.password);
Properties connectionProperties = new Properties();
connectionProperties.setProperty("autoCommit", "false");
dataSource.setConnectionProperties(connectionProperties);
return dataSource;
}
}
また、初期処理を行っているexamples.cli.MainRunner
クラスでは、@Lazy(false)
をつけてLazy Initializationを無効にしている。(どうせすぐ呼ばれるので@Lazy(false)
つける意味はないかもしれない)
DBアクセスを行うクラス
examples.cli.MainRunner
クラスでは入力パラメータ解析前に実行されるため、普通にDBアクセスを行うクラスをDIしていると起動時にエラーとなる。
そのため、DBアクセスを行うクラスの取得はApplicationContextから直接取得するように実装している。
// サービスを実行する
MainService service = context.getBean(MainService.class);
long count = service.execute(paramInfo);
このように実装することで、データソース初期化処理をコマンドパラメータ解析後に行えるようになっている。
DBアクセスを行うクラスは以下の通り。
@Component
public class MainServiceImpl implements MainService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
@Override
public long execute(ParamInfo paramInfo) {
Long selectCount = jdbcTemplate.queryForObject(
"select count(*) from main_tbl where key = ?",
new Object[] { paramInfo.key },
Long.class);
return selectCount;
}
}
Lazy Initializationを使わない実装方法
諸事情でLazy Initializationが使えない場合は、動的にデータソースを切り替えられるorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
を使用すれば実現できる。
spring.main.lazy-initialization
の設定の設定を削除してデータソース初期化処理を以下のように実装する。
@SpringBootApplication
@Configuration
public class App {
private static final String DS_NONE_KEY = "ds_none";
private static final String DS_PGSQL_KEY = "ds_pgsql";
static DynamicRoutingDataSourceResolver resolver = new DynamicRoutingDataSourceResolver();
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Bean
public DynamicRoutingDataSourceResolver dataSource() throws ClassNotFoundException {
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
Map<Object, Object> dataSources = new HashMap<>();
dataSources.put(DS_NONE_KEY, dataSource);
resolver.setTargetDataSources(dataSources);
resolver.setDefaultTargetDataSource(dataSource);
return resolver;
}
static class DynamicRoutingDataSourceResolver extends AbstractRoutingDataSource {
private String dataSourcekey = DS_NONE_KEY;
@Override
protected Object determineCurrentLookupKey() {
return dataSourcekey;
}
public void initDataSource(DataBaseInfo dbInfo) {
StringBuilder builder = new StringBuilder();
builder.append("jdbc:postgresql://");
builder.append(dbInfo.host).append(":").append(dbInfo.port);
builder.append("/").append(dbInfo.dbName);
SimpleDriverDataSource dataSource = new SimpleDriverDataSource();
dataSource.setDriverClass(org.postgresql.Driver.class);
dataSource.setUrl(builder.toString());
dataSource.setUsername(dbInfo.userId);
dataSource.setPassword(dbInfo.password);
Properties connectionProperties = new Properties();
connectionProperties.setProperty("autoCommit", "false");
dataSource.setConnectionProperties(connectionProperties);
Map<Object, Object> dataSources = new HashMap<>();
dataSources.put(DS_PGSQL_KEY, dataSource);
dataSourcekey = DS_PGSQL_KEY;
setTargetDataSources(dataSources);
setDefaultTargetDataSource(dataSource);
afterPropertiesSet();
}
}
}
起動時の初期化処理では空のデータソースを設定しておき、コマンドパラメータ解析後にinitDataSource
メソッドを呼び出すとデータソースが切り替わる仕組み。
もっといい方法があるかもしれないが、自分で調べられる限界はここまで。
※以下の設定を利用してDBの初期化処理を手動で行うとしたが、Lazy Initializationと同じような気がしたのでやらなかった。
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
ソース
実装したソースは以下の通り。
参考
今回参考にしたサイトは以下の通り。
おわりに
初投稿は大変便利なSpring Bootさんでした。
こんな感じでたわいもない事をメモ代わりに投稿していこうかと思う。
Author And Source
この問題について(DB接続設定をパラメータで指定するSpring Bootアプリケーション), 我々は、より多くの情報をここで見つけました https://qiita.com/specialweek1128/items/c7e60261aeb3e2567906著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .