Tải bản đầy đủ (.pdf) (24 trang)

Persistence with JDBC

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (195.6 KB, 24 trang )

Persistence with JDBC
T
he previous chapter introduced the Spring Framework’s integration with Java data-access frame-
works. This chapter provides more detailed insight into Spring’s support for persistence using JDBC,
covering the following topics:
• How the JdbcTemplate class takes care of the boilerplate code you usually encounter and
simplifies working with the JDBC API.
• How to use the JdbcTemplate class to perform common database tasks, such as selecting,
inserting, updating, and deleting data.
• How to use a convenient base class for your data access objects (DAOs) that builds on the
JdbcTemplate class.
• How to use callbacks, which make performing more complex tasks easier.
• How to use executable query objects, which allow you to work with database operations in a
more object-oriented manner.
• How to perform batch operations, working with large chunks of data in the form of large
objects (LOBs), and obtaining native JDBC objects, while still leveraging the power of
Spring’s data abstraction framework.
• The features that are new in Spring 2.0, including the SimpleJdbcTemplate class, an even
more lightweight template class for performing JDBC operations.
Defining the Data Layer
It is of great importance to separate your applications into three tiers. One of those tiers is the data
tier. Because this chapter deals with persistence, we’ll start by showing you how to define (part of)
the data tier. Specifically, you’ll define a domain object that you will use for the duration of this
chapter.
A domain object is a Java representation of part of your domain model. It is typically a data
holder that is shared across the different layers of your application. We’ll define a Member domain
object as shown in Listing 6-1. Notice the other domain objects: Name, Address, and PhoneNumber.
Listing 6-1. The Member Domain Object
package com.apress.springbook.chapter06;
import java.util.List;
import java.util.ArrayList;


import java.util.Collections;
167
CHAPTER 6
9187ch06CMP2.qxd 7/26/07 12:31 PM Page 167
public class Member {
private Integer id;
private Name name = new Name();
private Integer age;
private Sex sex;
private Address address = new Address();
private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>();
public Member() { }
public Member(String firstName, String lastName) {
this.getName().setFirst(firstName);
this.getName().setLast(lastName);
}
void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public Address getAddress() {
return address;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;

}
public Name getName() {
return name;
}
public List<PhoneNumber> getPhoneNumbers() {
return Collections.unmodifiableList(phoneNumbers);
}
public void addPhoneNumber(PhoneNumber phoneNumber) {
this.phoneNumbers.add(phoneNumber);
}
public void removePhoneNumber(PhoneNumber phoneNumber) {
this.phoneNumbers.remove(phoneNumber);
}
public void removePhoneNumber(int index) {
this.phoneNumbers.remove(index);
}
CHAPTER 6

PERSISTENCE WITH JDBC168
9187ch06CMP2.qxd 7/26/07 12:31 PM Page 168
public Sex getSex() {
return sex;
}
public void setSex(Sex sex) {
this.sex = sex;
}
}
Next, we need to define an interface that provides access to instances of the Member class,
as shown in Listing 6-2. You’ll gradually implement this DAO interface throughout this chapter
(though, as we explained in Chapter 5, DAO in the Spring sense is different from traditional DAO).

Defining a DAO interface is considered a best practice because it allows your business logic code to
depend on the DAO interface instead of the actual implementation. This enables you to change the
implementation of the DAO interface without needing to refactor the rest of your application code.
Listing 6-2. The MemberDao Interface
package com.apress.springbook.chapter06;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
public interface MemberDao {
int getTotalNumberOfMembers();
Member load(Integer id);
void add(Member member);
void delete(Member member);
void updateAge(Integer memberId, Integer age);
long getTotalAge();
long getAverageAge();
long getOldestAge();
long getYoungestAge();
List getMembersForLastNameAndAge(String lastName, Integer age);
void addImageForMember(Integer memberId, InputStream in);
void getImage(Integer id, OutputStream out);
void importMembers(List<Member> members);
List loadAll();
}
Using the JdbcTemplate Class
As mentioned in the previous chapter, Spring greatly simplifies using the JDBC API. Take another
look at the first two code examples in the previous chapter. The first introduces a count query using
JDBC the traditional way. The second uses Spring’s template class to eliminate most of the boiler-
plate code.
CHAPTER 6


