Yesterday I extracted the common method save
into the Persister class.
The find
can be dealt with almost as easily. The only trick is that
there is a creation statement in it that builds an instance of the
appropriate object (User or Customer). Before proceeding, then, step one
is to extract this creation into a factory method. Step two is to update
the PersistableMetadata interface so that it defines this creation
method. (This suggests that the name for PersistableMetadata is no
longer precise; we can deal with that later.)
After that, I move the find
method to the Persister class. I then
modify it to call back to the creation method defined on the access
class. Here’s CustomerAccess:
package domain;
import java.util.*;
public class CustomerAccess implements PersistableMetadata {
public void save(Persistable persistable) {
new Persister(this).save(persistable);
}
public Customer find(String idKey) {
return (Customer)new Persister(this).find(idKey);
}
public String getTable() {
return "cust";
}
public String[] getColumns() {
return new String[] { Customer.ID, Customer.NAME };
}
public String getKeyColumn() {
return Customer.ID;
}
public Object create(List row) {
return new Customer(row.get(0), row.get(1));
}
}
Since the columns list and table name are now only used in one place, I
chose to inline them to the interface methods getColumns
andgetKeyColumn
. These methods are now constant methods.The updated
PersistableMetadata interface:
package domain;
import java.util.*;
public interface PersistableMetadata {
String getTable();
String[] getColumns();
String getKeyColumn();
Object create(List row);
}
The create
method will have to return an Object, hence the cast in the
CustomerAccess find
method.
Persister:
package domain;
import java.util.*;
import persistence.*;
public class Persister {
private PersistableMetadata metadata;
private JdbcAccess access;
public Persister(PersistableMetadata metadata) {
this(metadata, new JdbcAccess());
}
public Persister(PersistableMetadata metadata, JdbcAccess access) {
this.metadata = metadata;
this.access = access;
}
public void save(Persistable persistable) {
String[] columns = metadata.getColumns();
String[] fields = new String[columns.length];
for (int i = 0; i < columns.length; i++)
fields[i] = persistable.get(columns[i]);
String sql = new SqlGenerator().createInsert(metadata.getTable(), columns, fields);
access.execute(sql);
}
public Object find(String key) {
String sql =
new SqlGenerator().createFindByKey(
metadata.getTable(), metadata.getColumns(), metadata.getKeyColumn(), key);
List row = access.executeQuery(sql);
return metadata.create(row);
}
}
And finally, the missing test in PersistableTest:
package domain;
import java.util.*;
import junit.framework.*;
import persistence.*;
public class PersisterTest extends TestCase {
private static final String TABLE = "x";
private static final String[] COLUMNS = { "a", "b" };
private static final List EXPECTED_ROW = new ArrayList();
private static final Object RETURN_OBJECT = "object";
private JdbcAccess access;
private String lastSql;
private PersistableMetadata metadata;
protected void setUp() {
access = new JdbcAccess() {
public void execute(String sql) {
lastSql = sql;
}
public List executeQuery(String sql) {
lastSql = sql;
return EXPECTED_ROW;
}
};
metadata = new PersistableMetadata() {
public String getTable() {
return TABLE;
}
public String[] getColumns() {
return COLUMNS;
}
public String getKeyColumn() {
return COLUMNS[0];
}
public Object create(List row) {
if (row == EXPECTED_ROW)
return RETURN_OBJECT;
return null;
}
};
}
public void testSave() {
Persistable persistable = new Persistable() {
public String get(String key) {
if (key.equals(COLUMNS[0]))
return "0";
if (key.equals(COLUMNS[1]))
return "1";
return null;
}
};
Persister persister = new Persister(metadata, access);
persister.save(persistable);
assertEquals("insert into x (a,b) values ('0','1')", lastSql);
}
public void testFindBy() {
final String key = "k";
Persister persister = new Persister(metadata, access);
assertEquals(RETURN_OBJECT, persister.find(key));
assertEquals("select a,b from x where a='k'", lastSql);
}
}
The method testFindBy
was pretty easy to write, considering that I
already had the Persistable, PersistableMetadata, and JdbcAccess test
implementations/stubs. Here, I’ve added a stub for executeQuery
and
create
. With refactoring to eliminate duplication in the test, the
individual test methods are pretty succinct.
The Persister tests leave a slightly sour taste in my mouth, as do most
tests where I introduce more complex mock interrelationships. Here, you
have to dig around the test class a bit to understand the relationship
between executeQuery
, create
, returnObject
, andexpectedRow
. I
don’t have a problem with mocks per se, but they are best isolated to as
few corners of your system as possible if they are at all tricky.