[JAVA] Spring Boot application that specifies DB connection settings with parameters

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.

environment

Initial processing

ʻ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.

Initialize DB connection settings

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)

Class for DB access

ʻ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;
    }

}

Implementation method without Lazy Initialization

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 })

Source

The implemented source is as follows.

cli-examples

reference

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

in conclusion

The first post was Spring Boot, which is very convenient. I think I'll post something like this instead of a memo.

Recommended Posts

Spring Boot application that specifies DB connection settings with parameters
Spring Boot DB connection pool
Inquiry application creation with Spring Boot
Processing at application startup with Spring Boot
Start web application development with Spring Boot
Launch Nginx + Spring Boot application with docker-compose
Run WEB application with Spring Boot + Thymeleaf
Spring Boot2 Web application development with Visual Studio Code SQL Server connection
Configure Spring Boot application with maven multi module
Spring Boot 2.3 Application Availability
Download with Spring Boot
Settings for connecting to MySQL with Spring Boot + Spring JDBC
Try using OpenID Connect with Keycloak (Spring Boot application)
Spring Boot with Spring Security Filter settings and addictive points
A story that stumbled when deploying a web application created with Spring Boot to EC2
Generate barcode with Spring Boot
Implement GraphQL with Spring Boot
Try DB connection with Java
Get started with Spring boot
Implement REST API with Spring Boot and JPA (Application Layer)
DB connection settings Spring-Boot application.properties
Hello World with Spring Boot!
[Spring Boot] Web application creation
Run LIFF with Spring Boot
SNS login with Spring Boot
Spring Boot Hot Swapping settings
File upload with Spring Boot
Spring Boot starting with copy
Sample web application that handles multiple databases in Spring Boot 1.5
CICS-Run Java application-(4) Spring Boot application
Spring Boot starting with Docker
Spring Boot DB related tips
Set cookies with Spring Boot
Use Spring JDBC with Spring Boot
Add module with Spring Boot
Getting Started with Spring Boot
Spring Boot performance related settings
Create microservices with Spring Boot
Send email with spring boot
Introduction of library ff4j that realizes FeatureToggle with Spring Boot
Let's make a book management web application with Spring Boot part1
Let's make a book management web application with Spring Boot part3
Let's make a book management web application with Spring Boot part2
[Beginner] Let's write REST API of Todo application with Spring Boot
Use Basic Authentication with Spring Boot
Spring Boot application development in Eclipse
Spring Boot application code review points
gRPC on Spring Boot with grpc-spring-boot-starter
Create an app with Spring Boot 2
Hot deploy with Spring Boot development
Database linkage with doma2 (Spring boot)
Spring Boot programming with VS Code
Until "Hello World" with Spring Boot
Get validation results with Spring Boot
(Intellij) Hello World with Spring Boot
Create an app with Spring Boot
Google Cloud Platform with Spring Boot 2.0.0
Check date correlation with Spring Boot
Implement Spring Boot application in Gradle
I tried GraphQL with Spring Boot
[Java] LINE integration with Spring Boot