I made a command line application that connects to DB with Spring Boot. Although it is a common application in itself, it is rare that DB connection information is implemented so that it can be specified by command parameters, so I will summarize the implementation method.
ʻOrg.springframework.boot.CommandLineRunner` If you implement a class that implements the interface, Spring Boot will call it after the boot process.
@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, "Display a help message.");
options.addOption("h", "host", true, "Specify the DB host name. This value The default value is\"localhost\"is.");
options.addOption("p", "port", true, "Specify the DB port number. This value The default value is\"5432\"is.");
options.addOption("U", "dbUserId", true, "Specify the DB user ID. This value The default value is\"postgres\"is.");
options.addOption("P", "dbPassword", true, "Please specify the DB password. This value The default value is\"postgres\"is.");
options.addOption("d", "dbName", true, "Please specify the database name. This value The default value is\"postgres\"is.");
options.addOption("k", "key", true, "Please specify the key for data search. This value is required.");
//Optional analysis
CommandLine cl = null;
try {
cl = new DefaultParser().parse(options, args);
} catch (UnrecognizedOptionException e) {
System.err.println("An unknown option is specified.[" + e.getMessage() + "]");
} catch (MissingArgumentException e) {
System.err.println("No optional arguments have been entered.[" + e.getMessage() + "]");
}
if (cl == null || cl.hasOption("?")) {
new HelpFormatter().printHelp(HELP_MESSAGE, options);
return;
}
//Get database connection information
DataBaseInfo.create(cl);
if (DataBaseInfo.instance == null) {
new HelpFormatter().printHelp(HELP_MESSAGE, options);
return;
}
//Get data for data search
ParamInfo paramInfo = ParamInfo.create(cl);
if (paramInfo == null) {
new HelpFormatter().printHelp(HELP_MESSAGE, options);
return;
}
//Run the service
MainService service = context.getBean(MainService.class);
long count = service.execute(paramInfo);
System.out.println("Key(" + paramInfo.key + ") Search results:" + count + "Case");
}
}
I implemented it like this.
I will explain @Lazy (false)
later, but I used Apache Commons CLI to analyze the parameters.
When I display the help, it looks like this.
usage: java -jar cli-examples.jar -k <key> [option]
-?,--help Displays help messages.
-d,--dbName <arg>Please specify the database name. This value The default value is"postgres"is.
-h,--host <arg>Specify the DB host name. This value The default value is"localhost"is.
-k,--key <arg>Please specify the key for data search. This value is required.
-p,--port <arg>Specify the DB port number. This value The default value is"5432"is.
-P,--dbPassword <arg>Please specify the DB password. This value The default value is"postgres"is.
-U,--dbUserId <arg>Specify the DB user ID. This value The default value is"postgres"is.
For Python, the parameter analysis class is built into the standard library, so please include it in Java as well.
The analysis process of the input parameter is as follows in the above source.
DataBaseInfo.create(cl);
Since Spring Boot initializes the component at startup, if the DB access related class is DI to the registered component, the data source will be initialized and an error will occur.
Therefore, in Lazy Initialization implemented from Spring Boot v2.2.0, data source initialization process should be performed at the time of component acquisition.
To set it, just add the setting of spring.main.lazy-initialization
to ʻapplication.yml`.
spring:
main:
banner-mode: log
lazy-initialization: true
So, the data source instantiation process is as follows.
@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("Initialization of DB connection information is not completed.");
}
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;
}
}
Also, in the ʻexamples.cli.MainRunnerclass that is performing the initial processing, Lazy Initialization is disabled by adding
@Lazy (false). (It may not make sense to add
@Lazy (false)` because it will be called immediately anyway)
ʻExamples.cli.MainRunner` class is executed before input parameter analysis, so if you DI a class that normally accesses DB, an error will occur at startup.
Therefore, the acquisition of the class that performs DB access is implemented so that it is acquired directly from ApplicationContext.
//Run the service
MainService service = context.getBean(MainService.class);
long count = service.execute(paramInfo);
By implementing in this way, the data source initialization process can be performed after command parameter analysis.
The classes that perform DB access are as follows.
@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;
}
}
If Lazy Initialization isn't available for some reason, you can do so by using ʻorg.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource`, which can dynamically switch data sources.
Delete the setting of spring.main.lazy-initialization
and implement the data source initialization process as follows.
@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();
}
}
}
A mechanism in which an empty data source is set in the initialization process at startup, and the data source is switched when the ʻinitDataSource` method is called after command parameter analysis.
There may be a better way, but that's the limit of what you can find out.
@EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class })
The implemented source is as follows.
The sites referred to this time are as follows.
-Creating a CLI app with Spring Boot -I tried Lazy Initialization with Spring Boot 2.2.0 -[Spring Boot] Dynamic data source change
The first post was Spring Boot, which is very convenient. I think I'll post something like this instead of a memo.
Recommended Posts