h2 JDBC

https://github.com/spring-boot-tutorials/spring-data-h2-jdbc

In this article we will configure Spring Boot to connect to an h2 database via JDBC.

Create Initial Code Base

Dependencies

Dependencies used in pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>9.1.0</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.38</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

Properties

In src/main/resources/application.properties let’s add the following properties so the Spring Boot application can connect to the database

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

Model

Let’s create a new POJO src/main/java/com/example/Person.java

@Data
@SuperBuilder
public class Person {
    private Long id;
    private String firstName;
    private String lastName;
}

RowMapper

Next we will create a RowMapper to convert ResultSet into our POJO Person.

This file will be called src/main/java/com/example/PersonRowMapper.java

public class PersonRowMapper implements RowMapper<Person> {
    @Override
    public Person mapRow(ResultSet rs, int rowNum) throws SQLException {
        return Person.builder()
                .id(rs.getLong("ID"))
                .firstName(rs.getString("FIRST_NAME"))
                .lastName(rs.getString("LAST_NAME"))
                .build();
    }
}

Sql Exception Handler

Create a custom SQLErrorCodeSQLExceptionTranslator to handle any SQL execution errors:

public class CustomSQLErrorCodeTranslator extends SQLErrorCodeSQLExceptionTranslator {
    @Override
    protected DataAccessException customTranslate(String task, String sql, SQLException sqlException) {
        if (sqlException.getErrorCode() == 23505) {
            return new DuplicateKeyException("Custom Exception translator - Integrity constraint violation.", sqlException);
        }
        return null;
    }
}

Main

Now let’s use this repository.

Go back to MainApplication.java and add the following:

@Configuration
@SpringBootApplication
public class MainApplication implements CommandLineRunner {

    public static void main(String[] args) {
            SpringApplication.run(MainApplication.class, args);
    }

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

    @Autowired
    private DataSource dataSource;

    @Override
    public void run(String... args) {
            // Custom Exception Handling
            jdbcTemplate.setExceptionTranslator(new CustomSQLErrorCodeTranslator());

            // 1. Simple Inserts
            jdbcTemplate.execute("INSERT INTO PERSON(first_name, last_name) VALUES('Victor', 'Hugo')");
            jdbcTemplate.update("INSERT INTO PERSON(first_name, last_name) VALUES (?, ?)", "Bill", "Gates");

            // 2. Simple Query
            int result = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM PERSON", Integer.class);
            System.out.println("2. Number of Persons, " + result);

            // 3. Named Parameter Query
            SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("id", 1);
            String firstName = namedParameterJdbcTemplate.queryForObject("SELECT FIRST_NAME FROM PERSON WHERE ID = :id", namedParameters, String.class);
            System.out.println("3. Person with ID=1 has name=" + firstName);

            // 4. RowMapper
            String query = "SELECT * FROM PERSON WHERE ID = ?";
            Person person = jdbcTemplate.queryForObject(query, new PersonRowMapper(), 1);
            System.out.println("4. " + person.toString());

            // 5. SimpleJDBC
            SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource).withTableName("PERSON");
            Map<String, Object> parameters = new HashMap<>();
            parameters.put("ID", 1000);
            parameters.put("FIRST_NAME", "Jesus");
            parameters.put("LAST_NAME", "Christ");
            int i = simpleJdbcInsert.execute(parameters);
            System.out.println("5. SimpleJDBC returned i=" + i);

            // 6. SimpleJDBC with Generated Key Columns
            simpleJdbcInsert = new SimpleJdbcInsert(dataSource).withTableName("PERSON")
                            .usingGeneratedKeyColumns("ID");
            parameters = new HashMap<>();
            parameters.put("FIRST_NAME", "Jesus");
            parameters.put("LAST_NAME", "Christ");
            Number id = simpleJdbcInsert.executeAndReturnKey(parameters);
            System.out.println("6. SimpleJDBC with Generated Key Columns return id=" + id.longValue());

            // 7. Stored Procedure Calls
//          SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(dataSource).withProcedureName("READ_EMPLOYEE");
//          SqlParameterSource in = new MapSqlParameterSource().addValue("in_id", id);
//          Map<String, Object> out = simpleJdbcCall.execute(in);
//          System.out.println("7. " + out);

            // 8. Batch JdbcTemplate
            List<Person> people = List.of(
                            Person.builder().id(100L).firstName("Person100").lastName("Person100").build(),
                            Person.builder().id(101L).firstName("Person101").lastName("Person101").build(),
                            Person.builder().id(102L).firstName("Person102").lastName("Person102").build(),
                            Person.builder().id(103L).firstName("Person103").lastName("Person103").build()
            );
            int[] batched = jdbcTemplate.batchUpdate("INSERT INTO PERSON VALUES (?, ?, ?)",
                            new BatchPreparedStatementSetter() {
                                    @Override
                                    public void setValues(PreparedStatement ps, int i) throws SQLException {
                                            ps.setLong(1, people.get(i).getId());
                                            ps.setString(2, people.get(i).getFirstName());
                                            ps.setString(3, people.get(i).getLastName());
                                    }
                                    @Override
                                    public int getBatchSize() {
                                            return 4;
                                    }
                            });
            System.out.println("8. " + Arrays.toString(batched));

            // 9. Batch NamedParameterJdbcTemplate
            SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(List.of(
                            Person.builder().id(104L).firstName("Person104").lastName("Person104").build(),
                            Person.builder().id(105L).firstName("Person105").lastName("Person105").build(),
                            Person.builder().id(106L).firstName("Person106").lastName("Person106").build(),
                            Person.builder().id(107L).firstName("Person107").lastName("Person107").build()
            ).toArray());
            int[] updateCounts = namedParameterJdbcTemplate.batchUpdate("INSERT INTO PERSON VALUES (:id, :firstName, :lastName)", batch);
            System.out.println("9. " + Arrays.toString(updateCounts));
    }
}

Run Application

Open terminal at project root and execute the following:

mvn spring-boot:run

There should be no errors and the output will display all the CRUD operations.