PERSISTENCE WITH JDBC 169
9187ch06CMP2.qxd 7/26/07 12:31 PM Page 169
Spring provides the org.springframework.jdbc.core.JdbcTemplate class, which simplifies
working with JDBC. As with all Spring template classes, it provides resource management, excep-
tion handling, and transparent participation in ongoing transactions. So, you don’t need to open
and close database connections, handle unrecoverable exceptions, or write code to participate in a
transaction.

Tip
The
JdbcTemplate
class is a stateless and thread-safe class, so you can use a single instance that many
classes can use. However, you should use only one
JdbcTemplate
instance per data source.
The Spring template classes offer more than the advantages of working directly with JDBC.
They provide convenience methods for obtaining integers, objects, and so on directly. So instead of
needing to obtain a ResultSet, read the first row, and then get the first value in the row, you can use
the convenience method queryForInt() on the template class to return an integer directly. Table 6-1
lists some of those methods.
Table 6-1. Some Convenience Methods Provided by JdbcTemplate
Method Description
execute() Executes a SQL statement that returns either null or the object that was the
result of the statement
query() Executes a SQL query and returns the result as a list of objects
queryForInt() Executes a SQL query and returns the result as an integer
queryForLong() Executes a SQL query and returns the result as a long
queryForMap() Executes a SQL query and returns the single row result as a Map (each
column being an entry in the map)

queryForList() Executes a SQL query and returns the result as a List (containing the result
of the queryForMap() method for each row in the result)
queryForObject() Executes a SQL query and returns the result as an object (either by
specifying a class or by providing a callback)
queryForRowSet() Executes a SQL query and returns an instance of SqlRowSet (a wrapper for a
javax.sql.RowSet), which eliminates the need to catch SqlException
We’ll start by implementing the first method of the MemberDao interface using the JdbcTemplate
class, as shown in Listing 6-3. This is in a class called MemberDaoImpl.
Listing 6-3. Using the Convenience Methods Provided by the JdbcTemplate Class
public int getTotalNumberOfMembers() {
return new JdbcTemplate(dataSource).queryForInt(
"SELECT COUNT(0) FROM members"
);
}
Again, compare this code with the two first examples of the previous chapter, and notice the
absence of a lot of code that you would normally need to write in order to perform this operation:
• You do not need to manage resources. A connection to the database is automatically opened
and closed, even when an error occurs. In addition to eliminating all the boilerplate code,
this automatic resource management also prevents resource leaks due to incorrectly man-
aged connections.
CHAPTER 6

PERSISTENCE WITH JDBC170
9187ch06CMP2.qxd 7/26/07 12:31 PM Page 170
• The code will automatically participate in any ongoing Spring-managed transactions with-
out you needing to write code to manage transactions (that is, commit and roll back). We’ll
discuss transaction management in the next chapter.
• It eliminates the need for exception handling.
The JDBC API is notorious for its exception handling. This is mainly because it requires you to
handle numerous unrecoverable exceptions, such as when a connection to the database could not

be established. In most cases, a SQLException indicates an unrecoverable error, and it is therefore
not desirable to need to handle them.
Spring will translate any data-access-related exception into a fine-grained, hierarchical tree of
unchecked exceptions (an instance of java.lang.RuntimeException). These exceptions do not need
to be declared in your method signature and therefore do not need to be handled. Obviously, you
may want to catch some exceptions, especially those you anticipate and know how to handle. All
other exceptions will be treated as unrecoverable and will be handled in the front-end tier. We dis-
cussed Spring’s data-access exception translation in detail in the previous chapter. Figure 6-1 shows
the part of the Spring data-access exception hierarchy that is related to using the JDBC API.
Figure 6-1. JDBC-related part of the DataAccessException hierarchy
The most important exceptions related to working with the JDBC API in this hierarchy are as
follows:
DataAccessResourceFailureException: This exception (or one of its subtypes) is thrown when
a problem occurs while connecting to the database, such as not being able to connect to the
database.
DataIntegrityViolationException: This exception indicates a data-integrity problem, such as
specifying no data for a column that requires data to be set or inserting a duplicate unique
value.
DataRetrievalFailureException: This exception is thrown when a problem occurs while
retrieving data from the database, such as querying for a single result and getting more than
one result.
CHAPTER 6

