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 findmethod 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<String> 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 and
getKeyColumn. 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<String> 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<String> 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<String> 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<String> 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<String> 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, and expectedRow. 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.
February 2004 March 2004 May 2004 September 2004 October 2004 January 2005 February 2005 September 2005 October 2005 November 2005 December 2005 January 2006 February 2006 March 2006 June 2006 August 2006 January 2007 February 2007 March 2007 April 2007 September 2007 October 2007 November 2007 December 2007 January 2008