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:
spring.application.name=runnerz-apispring.h2.console.enabled=truespring.datasource.generate-unique-name=falsespring.datasource.name=runnerz-apiRestarting 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:
@Repositorypublic 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.