Chapter 10. jdbc

10.1. jdbc: JDBC Simplification using the JdbcTemplate

10.2. Introduction

In this lab you will gain experience with Spring’s JDBC simplification. You will use a JdbcTemplate to execute SQL statements with JDBC.

What you will learn:

  1. How to retrieve data with JDBC
  2. How to insert or update data with JDBC

Specific subjects you will gain experience with:

  1. The JdbcTemplate class
  2. The RowMapper interface
  3. The ResultSetExtractor interface

Estimated time to complete: 45 minutes

10.3. Quick Instructions

Quick instructions for this exercise have been embedded within the lab materials in the form of TODO comments. To display them, open the Tasks view (Window >> Show View >> Tasks (not Task List)).

10.4. Detailed Instructions

10.4.1. Refactoring a repository to use JdbcTemplate

The goal for this lab is to refactor the existing JDBC based repositories from their standard try, catch, finally, try, catch paradigm to using Spring’s JdbcTemplate. The first repository to refactor will be the JdbcRewardRepository. This repository is the easiest to refactor and will serve to illustrate some of the key features available because of Spring’s simplification.

10.4.1.1. Use JdbcTemplate in a test to verify insertion

(TODO-01) Before making any changes to JdbcRewardRepository, let’s first ensure the existing functionality works by implementing a test. Open JdbcRewardRepositoryTests in the rewards.internal.reward package and notice the getRewardCount() method. In this method use the jdbcTemplate included in the test fixture to query for the number of rows in the T_REWARD table and return it.

(TODO-02) In the same class, find the verifyRewardInserted(RewardConfirmation, Dining) method. In this method, use the jdbcTemplate to query for a map of all values in the T_REWARD table based on the confirmationNumber of the RewardConfirmation. The column name to use for the confirmationNumber in the where clause is CONFIRMATION_NUMBER.

Finally run the test class. When you have the green bar, move on to the next step.

10.4.1.2. Refactor JdbcRewardRepository to use JdbcTemplate

(TODO-03) We are now going to refactor an existing Repository class so it can use the JdbcTemplate. To start find the JdbcRewardRepository in the rewards.internal.reward package. Open the class and add a private field to it of type JdbcTemplate. In the constructor, instantiate the JdbcTemplate and assign it to the field you just created.

Next refactor the nextConfirmationNumber() method to use the JdbcTemplate. This refactoring is a good candidate for using the queryForObject(String, Class<T>, Object…​) method.

[Tip] Tip

The Object…​ means a variable argument list allowing you to append an arbitrary number of arguments to a method invocation, including no arguments at all.

Next refactor the confirmReward(AccountContribution, Dining) method to use the template. This refactoring is a good candidate for using the update(String, Object…​) method.

Once you have completed these changes, run the test class again (JdbcRewardRepositoryTests) to ensure these changes work as expected. When you have the green bar, move on to the next step.

10.4.2. Using a RowMapper to create complex objects

10.4.2.1. Use a RowMapper to create Restaurant objects

(TODO-04) In many cases, you’ll want to return complex objects from calls to the database. To do this you’ll need to tell the JdbcTemplate how to map a single ResultSet row to an object. In this step, you’ll refactor JdbcRestaurantRepository using a RowMapper to create Restaurant objects.

Before making any changes, run the JdbcRestaurantRepositoryTests class to ensure that the existing implementation functions correctly. When you have the green bar, move on to the next step.

Next, find the JdbcRestaurantRepository in the rewards.internal.restaurant package. Open this class and again modify it so that it has a JdbcTemplate field.

Time to refactor the findByMerchantNumber(String) method to use the template.

Create a private inner class called RestaurantRowMapper that implements the RowMapper interface. Note that this interface has a generic type parameter that should be populated in the implementation.

If you’ve implemented the interface correctly, the class and method declarations should look like Figure 1. The implementation of the mapRow(ResultSet, int) method should delegate to the mapRestaurant(ResultSet) method.

RestaurantRowMapper class and method declaration. 

    private class RestaurantRowMapper implements RowMapper<Restaurant> {
        public Restaurant mapRow(ResultSet rs, int rowNum) throws SQLException {
            return mapRestaurant(rs);
        }
    }

Now refactor the findByMerchantNumber(String) method to use the template. This refactoring is a good candidate for using the queryForObject(String, RowMapper<T>, Object…​) method.

[Note] Note

If you prefer to use a lambda instead please do so. Have it delegate to the existing mapRestaurant(ResultSet) method.

Finally run the JdbcRestaurantRepositoryTests class. When you have the green bar, move on to the next step.

10.4.3. OPTIONAL STEP: Refactoring the JdbcAccountRepository

In this repository there are two different methods that need to be refactored: updateBeneficiaries(Account) and findByCreditCard(String).

Only do this section if you have enough time left. You will need 10-15 mins.

10.4.3.1. Refactoring a SQL UPDATE

Before making any changes run the JdbcAccountRepositoryTests class to ensure the existing implementation functions properly. When you have the green bar, move on.

(TODO-05) Next find the JdbcAccountRepository in the rewards.internal.account package. Open this class and again modify it so that it has a field of type JdbcTemplate.

(TODO-06) Start by refactoring the updateBeneficiaries(Account) method to use the JdbcTemplate. This refactoring is very similar to the one that you did earlier for the JdbcRewardRepository.

When you are done, rerun the JdbcAccountRepositoryTests tests. When you have the green bar, you are good.

10.4.3.2. EXTRA CREDIT: Use a ResultSetExtractor to traverse a ResultSet for creating Account objects

This is an optional step if you have at least 10 minutes of the lab remaining.

(TODO-07) Sometimes when doing complex joins in a query you’ll need to have access to an entire result set instead of just a single row of a result set to build a complex object. To do this you’ll need to tell the JdbcTemplate that you’d like full control over ResultSet extraction.

In this step you’ll refactor findByCreditCard(String) using a ResultSetExtractor to create Account objects.

Create a private inner class called AccountExtractor that implements the ResultSetExtractor interface. Note that this interface also has a generic type parameter that should be populated. The implementation of the extractData(ResultSet) method should delegate to the existing mapAccount(ResultSet) method.

[Note] Note

If you prefer to use a lambda instead please do so. Have it delegate to the existing mapAccount(ResultSet) method

Next refactor the findByCreditCard(String) method to use the template. This refactoring is a good candidate for using the query(String, ResultSetExtractor<T>, Object…​) method.

Finally run the JdbcAccountRepositoryTests tests once again. When you have the green bar, you’ve completed the lab!

[Tip] Tip

Note that all three repositories still have a DataSource field. Now that you’re using the constructor to instantiate the JdbcTemplate, you do not need the DataSource field anymore. For completeness' sake, you can remove the DataSource fields if you like.