Spring Boot run command line and terminate - java

I have a large Spring Boot monolithic Web Application project. This application is packaged as an executable JAR and serves all kinds of JSON REST endpoints.
Now, I sometimes want to run a piece of Java code to process or import a large file, or clean up certain database tables from the command line.
What would be good way to do this with Spring Boot?
I first looked into CommandLineRunner interface but this seems to serve a completely different use case. This is executed always when running the Spring Boot application, followed by starting the main application.
I would like to have this functionality in the same application as the main web app for various reasons:
Reuse same application configuration (DB credentials, external config files, etc.)
Reuse application context
Shared application logic and code
Difficult to split into smaller (micro) services

If you want to reuse the same jar, you could use a combination of Profiles and CommandLineRunners.
#Configuration
public class BatchConfig {
#Bean
#Profile("import")
public CommandLineRunner import() {
// ...
}
#Bean
#Profile("dbClean")
public CommandLineRunner dbClean() {
// ...
}
}
Then, when you run the jar, pass the desired profile as argument.
java -jar -Dspring.profiles.active=dbClean yourJar.jar
In this way, your command line runners are executed only when the profile matches.

Related

With Spring Boot 2.2+, how can you create a bean when the application is NOT running on a Cloud Platform

In Spring Boot 2.2 there is an annotation #ConditionalOnCloudPlatform which can be used to detect whether the application is running on a Cloud Platform.
I have the reverse issue - I want to not run a bean which is running on a Cloud Platform. For example, I have code which I don't want to run when running on Kubernetes (as the platform where I run Kubernetes already supplies the functions in the given bean).
What's the best approach to do this? I feel like I need a #ConditionalOnCloudPlatform annotation.
Thanks.
Use # ConditionalOnMissingBean with the name of Kubernetes bean of # ConditionalOnMissingClass with one of bean's (or related) classes.
Solution 1
It seems you are using Spring Cloud Kubernetes. If so, when an application runs as a pod inside Kubernetes a Spring profile named 'kubernetes' is automatically get activated.
#Component
#Profile("!kubernetes")
public class MyLocalTestBean {}
Solution 2
Invert condition using NonNestedConditions (the code below is not verified)
class ConditionalOnNotInCloud extends NoneNestedConditions {
ConditionalOnNotInCloud() {
super(ConfigurationPhase.PARSE_CONFIGURATION);
}
#ConditionalOnCloudPlatform
static class OnCloudCondition {
}
}
#Bean
#Conditional(ConditionalOnNotInCloud.class)
public StubBean createStubBean() {}

Unit testing a Configuration in spring Batch which runs in spring boot application

I am Using Spring boot application to run spring batch and I have a Config java class which loads all configuration related stuff like getting some values from properties file and setting them, calling reader,writers etc.
I am trying to run a junit for just few methods in that config class from my unit test class. how can we achieve that. I am trying to use SpringRunner Can someone through some light .

Spring Boot: run liquibase migrations without starting app

