Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Kenneth A. Kousen - Making Java Groovy - 2014.pdf
Скачиваний:
50
Добавлен:
19.03.2016
Размер:
15.36 Mб
Скачать

The Groovy approach, part 1: groovy.sql.Sql

203

ResultSet rs = pst.executeQuery(); if (rs.next()) {

p = new Product(); p.setId(rs.getInt("id")); p.setName(rs.getString("name")); p.setPrice(rs.getDouble("price"));

}

rs.close();

}catch (SQLException e) { e.printStackTrace();

}finally {

try {

if (pst != null) pst.close(); if (conn != null) conn.close();

} catch (SQLException e) { e.printStackTrace();

}

}

return p;

The essence; everything else is ceremony

}

As with so many things in Java, the best thing you can say about this code is that eventually you get used to it. All that’s being done here is to execute a select statement with a where clause including the necessary product ID and converting the returned database row into a Product object. Everything else is ceremony.

I could go on to show the remaining implementation methods, but suffice it to say that the details are equally buried. See the book source code for details.

Lessons learned (JDBC)

1JDBC is a very verbose, low-level set of classes for SQL access to relational databases.

2The Spring JdbcTemplate class (covered in chapter 7) is a good choice if Groovy is not available.

Years ago this was the only realistic option for Java. Now other options exist, like Spring’s JdbcTemplate (discussed in chapter 7 on Spring) and object-relational mapping tools like Hibernate (discussed later in this chapter). Still, if you already know SQL and you want to implement a DAO interface, Groovy provides a very easy alternative: the groovy.sql.Sql class.

8.2The Groovy approach, part 1: groovy.sql.Sql

The groovy.sql.Sql class is a simple façade over JDBC. The class takes care of resource management for you, as well as creating and configuring statements and logging errors. It’s so much easier to use than regular JDBC that there’s never any reason to go back.

The next listing shows the part of the class that sets up the connection to the database and initializes it.

www.it-ebooks.info

204

CHAPTER 8 Database access

Listing 8.6 A ProductDAO implementation using the groovy.sql.Sql class

import groovy.sql.Sql

class SqlProductDAO implements ProductDAO {

Sql sql = Sql.newInstance(url:'jdbc:h2:mem:', driver:'org.h2.Driver')

SqlProductDAO() { sql.execute '''

create table product ( id int primary key, name varchar(25), price double

)''' sql.execute """

insert into product values (1,'baseball',4.99), (2,'football',14.95), (3,'basketball',14.99)"""

}

// ... more to come ...

}

Configure the database properties

Multiline strings to make reading easier

The groovy.sql.Sql class contains a static factory method called newInstance that returns a new instance of the class. The method is overloaded for a variety of parameters; see the GroovyDocs for details.

The execute method takes an SQL string and, naturally enough, executes it. Here I’m using a multiline string to make the create table and insert into statements easier to read. The Sql class takes care of opening a connection and closing it when finished.

THE SQL CLASS The groovy.sql.Sql class does everything raw JDBC does, and handles resource management as well.

The same execute method can be used to delete products:

void deleteProduct(int id) {

sql.execute 'delete from product where id=?', id

}

The execute method not only creates the prepared statement, it also inserts the provided ID into it and executes it. It’s hard to get much simpler than that.

Inserting products can use the same method, but with a list of parameters:

void insertProduct(Product p) {

def params = [p.id, p.name, p.price] sql.execute

'insert into product(id,name,price) values(?,?,?)', params

}

The class has another method called executeInsert, which is used if any of the columns are auto-generated by the database. That method returns a list containing the

www.it-ebooks.info

The Groovy approach, part 1: groovy.sql.Sql

205

generated values. In this example the id values are supplied in the program. Autogenerated values will be considered in section 8.3 on Hibernate and JPA.

Retrieving products involves a minor complication. There are several useful methods for querying. Among them are firstRow, eachRow, and rows. The firstRow method is used when a single row is required. Either eachRow or rows can be used if there are multiple rows in the result set. In that case, eachRow returns a map of column names to column values, and the rows method returns a list of maps, one for each row.

The complication is that the returned column names are in all capitals. For example, the query

sql.firstRow 'select * from product where id=?', id

returns

[ID:1, NAME:baseball, PRICE:4.99]

for an id of 1. Normally I’d like to use that map as the argument to the Product constructor, but because the Product attributes are all lowercase that won’t work.