PERSISTENCE WITH JDBC 171
9187ch06CMP2.qxd 7/26/07 12:31 PM Page 171
Using the JdbcDaoSupport Class
In addition to offering the JdbcTemplate class to provide powerful JDBC support to your application,
Spring also provides convenient base classes to implement DAOs for all supported persistence APIs;
therefore, it offers one for working with the JDBC API. This important org.springframework.jdbc.
core.support.JdbcDaoSupport class provides convenient access to a JdbcTemplate instance through

the getJdbcTemplate() method. You can either inject a JdbcTemplate into your DAO in the config-
uration directly or inject just a preconfigured DataSource instance. In this example, we will use an
injected data source to generate a JdbcTemplate instance.
First, the initial implementation of the MemberDao that will be used by the middle-tier logic is
shown in Listing 6-4. This implementation will extend the DAO support class provided by Spring for
working with the JDBC API.
Listing 6-4. Using the JdbcDaoSupport Class As the Base Class for the DAO Implementation
package com.apress.springbook.chapter06;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
public class MemberDaoImpl extends JdbcDaoSupport implements MemberDao {
public int getTotalNumberOfMembers() {
return getJdbcTemplate().queryForInt("SELECT COUNT(0) FROM members");
}
}
The difference between the previous implementation of getTotalNumberOfMembers() and this
one is that we do not instantiate a new JdbcTemplate instance, but rather ask the superclass for an
instance.
To be able to get an instantiated JdbcTemplate, you need to configure the DAO implementation
in a Spring application context. You need to provide it with a valid data source that it will use to
create a template, as shown in Listing 6-5.
Listing 6-5. Part of the data-layer.xml Application Context Defining the DAO
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:mem:."/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
<bean id="memberDao" class="com.apress.springbook.chapter06.MemberDaoImpl">

<property name="dataSource" ref="dataSource"/>
</bean>
Depending on the database you are using, you need to modify the properties of the data
source. You can find more information about using data sources in the previous chapter.

Caution
Make sure to set the
destroy-method
parameter on the data source. This will guarantee that the
connection pool and all underlying connections are closed properly.
CHAPTER 6

PERSISTENCE WITH JDBC172
9187ch06CMP2.qxd 7/26/07 12:31 PM Page 172
Now we can look at working with the data in the database.
Working with Database Data
The following sections demonstrate the most common data-access and manipulation tasks using
the JdbcTemplate class. First, we revisit how to select data from the database. Next, we discuss how
to insert new data and update and delete existing data. Finally, we discuss aggregate functions to
perform on data in the database.
Selecting Data
When working with databases, probably the most common task is accessing data that is already in
the database. To do this, you need to write a SQL query that retrieves only the data of interest.
Listing 6-6 demonstrates using a SELECT statement to obtain all Member instances from the
database.
Listing 6-6. Selecting Data Using a SELECT Statement
public List<Member> loadAll() {
return (List<Member>)
getJdbcTemplate().query("SELECT * FROM member", new MemberRowMapper());
}

The last argument to the query() method is an implementation of the org.springframework.
jdbc.core.RowMapper interface that is part of Spring. We discuss the RowMapper interface in more
detail in the “Using Callbacks” section later in this chapter. For now, just note that the implemen-
tation maps the SQL ResultSet to a Member instance.
Inserting Data
Most applications want to add data to the database as well. To do this, use an INSERT statement.
Listing 6-7 inserts a new Member instance into the database.
Listing 6-7. Inserting Data Using an INSERT Statement
public void add(Member member) {
getJdbcTemplate().update(
"INSERT INTO member (name_first, name_middle, name_last, address_line1, " +
"address_line2, address_city, address_state, address_zip, age) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
new Object[] {
member.getName().getFirst(),
member.getName().getMiddle(),
member.getName().getLast(),
member.getAddress().getLine1(),
member.getAddress().getLine2(),
member.getAddress().getCity(),
member.getAddress().getState(),
member.getAddress().getZip(),
member.getAge()
}
);
}
CHAPTER 6

PERSISTENCE WITH JDBC 173
9187ch06CMP2.qxd 7/26/07 12:31 PM Page 173

