hal/src/zutil/db/bean/DBBeanSQLResultHandler.java

229 lines
7.6 KiB
Java
Raw Normal View History

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;
import java.util.HashMap;
2010-09-04 16:53:05 +00:00
import java.util.LinkedList;
import java.util.List;
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;
import zutil.log.LogUtil;
2010-09-04 16:53:05 +00:00
public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{
public static final Logger logger = LogUtil.getLogger();
/** This is the time to live for the cached items **/
2010-11-09 17:17:53 +00:00
public static final long CACHE_TTL = 1000*60*10; // 10 min 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>();
2010-11-09 17:17:53 +00:00
logger.fine("Loading new DBBean List");
2010-09-04 16:53:05 +00:00
while( result.next() ){
DBBean obj = createBean(result);
bean_list.add( obj );
}
return (T) bean_list;
}
else{
2010-11-09 17:17:53 +00:00
if( result.next() ){
2010-09-04 16:53:05 +00:00
return (T) createBean(result);
2010-11-09 17:17:53 +00:00
}
2010-09-04 16:53:05 +00:00
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{
try {
2010-10-30 13:04:45 +00:00
Long id = result.getLong( "id" );
DBBean obj = getCachedDBBean(bean_class, id);
if( obj != null )
return obj;
2010-11-09 17:17:53 +00:00
logger.fine("Loading new DBBean("+bean_class.getName()+") with id: "+id);
obj = bean_class.newInstance();
cacheDBBean(obj, id);
2010-10-27 13:49:46 +00:00
2010-11-09 18:58:52 +00:00
// Set id field
2010-10-30 13:04:45 +00:00
obj.id = id;
2010-10-27 13:49:46 +00:00
// Get the rest
for( Field field : bean_config.fields ){
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-11-09 18:58:52 +00:00
Long subid = result.getLong( 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 );
2010-11-09 18:58:52 +00:00
DBBeanConfig subConfig = DBBean.getBeanConfig( linkTable.beanClass() );
String linkTableName = linkTable.name();
String subTable = subConfig.tableName;
String idcol = (linkTable.idColumn().isEmpty() ? bean_config.tableName : linkTable.idColumn() );
2010-09-04 16:53:05 +00:00
// Load list from link table
2010-11-09 18:58:52 +00:00
//String subsql = "SELECT * FROM "+linkTableName+" NATURAL JOIN "+subConfig.tableName+" WHERE "+idcol+"=?";
String subsql = "SELECT obj.* FROM "+linkTableName+" as link, "+subTable+" as obj WHERE obj."+idcol+"=? AND obj.id=link.id";
2010-11-09 17:17:53 +00:00
logger.finest("List Load Query: "+subsql);
PreparedStatement subStmt = db.getPreparedStatement( subsql );
2010-11-09 18:58:52 +00:00
subStmt.setObject(1, obj.getId() );
2010-09-04 16:53:05 +00:00
List<? extends DBBean> list = DBConnection.exec(subStmt,
DBBeanSQLResultHandler.createList(linkTable.beanClass(), db));
2010-09-04 16:53:05 +00:00
obj.setFieldValue(field, list);
}
}
// Normal field
else
2010-09-04 16:53:05 +00:00
obj.setFieldValue(field, result.getObject(name));
}
return obj;
2010-11-09 18:58:52 +00:00
} catch (Exception e) {
2010-09-04 16:53:05 +00:00
throw new SQLException(e);
}
}
/**
* 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
}