One possible solution is to transform the map into a new one with lowercase keys. That’s what the collectEntries method in the Map class is for. The resulting implementation of the findProductById method is therefore

Product findProductById(int id) {

def row = sql.firstRow('select * from product where id=?', id)

new Product( row.collectEntries { k,v -> [k.toLowerCase(), v] } );

}

It would be easy enough to generalize this to the getAllProducts method by using eachRow and transforming them one at a time. A somewhat more elegant solution is to use the rows method and transform the resulting list of maps directly:

List<Product> getAllProducts() {

sql.rows('select * from product').collect { row -> new Product(

row.collectEntries { k,v -> [k.toLowerCase(), v] }

)

}

}

This solution is either incredibly elegant or too clever by half, depending on your point of view. Collecting2 everything together (except for the initialization shown in the constructor already), the result is shown in the following listing.

2 No pun intended.

www.it-ebooks.info

206 CHAPTER 8 Database access

Listing 8.7 The complete SqlProductDAO class, except for the parts already shown

class SqlProductDAO implements ProductDAO {

Sql sql = Sql.newInstance(url:'jdbc:h2:mem:',driver:'org.h2.Driver')

List<Product> getAllProducts() {

sql.rows('select * from product').collect { row -> new Product(

row.collectEntries { k,v -> [k.toLowerCase(), v] }

)

}

}

Product findProductById(int id) {

def row = sql.firstRow('select * from product where id=?', id) new Product(

row.collectEntries { k,v -> [k.toLowerCase(), v] } );

}

void insertProduct(Product p) {

def params = [p.id, p.name, p.price] sql.execute

'insert into product(id,name,price) values(?,?,?)', params

}

void deleteProduct(int id) {

sql.execute 'delete from product where id=?', id

}

}

By the way, there’s one other option available,3 but only if the Person class is implemented in Groovy. If so, I can add a constructor to the Person class that handles the case conversion there:

class Product { int id String name double price

Person(Map args) { args.each { k,v ->

setProperty( k.toLowerCase(), v)

}

}

}

With this constructor, the getAllProducts method reduces to

List<Product> getAllProducts() {

sql.rows('select * from product').collect { new Product(it) }

}

It’s hard to beat that for elegance.

3 Thanks to Dinko Srkoc on the Groovy Users email list for this helpful suggestion.

www.it-ebooks.info

The Groovy approach, part 1: groovy.sql.Sql

207

Going meta

The “elegant” solution in the chapter breaks down if the class attributes use camel case, which is normal. The corresponding database table entries would then use underscores to separate the words.

As shown by Tim Yates on the Groovy Users email list,4 you can use Groovy metaprogramming to add a toCamelCase method to the String class to do the conversion. The relevant code is

String.metaClass.toCamelCase = {-> delegate.toLowerCase().split('_')*.capitalize().join('').with {

take( 1 ).toLowerCase() + drop( 1 )

}

}

Every Groovy class has a metaclass retrieved by the getMetaClass method. New methods can be added to the metaclass by assigning closures to them, as is done here. A no-arg closure is used, which implies that the new method will take zero arguments.

Inside the closure the delegate property refers to the object on which it was invoked. In this case it’s the string being converted. The database table columns are in uppercase separated by underscores, so the delegate is converted to lowercase and then split at the underscores, resulting in a list of strings.

Then the spread-dot operator is used on the list to invoke the capitalize method on each one, which capitalizes only the first letter. The join method then reassembles the string.

Then comes the fun part. The with method takes a closure, and inside that closure any method without a reference is invoked on the delegate. The take and drop methods are used on lists (or, in this case, a character sequence). The take method retrieves the number of elements specified by its argument. Here that value is 1, so it returns the first letter, which is made lowercase. The drop method returns the rest of the elements after the number in the argument is removed, which in this case means the rest of the string.

The result is that you can call the method on a string and convert it. 'FIRST_NAME'

.toLowerCase() becomes 'firstName', and so on.

Welcome to the wonderful world of Groovy metaprogramming.

The advantages of groovy.sql.Sql over raw JDBC are obvious. If I have SQL code already written, I always use it. 4

4 See http://groovy.329449.n5.nabble.com/Change-uppercase-Sql-column-names-to-lowercase-td5712088.html for the complete discussion.

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]