To insert data into the database, you use the update() method on the JdbcTemplate class and
provide it with a SQL INSERT statement and the arguments to put in the INSERT statement. This
statement is executed, and no result is returned. Note that we are using an object array to supply
the template method with the arguments to insert into the placeholders inside the SQL query. Spec-
ifying question marks in your SQL queries and providing the arguments to replace them with is
common when working with the JDBC API. In later sections, you will see some more advanced
examples of inserting data. We will also discuss how to externalize the actual SQL statements from
your methods.

Tip
It is considered a best practice to use the
update()
method of the
JdbcTemplate
class for both
INSERT
and
UPDATE
statements.
Updating Data
Another common task for applications is updating existing entries in the database. You do this
using the UPDATE SQL statement. In the following example, we want to update an existing Member
instance. When the member has a birthday, we want to update the age field. Therefore, we add a
method to the DAO interface that updates the age of the member to the age that was passed in as
a parameter, as shown in Listing 6-8.
Listing 6-8. Updating Data Using an UPDATE Statement
public void updateAge(Integer memberId, Integer age) {
getJdbcTemplate().update(
"UPDATE member SET age = ? WHERE id = ?",
new Object[] { age, memberId }

);
}
The only difference from the previous example is the SQL statement, which updates only the
column that needs to be changed. Note that you can update multiple columns by separating the
column/value pairs with a comma.
Deleting Data
Another common task related to persistence is removing existing data from the database. Suppose
that we want to provide the user with the means to clean up the database by removing specific
member instances. Listing 6-9 demonstrates how to do this.
Listing 6-9. Deleting Data Using a DELETE Statement
public void delete(Member member) {
getJdbcTemplate().update(
"DELETE FROM member WHERE id = ?",
new Object[] { member.getId() }
);
}
Again, this example is similar to the previous examples. However, it uses a SQL DELETE state-
ment to remove the data from the database.
CHAPTER 6

PERSISTENCE WITH JDBC174
9187ch06CMP2.qxd 7/26/07 12:31 PM Page 174
Using Aggregate Functions
Table 6-2 provides an overview of the SQL aggregate functions. Most databases offer a number of
additional aggregate functions, but they are mostly vendor-specific and therefore tie your code to a
particular database vendor.
Table 6-2. Common SQL Aggregate Functions
Function Description
AVG(column) Returns the average value of a certain column
COUNT(0) Returns the number of selected rows

COUNT(column) Returns the number of rows of a certain column (excluding rows with a null
value for this column)
MAX(column) Returns the highest value of a certain column
MIN(column) Returns the lowest value of a certain column
SUM(column) Returns the sum of all values of a certain column
Revisit the first JDBC example of this chapter (Listing 6-3). It uses the COUNT(0) aggregate func-
tion to determine the total number of members. Listing 6-10 shows a few more examples of using
aggregate functions to get some statistics on existing members.
Listing 6-10. Examples of Using Aggregate Functions
public long getTotalAge() {
return getJdbcTemplate().queryForLong("SELECT SUM(age) FROM member");
}
public long getAverageAge() {
return getJdbcTemplate().queryForLong("SELECT AVG(age) FROM member");
}
public long getOldestAge() {
return getJdbcTemplate().queryForLong("SELECT MAX(age) FROM member");
}
public long getYoungestAge() {
return getJdbcTemplate().queryForLong("SELECT MIN(age) FROM member");
}
You could also implement the examples in Listing 6-10 by retrieving all the data from the data-
base and determining the average and sum programmatically. However, because these aggregate
functions are implemented natively by the database, using them greatly improves performance.
Furthermore, using aggregate functions greatly reduces network traffic by transferring only the
result of the function instead of the entire data set on which the function is performed.
Note that we are using the queryForLong() method provided by the JdbcTemplate class to avoid
having to inspect the result set and cast the content of the result set to a long. We can do this
because we know the result of the aggregate function is of the long type.


Tip
Because aggregate functions generally outperform doing the same operation programmatically in terms of
CPU cycles as well as network traffic, use aggregate functions wherever applicable.
CHAPTER 6

PERSISTENCE WITH JDBC 175
9187ch06CMP2.qxd 7/26/07 12:31 PM Page 175

Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Tải bản đầy đủ ngay
×