2010-09-04 16:53:05 +00:00
|
|
|
package zutil.db.bean;
|
|
|
|
|
|
|
|
|
|
import java.lang.reflect.Field;
|
|
|
|
|
import java.sql.PreparedStatement;
|
|
|
|
|
import java.sql.SQLException;
|
|
|
|
|
import java.sql.Statement;
|
|
|
|
|
import java.sql.ResultSet;
|
2010-10-07 11:27:02 +00:00
|
|
|
import java.util.HashMap;
|
2010-09-04 16:53:05 +00:00
|
|
|
import java.util.LinkedList;
|
|
|
|
|
import java.util.List;
|
2010-10-07 11:27:02 +00:00
|
|
|
import java.util.logging.Logger;
|
2010-09-04 16:53:05 +00:00
|
|
|
|
|
|
|
|
import zutil.db.DBConnection;
|
|
|
|
|
import zutil.db.SQLResultHandler;
|
|
|
|
|
import zutil.db.bean.DBBean.DBBeanConfig;
|
|
|
|
|
import zutil.db.bean.DBBean.DBLinkTable;
|
2010-10-07 11:27:02 +00:00
|
|
|
import zutil.log.LogUtil;
|
2010-09-04 16:53:05 +00:00
|
|
|
|
|
|
|
|
public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{
|
2010-10-07 11:27:02 +00:00
|
|
|
public static final Logger logger = LogUtil.getLogger();
|
|
|
|
|
/** This is the time to live for the cached items **/
|
|
|
|
|
public static final long CACHE_TTL = 1000*60*10; // in ms
|
|
|
|
|
/** A cache for detecting recursion **/
|
|
|
|
|
protected static HashMap<Class<?>, HashMap<Object,DBBeanCache>> cache =
|
|
|
|
|
new HashMap<Class<?>, HashMap<Object,DBBeanCache>>();
|
|
|
|
|
/**
|
|
|
|
|
* A cache container that contains a object and last read time
|
|
|
|
|
*/
|
|
|
|
|
protected static class DBBeanCache{
|
|
|
|
|
public long timestamp;
|
|
|
|
|
public DBBean bean;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-04 16:53:05 +00:00
|
|
|
private Class<? extends DBBean> bean_class;
|
|
|
|
|
private DBBeanConfig bean_config;
|
|
|
|
|
private DBConnection db;
|
|
|
|
|
private boolean list;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new instance of this class that returns only one bean
|
|
|
|
|
*
|
|
|
|
|
* @param cl is the DBBean class that will be parsed from the SQL result
|
|
|
|
|
* @return a new instance of this class
|
|
|
|
|
*/
|
|
|
|
|
public static <C extends DBBean> DBBeanSQLResultHandler<C> create(Class<C> cl){
|
|
|
|
|
return new DBBeanSQLResultHandler<C>(cl, null, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new instance of this class that returns a bean with all its containing beans
|
|
|
|
|
*
|
|
|
|
|
* @param cl is the DBBean class that will be parsed from the SQL result
|
|
|
|
|
* @param db is the DB connection for loading internal beans
|
|
|
|
|
* @return a new instance of this class
|
|
|
|
|
*/
|
|
|
|
|
public static <C extends DBBean> DBBeanSQLResultHandler<C> create(Class<C> cl, DBConnection db){
|
|
|
|
|
return new DBBeanSQLResultHandler<C>(cl, db, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new instance of this class that returns a list of beans
|
|
|
|
|
*
|
|
|
|
|
* @param cl is the DBBean class that will be parsed from the SQL result
|
|
|
|
|
* @return a new instance of this class
|
|
|
|
|
*/
|
|
|
|
|
public static <C extends DBBean> DBBeanSQLResultHandler<List<C>> createList(Class<C> cl){
|
|
|
|
|
return new DBBeanSQLResultHandler<List<C>>(cl, null, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new instance of this class that returns a list of beans with all the internal beans
|
|
|
|
|
*
|
|
|
|
|
* @param cl is the DBBean class that will be parsed from the SQL result
|
|
|
|
|
* @param db is the DB connection for loading internal beans
|
|
|
|
|
* @return a new instance of this class
|
|
|
|
|
*/
|
|
|
|
|
public static <C extends DBBean> DBBeanSQLResultHandler<List<C>> createList(Class<C> cl, DBConnection db){
|
|
|
|
|
return new DBBeanSQLResultHandler<List<C>>(cl, db, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a new instance of this class
|
|
|
|
|
*
|
|
|
|
|
* @param cl is the DBBean class that will be parsed from the SQL result
|
|
|
|
|
* @param db is the DB connection for loading internal beans, may be null to disable internal beans
|
|
|
|
|
* @param list is if the handler should return a list of beans instead of one
|
|
|
|
|
*/
|
|
|
|
|
protected DBBeanSQLResultHandler(Class<? extends DBBean> cl, DBConnection db, boolean list) {
|
|
|
|
|
this.bean_class = cl;
|
|
|
|
|
this.list = list;
|
|
|
|
|
this.db = db;
|
|
|
|
|
this.bean_config = DBBean.getBeanConfig( cl );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Is called to handle an result from an query.
|
|
|
|
|
*
|
|
|
|
|
* @param stmt is the query
|
|
|
|
|
* @param result is the ResultSet
|
|
|
|
|
*/
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
|
public T handleQueryResult(Statement stmt, ResultSet result) throws SQLException{
|
|
|
|
|
if( list ){
|
|
|
|
|
LinkedList<DBBean> bean_list = new LinkedList<DBBean>();
|
|
|
|
|
while( result.next() ){
|
|
|
|
|
DBBean obj = createBean(result);
|
|
|
|
|
bean_list.add( obj );
|
|
|
|
|
}
|
|
|
|
|
return (T) bean_list;
|
|
|
|
|
}
|
|
|
|
|
else{
|
|
|
|
|
if( result.next() )
|
|
|
|
|
return (T) createBean(result);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Instantiates a new bean and assigns field values from the ResultSet
|
|
|
|
|
*
|
|
|
|
|
* @param result is where the field values for the bean will bee read from, the cursor should be in front of the data
|
|
|
|
|
* @return a new instance of the bean
|
|
|
|
|
*/
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
|
private DBBean createBean(ResultSet result) throws SQLException{
|
2010-10-07 11:27:02 +00:00
|
|
|
try {
|
|
|
|
|
Object id = result.getObject( bean_config.id_field.getName() );
|
|
|
|
|
DBBean obj = getCachedDBBean(bean_class, id);
|
|
|
|
|
if( obj != null )
|
|
|
|
|
return obj;
|
|
|
|
|
obj = bean_class.newInstance();
|
|
|
|
|
cacheDBBean(obj, id);
|
2010-10-27 13:49:46 +00:00
|
|
|
|
|
|
|
|
// Get id field
|
|
|
|
|
obj.setFieldValue(bean_config.id_field, result.getLong(bean_config.id_field.getName()));
|
|
|
|
|
// Get the rest
|
|
|
|
|
for( Field field : bean_config.fields ){
|
|
|
|
|
// Skip id field (its already loaded)
|
|
|
|
|
if( field.equals(bean_config.id_field) )
|
|
|
|
|
continue;
|
2010-09-04 16:53:05 +00:00
|
|
|
String name = field.getName();
|
|
|
|
|
|
|
|
|
|
// Another DBBean class
|
|
|
|
|
if( DBBean.class.isAssignableFrom( field.getType() )){
|
|
|
|
|
if(db != null){
|
2010-10-07 11:27:02 +00:00
|
|
|
Object subid = result.getObject(name);
|
|
|
|
|
DBBean subobj = getCachedDBBean(field.getType(), subid);
|
|
|
|
|
if( subobj == null )
|
|
|
|
|
subobj = DBBean.load(db, (Class<? extends DBBean>)field.getType(), subid);
|
2010-09-04 16:53:05 +00:00
|
|
|
obj.setFieldValue(field, subobj);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// A list of DBBeans
|
|
|
|
|
else if( List.class.isAssignableFrom( field.getType() ) &&
|
|
|
|
|
field.getAnnotation( DBLinkTable.class ) != null){
|
|
|
|
|
if(db != null){
|
2010-09-07 19:14:13 +00:00
|
|
|
DBLinkTable linkTable = field.getAnnotation( DBLinkTable.class );
|
|
|
|
|
String subtable = linkTable.name();
|
|
|
|
|
String idcol = (linkTable.column().isEmpty() ? bean_config.tableName : linkTable.column() );
|
2010-09-04 16:53:05 +00:00
|
|
|
|
|
|
|
|
// Load list from link table
|
2010-10-07 11:27:02 +00:00
|
|
|
PreparedStatement subStmt = db.getPreparedStatement("SELECT * FROM "+subtable+" WHERE ?=?");
|
|
|
|
|
subStmt.setString(1, idcol);
|
|
|
|
|
subStmt.setObject(2, bean_config.id_field.get( obj ));
|
2010-09-04 16:53:05 +00:00
|
|
|
List<? extends DBBean> list = DBConnection.exec(subStmt,
|
2010-10-07 11:27:02 +00:00
|
|
|
DBBeanSQLResultHandler.createList(linkTable.beanClass(), db));
|
2010-09-04 16:53:05 +00:00
|
|
|
obj.setFieldValue(field, list);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Normal field
|
2010-10-07 11:27:02 +00:00
|
|
|
else
|
2010-09-04 16:53:05 +00:00
|
|
|
obj.setFieldValue(field, result.getObject(name));
|
|
|
|
|
}
|
|
|
|
|
return obj;
|
|
|
|
|
|
|
|
|
|
} catch (InstantiationException e) {
|
|
|
|
|
throw new SQLException(e);
|
|
|
|
|
} catch (IllegalAccessException e) {
|
|
|
|
|
throw new SQLException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2010-10-07 11:27:02 +00:00
|
|
|
/**
|
|
|
|
|
* Adds the given object to the cache
|
|
|
|
|
*
|
|
|
|
|
* @param obj is the object to cache
|
|
|
|
|
* @param id is the id object of the bean
|
|
|
|
|
*/
|
|
|
|
|
protected static void cacheDBBean(DBBean obj, Object id) {
|
|
|
|
|
DBBeanCache item = new DBBeanCache();
|
|
|
|
|
item.timestamp = System.currentTimeMillis();
|
|
|
|
|
item.bean = obj;
|
|
|
|
|
if( cache.containsKey(obj.getClass()) )
|
|
|
|
|
cache.get(obj.getClass()).put(id, item);
|
|
|
|
|
else{
|
|
|
|
|
HashMap<Object, DBBeanCache> map = new HashMap<Object, DBBeanCache>();
|
|
|
|
|
map.put(id, item);
|
|
|
|
|
cache.put(obj.getClass(), map);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param c is the class of the bean
|
|
|
|
|
* @param id is the id object of the bean
|
|
|
|
|
* @return an cached DBBean object or null if the object is not cached or has expired
|
|
|
|
|
*/
|
|
|
|
|
protected static DBBean getCachedDBBean(Class<?> c, Object id){
|
|
|
|
|
if( cache.containsKey(c) ){
|
|
|
|
|
DBBeanCache item = cache.get(c).get(id);
|
|
|
|
|
// Check if the cache is valid
|
|
|
|
|
if( item != null && item.timestamp+CACHE_TTL > System.currentTimeMillis() ){
|
|
|
|
|
return item.bean;
|
|
|
|
|
}
|
|
|
|
|
// The cache is old, remove it and return null
|
|
|
|
|
else{
|
|
|
|
|
logger.finer("Cache to old: "+c.getName()+" ID: "+id);
|
|
|
|
|
cache.get(c).remove(id);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
logger.finer("Cache miss: "+c.getName()+" ID: "+id);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2010-09-04 16:53:05 +00:00
|
|
|
}
|