In Spring Boot, the documentation seems to encourage running migrations on app startup.
This is fine, but sometimes app startup may have side effects / dependencies that I don't want to bother with - I just want to run the migrations on their own. Think just setting up a local dev database, to poke around in it, without even running the app.
In Dropwizard by comparison, running migrations alone is straightforward with built in arguments for the app, like so
java -jar hello-world.jar db migrate helloworld.yml
Is there anything equivalent for Spring Boot? Or do I just have to drop down and run liquibase directly?
I'm interested in a direct answer, but also kind of interested in seeing if I'm misunderstanding something at a higher level - like perhaps this approach of running on startup is generally 'better' for some reasons I haven't discovered yet, so you're encouraged solely to do it this way by Spring Boot as a design choice.
You can use different Spring profiles: For instance, use a profile called 'init' which will activate the 'liquibase' profile.
application.yml: (disable Liquibase by default)
spring:
liquibase:
enabled: false
application-init.yml: (does not run a web container, so spring will close automatically after startup)
spring:
profiles:
include: liquibase
main:
web-application-type: none
spring-liquibase.yml: (enables liquibase)
spring:
liquibase:
enabled: true
change-log: classpath:/db/changelog/changelog.xml
This setup allows you to run Liquibase as an init container (spring.profiles.active=init), but if you want to you can still run Liquibase as part of your web-app (spring.profiles.active=liquibase).
I know this is an old question, but in case someone else stumbles upon it, this might be useful.
You can define a command line argument for your app, that you will use to only spin up a portion of the app context that will run migration.
Here's an example in Kotlin:
import org.springframework.boot.ApplicationContextFactory.ofContextClass
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.runApplication
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Import
#SpringBootApplication
class Application
#Import(DataSourceAutoConfiguration::class, LiquibaseAutoConfiguration::class)
class LiquibaseInit
fun main(args: Array<String>) {
if (args.contains("dbinit")) {
SpringApplicationBuilder(LiquibaseInit::class.java)
.contextFactory(ofContextClass(AnnotationConfigApplicationContext::class.java))
.run(*args)
return
}
runApplication<Application>(*args)
}
Note: with Spring Boot 2.4.0 and earlier use SpringApplicationBuilder#contextClass instead of contextFactory.
We have 2 classes declared here: Application (the main app class having #SpringBootApplication annotation) and LiquiBaseInit (having #DataSourceAutoConfiguration and #LiquibaseAutoConfiguration), the first one will spin up the whole context, while the latter will only spin up the beans necessary for Liquibase to run migration.
Inside the main function we check if the arguments array has a string dbinit and if it is there we start an application context out of LiquiBaseInit class.
Now you can run migration with your jar file like this:
java -jar hello-world.jar dbinit
If you're going to run your app in Kubernetes, you might also want to check out this article in my blog: https://blog.monosoul.dev/2021/12/26/using-liquibase-with-kubernetes/ .
This answer mentions a hook that runs after Liquibase. In that question, it was used to populate the database, probably with test or default values.
#Bean
#DependsOn("liquibase")
public YourBean yourBean() {
return new YourBean();
}
static class YourBean {
#PostConstruct
public void shutdown() {
// Exit your application here.
);
}
}
That could work. I don't know, you'd probably even have access to the liquibase mode and only shut down when it was "create."
In general, it might be just less work to run migrations on app start, especially if you're using Spring Boot and its all set up for you. You can then just stop the app.
However if you really would like to run migrations without the Spring Boot app, you can use the Liquibase Gradle and Maven plugins:
https://github.com/liquibase/liquibase-gradle-plugin
https://docs.liquibase.com/tools-integrations/maven/home.html
This will require you to set up the database credentials in another config file, in addition to your application config, so that the plugins can connect to the database.
For this purpose we created our own script that allows not only to gen migrations but also to manually apply them, however it could be applied only from gradle, see:
https://github.com/Wissance/SpringUu
to apply update we use following command:
.\gradlew.bat update -PrunList='changesApply'
to specify db connection edit gradle section changesApply
For more details about our tool see article:
https://m-ushakov.medium.com/code-first-with-spring-boot-hibernate-and-liquibase-48f5c9998d95
U can set up and configure maven liquibase plugin:
https://docs.liquibase.com/tools-integrations/maven/commands/home.html
It allows to use migrations without starting spring boot app itself
can be set up as part of building artifact or like separate task

Spring & Maven - how to provide custom properties to Spring application

I have a Spring application which runs in a web container (Tomcat). This Spring application uses a properties file to find the database JDBC location:
#Configuration
#PropertySource("classpath:app.properties")
public class MyApplication {
}
In app.properties, I have:
database.dataSource.url=jdbc:postgresql://localhost:5432/app
It's now easy to get the value at runtime:
#Component
class DatabaseConfiguration {
#Value("${database.dataSource.url}")
private String URL;
}
So far, so good. Now I am using the cargo-maven2-plugin plugin to deploy the WAR during an integration test. Before the WAR is deployed, an ad-hoc PostgreSQL database is deployed into a Docker container via the docker-maven-plugin plugin. This instance runs on a custom, dynamic port instead of the usual 5432. This port is filled in into the ${database.port} property by the docker-maven-plugin plugin.
This means that I need to somehow alter app.properties on the fly to fill in this port. This seems hacky, so maybe there is a way to provide/override the port via the cargo-maven2-plugin to my Spring application, so I could use that one instead of the one in app.properties?
What is a 'clean' way to achieve this?
Use #TestPropertySource to use a different properties file for testing. https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/test/context/TestPropertySource.html

Embedding a standalone spring application jar in Spring boot application as single context

I have a external client Jar file which uses Spring core, uses ClasspathApplicationContext loader to load app-context.xml (also embeded in jar file).
The jar file is invoked using main method, and expects some arguments and runs as a stand alone java application.
I have my primary spring boot based rest application, I need to incorporate this stand alone application in my application and run it under one single context.
I plan to use functionality provided by client jar as a service, from my rest controller.
Any pointers or ideas on how to do this would be great.
Thanks..!!
Add the jar file as a dependency in the new rest application project and add #ImportResource annotation on your Spring boot main class inside your rest application and provide the xml files as classpath resources which needs to be loaded in the context. This creates all the beans defined in the xml contexts which u can autowired to build the functionalities in your new application.

Categories

Resources