GORM的查询方法很方便(可查看http://www.grails.org/doc/1.2.x/ref/Domain%20Classes/findAll.html)
但因为我现在用Db4o,根本就不需要ORM,GORM的finders派不上用场。但是GORM把finders以及validate注入到Domain Classes中的方式对我们很有启发性。
Db4o提供三种查询方式:QBE, NQ和SODA。其中NQ是type-safe的,在编译期可以作语法检查,在运行期可以被Db4o转化成高效的SODA。我不喜欢在Grails中使用NQ,一是因为这让代码看起来不太漂亮,二是无法保证所有的NQ都会被转化成SODA。
利用Groovy的Closure我写了两个方法:findAll和find,最终执行的都是SODA查询。改一改Grails的FindAllPersistentMethod代码:
package com.grs.db4opersistence.metaclass;
import com.db4o.ObjectContainer;
import com.db4o.query.Query;
import com.grs.utils.GroovyDataUtils;
import groovy.lang.MissingMethodException;
import groovy.lang.Closure;
import org.springframework.beans.SimpleTypeConverter;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Pattern;
/**
* The "findAll" persistent static method allows searching for instances
*
* Usages:
* (1) 0 arguments: User.findAll()
* (2) 1 argument : User.findAll(sortInfoAndExampleMap) : User.findAll(sort:'dateCreated',order:'asc',department:department)
* (3) 1 argument : User.findAll(exampleObject)
* (4) 2 arguments: User.findAll(sortInfoMap, exampleObject) : User.findAll([sort:'dateCreated',order:'asc'],userExample)
* (5) 1 argument : User.findAll{it.descend('department').constrain(department)}
* (6) 2 arguments: User.findAll(sort:'dateCreated',order:'asc'){it.descend('department').constrain(department)}
* (7) 2 arguments: User.findAll(sort:'dateCreated',order:'asc',department:department){other complex constraints...}
* (8) 3 arguments: User.findAll(sortInfoMap, exampleObject){other complex constraints}
*
* @author Sam Chen
*
* Created: Jun 2, 2010
*
*/
public class FindAllPersistentMethod extends AbstractStaticPersistentMethod {
public static SimpleTypeConverter converter = new SimpleTypeConverter();
public FindAllPersistentMethod(ObjectContainer objectContainer,
ClassLoader classLoader) {
super(objectContainer, classLoader, Pattern.compile("^findAll$"));
}
protected Object doInvokeInternal(final Class clazz, String methodName, Closure additionalCriteria, final Object[] arguments) {
return doFindAll(clazz, methodName, additionalCriteria, arguments);
}
protected final Object doFindAll(final Class clazz, String methodName, Closure additionalCriteria, final Object[] arguments) {
if (arguments.length == 0) {
Query q = getDb4oTemplate().query();
q.constrain(clazz);
q.descend("deleted").constrain(false);
return q.execute();
}
if (arguments.length == 1) {
Object arg = arguments[0];
if(clazz.isAssignableFrom(arg.getClass())) {
return getDb4oTemplate().queryByExample(arg);
} else if(arg instanceof Map) {
Query q = getDb4oTemplate().query();
q.constrain(clazz);q.descend("deleted").constrain(false);
Map map = (Map) arg;
if (map.containsKey("sort") && map.containsKey("order")) {
if (map.get("order").equals("desc")) {
q.descend((String)map.get("sort")).orderDescending();
} else {
q.descend((String)map.get("sort")).orderAscending();
}
}
for(Iterator<String> it = map.keySet().iterator(); it.hasNext();) {
String key = it.next();
if("sort".equals(key) || "order".equals(key)) {
continue;
}
q.descend(key).constrain(map.get(key));
}
return q.execute();
} else if(arg instanceof Closure) {
Query q = getDb4oTemplate().query();
q.constrain(clazz);q.descend("deleted").constrain(false);
((Closure)arg).call(q);
return q.execute();
} else {
throw new MissingMethodException(methodName, clazz, arguments);
}
}
if (arguments.length == 2) {
Object arg0 = arguments[0];
Object arg1 = arguments[1];
if(!(arg0 instanceof Map)) {
throw new MissingMethodException(methodName, clazz, arguments);
}
Query q = getDb4oTemplate().query();
q.constrain(clazz);q.descend("deleted").constrain(false);
Map map = (Map) arg0;
if (map.containsKey("sort") && map.containsKey("order")) {
if (map.get("order").equals("desc")) {
q.descend((String)map.get("sort")).orderDescending();
} else {
q.descend((String)map.get("sort")).orderAscending();
}
}
for (Iterator<String> it = map.keySet().iterator(); it.hasNext();) {
String key = it.next();
if("sort".equals(key) || "order".equals(key)) {
continue;
}
q.descend(key).constrain(map.get(key));
}
if (arg1 instanceof Closure) {
((Closure) arg1).call(q);
} else if (arg1 instanceof Map || clazz.isAssignableFrom(arg1.getClass())) {
// extract properties from the domain object...
applyConstraintsFromExample(arg1, q);
} else {
throw new MissingMethodException(methodName, clazz, arguments);
}
return q.execute();
}
if (arguments.length == 3) {
Object arg0 = arguments[0]; // sortInfoMap
Object arg1 = arguments[1]; // example
Object arg2 = arguments[2]; // closure
if(!(arg0 instanceof Map) || !(arg1 instanceof Map || clazz.isAssignableFrom(arg1.getClass())) || !(arg2 instanceof Closure)) {
throw new MissingMethodException(methodName, clazz, arguments);
}
Query q = getDb4oTemplate().query();
q.constrain(clazz);q.descend("deleted").constrain(false);
Map map = (Map) arg0;
if (map.containsKey("sort") && map.containsKey("order")) {
if (map.get("order").equals("desc")) {
q.descend((String)map.get("sort")).orderDescending();
} else {
q.descend((String)map.get("sort")).orderAscending();
}
}
// extract properties from the domain object...
applyConstraintsFromExample(arg1, q);
((Closure)arg2).call(q);
return q.execute();
}
throw new MissingMethodException(methodName, clazz, arguments);
}
private void applyConstraintsFromExample(Object o, Query q) {
Map properties = (o instanceof Map ? (Map)o : GroovyDataUtils.getProperties(o));
for(Iterator<String> it = properties.keySet().iterator(); it.hasNext();) {
String key = it.next();
Object v = properties.get(key);
if(v != null) {
q.descend(key).constrain(v);
}
}
}
}
有了FindAllPersistentMethod方法,FindPersistentMethod就很简单了:
package com.grs.db4opersistence.metaclass;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import groovy.lang.Closure;
import org.springframework.beans.SimpleTypeConverter;
import java.util.regex.Pattern;
/**
* <p>
* The "find" persistent static method allows searching for an instance
*
* @author Sam Chen
* Created: Jun 2, 2010
*
*/
public class FindPersistentMethod extends FindAllPersistentMethod {
public static SimpleTypeConverter converter = new SimpleTypeConverter();
public FindPersistentMethod(ObjectContainer objectContainer, ClassLoader classLoader) {
super(objectContainer, classLoader);
this.setPattern(Pattern.compile("^find$"));
}
protected Object doInvokeInternal(final Class clazz, String methodName, Closure additionalCriteria, final Object[] arguments) {
ObjectSet os = (ObjectSet)this.doFindAll(clazz, methodName, additionalCriteria, arguments);
return os.size() > 0 ? os.get(0) : null;
}
}
,同时修改它的method signature:
def findAllMethod = new FindAllPersistentMethod(objectContainer, application.classLoader) /** * Usages: * (1) 0 arguments: User.findAll() * (2) 1 argument : User.findAll(sortInfoAndExampleMap) : User.findAll(sort:'dateCreated',order:'asc',department:department) * (3) 1 argument : User.findAll(exampleObject) * (4) 2 arguments: User.findAll(sortInfoMap, exampleObject) : User.findAll([sort:'dateCreated',order:'asc'],userExample) * (5) 1 argument : User.findAll{it.descend('department').constrain(department)} * (6) 2 arguments: User.findAll(sort:'dateCreated',order:'asc'){it.descend('department').constrain(department)} * (7) 2 arguments: User.findAll(sort:'dateCreated',order:'asc',department:department){other complex constraints...} * (8) 3 arguments: User.findAll(sortInfoMap, exampleObject){other complex constraints} */ mc.static.findAll = {-> findAllMethod.invoke(mc.javaClass, "findAll", [] as Object[]) } mc.static.findAll = {Object sortMapOrExampleOrClosure-> findAllMethod.invoke(mc.javaClass, "findAll", [sortMapOrExampleOrClosure] as Object[]) } mc.static.findAll = {Map map, Object eoc -> findAllMethod.invoke(mc.javaClass, "findAll", [map, eoc] as Object[]) } mc.static.findAll = {Map map, Object example, Closure c-> findAllMethod.invoke(mc.javaClass, "findAll", [map, example, c] as Object[]) }
再写一点测试代码:
package com.grs.sctms import grails.test.* class FindMethodIntegrationTests extends GrailsUnitTestCase { protected void setUp() { super.setUp() for (int i = 0; i < 100; i++) { new User(username:"user#$i", password:"pwd").save() } new User(username:"sam.ds.chen@xxx.com", password:"sam's password").save() } protected void tearDown() { super.tearDown() } void testFindAllWithEmptyArgument() { def users = User.findAll() assertEquals 101, users.size() } void testFindAllWithOneArguments() { // def users = User.findAll(sort:'username',order:'asc') // assertEquals 'sam.ds.chen@xxx.com', users[0].username def users = User.findAll(sort:'username',order:'asc', password:'pwd') assertEquals 100, users.size() users = User.findAll(new User(password:'pwd')) assertEquals 100, users.size() users = User.findAll{it.descend('password').constrain('pwd')} assertEquals 100, users.size() } void testFindAllWithTwoArguments() { def users = User.findAll([sort:'username',order:'asc'], new User(password:'pwd')) assertEquals 100, users.size() users = User.findAll(sort:'username',order:'asc') { it.descend('username').constrain('user#').like() } assertEquals 100, users.size() users = User.findAll(sort:'username',order:'asc',password:"sam's password") { it.descend('username').constrain('sam.ds.chen@xxx.com') } assertEquals 1, users.size() assertEquals "sam.ds.chen@xxx.com", users[0].username } void testFindAllWithThreeArguments() { def users = User.findAll([sort:'username',order:'asc'], [password:"sam's password"]) { } assertEquals 1, users.size() assertEquals "sam.ds.chen@xxx.com", users[0].username users = User.findAll([sort:'username',order:'asc'], new User(password:"sam's password")) { it.descend('password').constrain("sam's password") } assertEquals 1, users.size() assertEquals "sam.ds.chen@xxx.com", users[0].username } }
测试结果
-------------------------------------------------------
Running 2 integration tests...
Running test com.grs.sctms.FindMethodIntegrationTests...PASSED
Running test com.grs.sctms.ValidationIntegrationTests...PASSED
Tests Completed in 2485ms ...
-------------------------------------------------------
Tests passed: 5
Tests failed: 0
-------------------------------------------------------
不过有个小问题: 测试代码中被注释掉的部分
// def users = User.findAll(sort:'username',order:'asc') // assertEquals 'sam.ds.chen@xxx.com', users[0].username
通不过。每次跑的
users[0].username
的值都不一样 - 看来测试环境下排序没生效。而用grails run-app把它跑起来的时候,经debug证实数据是排好序的!怀疑是Grails的bug...