If you're anything like me, you HATE long compilation times. Frequently, we find ourselves in an absurd situation where we discover a new programming language that we love so much, but it also has slower compilation times when compared to its predecessor. Prime examples are Kotlin and TypeScript. It also seems that compilation times are just getting slower, version by version. It's a sad, sad world.
As the famous XKCD comic jokes:
If you're also like me, you absolutely LOVE good tooling. Good tooling has the potential to make our lives easier, enable us to lose less time waiting or doing empty work. HotSwap for JVM or TypeScript Language Server comes to mind.
The problem with Spring Boot and databases
There's no such thing as a free lunch. Sure, Spring Boot has awesome tooling, great development experience. But, it is famous for its NOTORIOUSLY bad start-up time. Some Spring Boot apps take longer to start than my entire OS. It's just hilarious.
Oftentimes, a major contributor to the start-up time is initializing the connection to the database. If you're using Flyway or Liquibase, it is even worse, as it takes time for them to validate your database, or change it if needed.
To waste some more of your precious time, people usually configure their local development environment to use an external database, so you also need to start this as well. Sure, it's good to use the same database for your local development as you do in production, but it's so unnecessary. Configure your integration tests to use that database to check for any potential errors.
Need to modify your database schema? Good luck restarting your DB instance each and every time you make a change.
Using H2 in-memory DB for local development with Gradle
The solution to the mentioned problem is to just use an in-memory database for local development. It's going to make your life so much easier.
Let's see how we can configure Gradle to use H2, but only for local development.
As an example, let's say that we have an application with dependencies that look like this, in your build.gradle
, with PostgreSQL as your production database:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
You can just add the H2 dependency to this section, but that would mean that in production, your app is going to have the H2 included in it as well. Let's not do that. It's filthy.
By default, the java
plugin in build.gradle
declares two source sets, main
and test
.
What we're going to do is add a new sourceSet
and configuration
just for local development in build.gradle
. We can call it localH2
. You will need to add this piece of code before declaring your dependencies.
configurations {
localH2Implementation.extendsFrom implementation
localH2RuntimeOnly.extendsFrom runtimeOnly
}
sourceSets {
localH2 {
compileClasspath += sourceSets.main.output
runtimeClasspath += sourceSets.main.output
}
}
This configuration enables us to define dependencies that are specific only to our local environment. But, it includes the whole main source set, so you'll have all the dependencies that you included normally.
We can now add the H2 dependency, like this:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'org.postgresql:postgresql'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
localH2RuntimeOnly 'com.h2database:h2'
}
We're almost there, but now, in our localH2
source set, we also have the PostgreSQL driver included. To remove that dependency, we can use this block of code:
configurations.localH2Implementation {
exclude group: 'org.postgresql', module: 'postgresql'
}
Ah, now everything is so clean.
Running a Spring Boot application with the local configuration
We now have two classpaths separated, for the production code and for our local development. But, we haven't explored how we can run this local configuration from your machine. If you just run the application through your IDE or with ./gradlew bootRun
, it's just going to use the production classpath.
If you're using IntelliJ, you'll have to make a small change in your Run Configuration to use the new classpath.
If you're running your app with ./gradlew bootRun
, don't worry, it's a pretty simple change as well.
You can add a new Gradle task to your build.gradle
to run it with the new classpath, like so:
task localH2(type: org.springframework.boot.gradle.tasks.run.BootRun){
mainClass = "gradle.springboot.h2.local.example.ExampleApplication"
classpath = sourceSets.localH2.runtimeClasspath
}
The mainClass
property should contain a reference to your main Java class. You can now run your app by calling ./gradlew localH2
. Isn't it nice?
Even better, having H2 in the classpath configures your DataSource automatically, so there's no additional configuration to be added.
Important note
If you had your DataSource configured in your application.yml
/ application.properties
, you should pull that config out into a new profile (let's say, postgres
?), so that it's not loaded by default.
Final thoughts
Get ready to indulge in the newfound time that you didn't even know you had. Too many times, have we, as developers, had to wait for that slow Spring Boot app to start.
There are at least some ways to make our lives easier and isn't that the whole point of DevOps and the rapid development culture of today?
Be safe!
The accompanying code can be found at the Github Repository.
Top comments (0)