Skip to content
thesarfo

Recipe

Connecting to H2 (Database & Console)

Wiring up the H2 in-memory database and its web console in a Spring Boot app, plus a full JdbcClient-based repository example.

views 0

To connect to H2 and its console, add the JDBC starter and H2 dependencies:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

Then configure the database properties — here just the name and enabling the console:

Terminal window
spring.application.name=runnerz-api
spring.h2.console.enabled=true
spring.datasource.generate-unique-name=false
spring.datasource.name=runnerz-api

Restarting the app prints a generated database URL like jdbc:h2:mem:runnerz-api. Copy that, navigate to localhost:8080/h2-console, paste it in, and connect.

The JDBC API lets you run SQL queries directly from your repository code. A full CRUD repository using JdbcClient:

@Repository
public class RunRepository {
private static final Logger log = LoggerFactory.getLogger(RunRepository.class);
private final JdbcClient jdbcClient;
public RunRepository(JdbcClient jdbcClient) {
this.jdbcClient = jdbcClient;
}
public List<Run> findAll() {
return jdbcClient.sql("select * from run")
.query(Run.class)
.list();
}
public Optional<Run> findById(Integer id) {
return jdbcClient.sql("SELECT id,title,started_on,completed_on,miles,location FROM Run WHERE id = :id")
.param("id", id)
.query(Run.class)
.optional();
}
public void create(Run run) {
var updated = jdbcClient.sql("INSERT INTO Run(id,title,started_on,completed_on,miles,location) values(?,?,?,?,?,?)")
.params(List.of(run.id(), run.title(), run.startedOn(), run.completedOn(), run.miles(), run.location().toString()))
.update(); // update() is used for create, update, and delete — they're all a form of "update" against the database
Assert.state(updated == 1, "Failed to create run " + run.title());
}
public void update(Run run, Integer id) {
var updated = jdbcClient.sql("update run set title = ?, started_on = ?, completed_on = ?, miles = ?, location = ? where id = ?")
.params(List.of(run.title(), run.startedOn(), run.completedOn(), run.miles(), run.location().toString(), id))
.update();
Assert.state(updated == 1, "Failed to update run " + run.title());
}
public void delete(Integer id) {
var updated = jdbcClient.sql("delete from run where id = :id")
.param("id", id)
.update();
Assert.state(updated == 1, "Failed to delete run " + id);
}
public int count() {
return jdbcClient.sql("select * from run").query().listOfRows().size();
}
public void saveAll(List<Run> runs) {
runs.stream().forEach(this::create);
}
public List<Run> findByLocation(String location) {
return jdbcClient.sql("select * from run where location = :location")
.param("location", location)
.query(Run.class)
.list();
}
}

This skips over other setup steps (like seeding data), and since H2 here is in-memory, all data is lost on restart — fine for development, but switch to a real database for production.