Moved the id field into DBBean instead of the sub class

This commit is contained in:
Ziver Koc 2010-10-07 11:27:02 +00:00
parent 0cc2f253e3
commit b8bf1946d5
2 changed files with 207 additions and 92 deletions

View file

@ -14,8 +14,10 @@ import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.logging.Logger;
import zutil.db.DBConnection; import zutil.db.DBConnection;
import zutil.log.LogUtil;
/** /**
* <XMP> * <XMP>
@ -38,6 +40,10 @@ import zutil.db.DBConnection;
* @author Ziver * @author Ziver
*/ */
public abstract class DBBean { public abstract class DBBean {
public static final Logger logger = LogUtil.getLogger();
/** The id of the bean **/
private Long id;
/** /**
* Sets the name of the table in the database * Sets the name of the table in the database
@ -45,9 +51,9 @@ public abstract class DBBean {
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
public @interface DBTable { public @interface DBTable {
String value(); String value();
} }
/** /**
* Sets the name of the table that links different DBBeans together * Sets the name of the table that links different DBBeans together
*/ */
@ -55,17 +61,20 @@ public abstract class DBBean {
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
public @interface DBLinkTable { public @interface DBLinkTable {
/** The name of the Link table */ /** The name of the Link table */
String name(); String name();
/** The name of the column that contains this objects id */ /** The class of the linked bean */
String column() default ""; Class<? extends DBBean> beanClass();
/** The name of the column that contains this objects id */
String column() default "";
} }
/** /**
* Sets the field as the id column in the table * Sets the field as the id column in the table
*/ */
@Retention(RetentionPolicy.RUNTIME) /*@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
public @interface DBTableID {} public @interface DBTableID {}
*/
/** /**
* A Class that contains information about a bean * A Class that contains information about a bean
@ -77,23 +86,23 @@ public abstract class DBBean {
protected Field id_field; protected Field id_field;
/** All the fields in the bean */ /** All the fields in the bean */
protected ArrayList<Field> fields; protected ArrayList<Field> fields;
protected DBBeanConfig(){ protected DBBeanConfig(){
fields = new ArrayList<Field>(); fields = new ArrayList<Field>();
} }
} }
/** This is a cache of all the initialized beans */ /** This is a cache of all the initialized beans */
private static HashMap<Class<? extends DBBean>,DBBeanConfig> beanConfigs = new HashMap<Class<? extends DBBean>,DBBeanConfig>(); private static HashMap<Class<? extends DBBean>,DBBeanConfig> beanConfigs = new HashMap<Class<? extends DBBean>,DBBeanConfig>();
/** This value is for preventing recursive loops */ /** This value is for preventing recursive loops */
private boolean processing; private boolean processing;
protected DBBean(){ protected DBBean(){
if( !beanConfigs.containsKey( this.getClass() ) ) if( !beanConfigs.containsKey( this.getClass() ) )
initBeanConfig( this.getClass() ); initBeanConfig( this.getClass() );
processing = false; processing = false;
} }
/** /**
* @return the ID field of the bean or null if there is non * @return the ID field of the bean or null if there is non
*/ */
@ -102,7 +111,7 @@ public abstract class DBBean {
initBeanConfig( c ); initBeanConfig( c );
return beanConfigs.get( c ).id_field; return beanConfigs.get( c ).id_field;
} }
/** /**
* @return all the fields except the ID field * @return all the fields except the ID field
*/ */
@ -111,7 +120,7 @@ public abstract class DBBean {
initBeanConfig( c ); initBeanConfig( c );
return beanConfigs.get( c ).fields; return beanConfigs.get( c ).fields;
} }
/** /**
* @return the configuration object for the specified class * @return the configuration object for the specified class
*/ */
@ -120,7 +129,7 @@ public abstract class DBBean {
initBeanConfig( c ); initBeanConfig( c );
return beanConfigs.get( c ); return beanConfigs.get( c );
} }
/** /**
* Caches the fields * Caches the fields
*/ */
@ -129,113 +138,149 @@ public abstract class DBBean {
DBBeanConfig config = new DBBeanConfig(); DBBeanConfig config = new DBBeanConfig();
// Find the table name // Find the table name
if( c.getAnnotation(DBTable.class) != null ) if( c.getAnnotation(DBTable.class) != null )
config.tableName = c.getAnnotation(DBTable.class).value().replace('\"', ' '); config.tableName = c.getAnnotation(DBTable.class).value().replace('\"', ' ');
// Add the fields in the bean // Add the fields in the bean
for( Field field : fields ){ for( Field field : fields ){
if( !Modifier.isTransient(field.getModifiers()) ){ int mod = field.getModifiers();
if(field.getAnnotation(DBBean.DBTableID.class) != null) if( !Modifier.isTransient( mod ) &&
config.id_field = field; !Modifier.isAbstract( mod ) &&
!Modifier.isFinal( mod ) &&
!Modifier.isStatic( mod ) &&
!Modifier.isInterface( mod ) &&
!Modifier.isNative( mod )){
//if(field.getAnnotation(DBBean.DBTableID.class) != null)
// config.id_field = field;
config.fields.add( field ); config.fields.add( field );
} }
} }
try {
config.id_field = DBBean.class.getDeclaredField("id");
config.id_field.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
beanConfigs.put(c, config); beanConfigs.put(c, config);
} }
/** /**
* Saves the Object to the DB * Saves the Object to the DB
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public void save(DBConnection sql) throws SQLException{ public void save(DBConnection db) throws SQLException{
if(processing) if(processing)
return; return;
processing = true; processing = true;
Class<? extends DBBean> c = this.getClass(); Class<? extends DBBean> c = this.getClass();
DBBeanConfig config = beanConfigs.get(c); DBBeanConfig config = beanConfigs.get(c);
try { try {
Object id = getFieldValue( config.id_field );
// Generate the SQL // Generate the SQL
StringBuilder query = new StringBuilder(); StringBuilder query = new StringBuilder();
query.append("REPLACE INTO ? "); if( id == null )
query.append("INSERT INTO ");
else query.append("UPDATE ");
query.append( config.tableName );
StringBuilder params = new StringBuilder();
for( Field field : config.fields ){ for( Field field : config.fields ){
query.append(" "); if( !List.class.isAssignableFrom(field.getType()) ){
query.append(field.getName()); params.append(" ");
query.append("=?, "); params.append(field.getName());
params.append("=?,");
}
} }
query.delete( query.length()-2, query.length()); if( params.length() > 0 ){
PreparedStatement stmt = sql.getPreparedStatement( sql.toString() ); params.delete( params.length()-1, params.length());
// add the table name query.append( " SET" );
stmt.setObject(1, config.tableName); query.append( params );
if( id != null )
query.append( " id=?" );
}
logger.fine("Save query: "+query.toString());
PreparedStatement stmt = db.getPreparedStatement( query.toString() );
// Put in the variables in the SQL // Put in the variables in the SQL
for(int i=0; i<config.fields.size() ;++i ){ for(int i=0; i<config.fields.size() ;++i ){
Field field = config.fields.get(i); Field field = config.fields.get(i);
// Another DBBean class // Another DBBean class
if( DBBean.class.isAssignableFrom( field.getType() )){ if( DBBean.class.isAssignableFrom( field.getType() )){
DBBean subobj = (DBBean)field.get(this); DBBean subobj = (DBBean)getFieldValue(field);
subobj.save(sql); if(subobj != null){
stmt.setObject(i+2, getBeanConfig(subobj.getClass()) ); subobj.save(db);
DBBeanConfig subconfig = getBeanConfig(subobj.getClass());
stmt.setObject(i+1, subobj.getFieldValue(subconfig.id_field) );
}
else
stmt.setObject(i+1, null);
} }
// A list of DBBeans // A list of DBBeans
else if( List.class.isAssignableFrom( field.getType() ) && else if( List.class.isAssignableFrom( field.getType() ) &&
field.getAnnotation( DBLinkTable.class ) != null){ field.getAnnotation( DBLinkTable.class ) != null){
List<DBBean> list = (List<DBBean>)field.get(this); List<DBBean> list = (List<DBBean>)getFieldValue(field);
DBLinkTable linkTable = field.getAnnotation( DBLinkTable.class ); if( list != null ){
String subtable = linkTable.name(); DBLinkTable linkTable = field.getAnnotation( DBLinkTable.class );
String idcol = (linkTable.column().isEmpty() ? config.tableName : linkTable.column() ); String subtable = linkTable.name();
String idcol = (linkTable.column().isEmpty() ? config.tableName : linkTable.column() );
DBBeanConfig subConfig = null;
for(DBBean subobj : list){ DBBeanConfig subConfig = null;
if(subConfig == null) for(DBBean subobj : list){
subConfig = beanConfigs.get( subobj.getClass() ); if(subConfig == null)
// Save links in link table subConfig = beanConfigs.get( subobj.getClass() );
PreparedStatement subStmt = sql.getPreparedStatement("REPLACE INTO ? ?=? ?=?"); // Save links in link table
subStmt.setString(1, subtable); PreparedStatement subStmt = db.getPreparedStatement("REPLACE INTO "+subtable+" SET ?=? ?=?");
subStmt.setString(2, idcol); subStmt.setString(1, idcol);
subStmt.setObject(3, config.id_field); subStmt.setObject(2, config.id_field);
subStmt.setString(4, subConfig.tableName); subStmt.setString(3, subConfig.tableName);
subStmt.setObject(5, subConfig.id_field.get(subobj)); subStmt.setObject(4, subobj.getFieldValue(subConfig.id_field) );
DBConnection.exec(subStmt); DBConnection.exec(subStmt);
// Save the sub bean // Save the sub bean
subobj.save(sql); subobj.save(db);
}
} }
} }
// Normal field // Normal field
else else{
stmt.setObject(i+2, field.get(this)); Object value = getFieldValue(field);
stmt.setObject(i+1, value);
}
} }
if( id != null )
stmt.setObject(config.fields.size(), id);
// Execute the SQL // Execute the SQL
DBConnection.exec(stmt); DBConnection.exec(stmt);
setFieldValue( config.id_field, db.getLastInsertID() );
} catch (SQLException e) { } catch (SQLException e) {
throw e; throw e;
} catch (Exception e) { } catch (Exception e) {
throw new SQLException(e); throw new SQLException(e);
} }
} }
/** /**
* Deletes the object from the DB, WARNING will not delete sub beans * Deletes the object from the DB, WARNING will not delete sub beans
*/ */
public void delete(DBConnection sql){ public void delete(DBConnection db){
Class<? extends DBBean> c = this.getClass(); Class<? extends DBBean> c = this.getClass();
DBBeanConfig config = beanConfigs.get(c); DBBeanConfig config = beanConfigs.get(c);
if( config.id_field == null ) if( config.id_field == null )
throw new NoSuchElementException("DBTableID annotation missing in bean!"); throw new NoSuchElementException("DBTableID annotation missing in bean!");
try { try {
PreparedStatement stmt = sql.getPreparedStatement( String sql = "DELETE FROM "+config.tableName+" WHERE "+ config.id_field +"=?";
"DELETE FROM ? WHERE "+ config.id_field +"=?"); logger.fine("Load query: "+sql);
// Put in the variables in the SQL PreparedStatement stmt = db.getPreparedStatement( sql );
stmt.setObject(1, config.tableName ); // Put in the variables in the SQL
stmt.setObject(2, config.id_field.get(this) ); logger.fine("Delete query: "+sql);
stmt.setObject(1, getFieldValue(config.id_field) );
// Execute the SQL // Execute the SQL
DBConnection.exec(stmt); DBConnection.exec(stmt);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
/** /**
* Loads all the rows in the table into a LinkedList * Loads all the rows in the table into a LinkedList
* *
@ -249,13 +294,14 @@ public abstract class DBBean {
initBeanConfig( c ); initBeanConfig( c );
DBBeanConfig config = beanConfigs.get(c); DBBeanConfig config = beanConfigs.get(c);
// Generate query // Generate query
PreparedStatement stmt = db.getPreparedStatement( "SELECT * FROM ?" ); String sql = "SELECT * FROM "+config.tableName;
stmt.setString(1, config.tableName); logger.fine("Load query: "+sql);
PreparedStatement stmt = db.getPreparedStatement( sql );
// Run query // Run query
List<T> list = DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(c, db) ); List<T> list = DBConnection.exec(stmt, DBBeanSQLResultHandler.createList(c, db) );
return list; return list;
} }
/** /**
* Loads all the rows in the table into a LinkedList * Loads all the rows in the table into a LinkedList
* *
@ -270,15 +316,14 @@ public abstract class DBBean {
initBeanConfig( c ); initBeanConfig( c );
DBBeanConfig config = beanConfigs.get(c); DBBeanConfig config = beanConfigs.get(c);
// Generate query // Generate query
PreparedStatement stmt = db.getPreparedStatement( "SELECT * FROM ? WHERE ?=? LIMIT 1" ); PreparedStatement stmt = db.getPreparedStatement( "SELECT * FROM "+config.tableName+" WHERE ?=? LIMIT 1" );
stmt.setString(1, config.tableName); stmt.setString(1, config.id_field.getName());
stmt.setString(2, config.id_field.getName()); stmt.setObject(2, id );
stmt.setObject(3, id );
// Run query // Run query
T obj = DBConnection.exec(stmt, DBBeanSQLResultHandler.create(c, db) ); T obj = DBConnection.exec(stmt, DBBeanSQLResultHandler.create(c, db) );
return obj; return obj;
} }
/** /**
* Creates a specific table for the given Bean * Creates a specific table for the given Bean
*/ */
@ -289,8 +334,8 @@ public abstract class DBBean {
// Generate the SQL // Generate the SQL
StringBuilder query = new StringBuilder(); StringBuilder query = new StringBuilder();
query.append("CREATE TABLE ? ( "); query.append("CREATE TABLE "+config.tableName+" ( ");
for( Field field : config.fields ){ for( Field field : config.fields ){
query.append(" "); query.append(" ");
query.append( field.getName() ); query.append( field.getName() );
@ -302,13 +347,11 @@ public abstract class DBBean {
query.delete( query.length()-2, query.length()); query.delete( query.length()-2, query.length());
query.append(")"); query.append(")");
PreparedStatement stmt = sql.getPreparedStatement( sql.toString() ); PreparedStatement stmt = sql.getPreparedStatement( sql.toString() );
// add the table name
stmt.setObject(1, config.tableName);
// Execute the SQL // Execute the SQL
DBConnection.exec(stmt); DBConnection.exec(stmt);
} }
private static String classToDBName(Class<?> c){ private static String classToDBName(Class<?> c){
if( c == String.class) return "CLOB"; // TEXT if( c == String.class) return "CLOB"; // TEXT
else if(c == Short.class) return "SMALLINT"; else if(c == Short.class) return "SMALLINT";
@ -329,7 +372,7 @@ public abstract class DBBean {
else if(c == byte.class) return "BINARY(1)"; else if(c == byte.class) return "BINARY(1)";
return null; return null;
} }
/** /**
* This is a workaround if the field is not visible to other classes * This is a workaround if the field is not visible to other classes
* *
@ -345,7 +388,7 @@ public abstract class DBBean {
} }
return null; return null;
} }
/** /**
* This is a workaround if the field is not visible to other classes * This is a workaround if the field is not visible to other classes
* *
@ -360,4 +403,11 @@ public abstract class DBBean {
e.printStackTrace(); e.printStackTrace();
} }
} }
/**
* @return the object id or null if the bean has not bean saved yet
*/
public Long getId(){
return id;
}
} }

View file

@ -5,16 +5,32 @@ import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.logging.Logger;
import zutil.db.DBConnection; import zutil.db.DBConnection;
import zutil.db.SQLResultHandler; import zutil.db.SQLResultHandler;
import zutil.db.bean.DBBean.DBBeanConfig; import zutil.db.bean.DBBean.DBBeanConfig;
import zutil.db.bean.DBBean.DBLinkTable; import zutil.db.bean.DBBean.DBLinkTable;
import zutil.log.LogUtil;
public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{ public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{
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;
}
private Class<? extends DBBean> bean_class; private Class<? extends DBBean> bean_class;
private DBBeanConfig bean_config; private DBBeanConfig bean_config;
private DBConnection db; private DBConnection db;
@ -108,16 +124,24 @@ public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private DBBean createBean(ResultSet result) throws SQLException{ private DBBean createBean(ResultSet result) throws SQLException{
try { try {
DBBean obj = bean_class.newInstance(); 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);
for( Field field : bean_config.fields ){ for( Field field : bean_config.fields ){
String name = field.getName(); String name = field.getName();
// Another DBBean class // Another DBBean class
if( DBBean.class.isAssignableFrom( field.getType() )){ if( DBBean.class.isAssignableFrom( field.getType() )){
if(db != null){ if(db != null){
DBBean subobj = DBBean.load(db, (Class<? extends DBBean>)field.getType(), result.getObject(name)); Object subid = result.getObject(name);
DBBean subobj = getCachedDBBean(field.getType(), subid);
if( subobj == null )
subobj = DBBean.load(db, (Class<? extends DBBean>)field.getType(), subid);
obj.setFieldValue(field, subobj); obj.setFieldValue(field, subobj);
} }
} }
@ -130,17 +154,16 @@ public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{
String idcol = (linkTable.column().isEmpty() ? bean_config.tableName : linkTable.column() ); String idcol = (linkTable.column().isEmpty() ? bean_config.tableName : linkTable.column() );
// Load list from link table // Load list from link table
PreparedStatement subStmt = db.getPreparedStatement("SELECT * FROM ? WHERE ?=?"); PreparedStatement subStmt = db.getPreparedStatement("SELECT * FROM "+subtable+" WHERE ?=?");
subStmt.setString(1, subtable); subStmt.setString(1, idcol);
subStmt.setString(2, idcol); subStmt.setObject(2, bean_config.id_field.get( obj ));
subStmt.setObject(3, bean_config.id_field.get( obj ));
List<? extends DBBean> list = DBConnection.exec(subStmt, List<? extends DBBean> list = DBConnection.exec(subStmt,
DBBeanSQLResultHandler.createList((Class<? extends DBBean>)field.getType(), db)); DBBeanSQLResultHandler.createList(linkTable.beanClass(), db));
obj.setFieldValue(field, list); obj.setFieldValue(field, list);
} }
} }
// Normal field // Normal field
else else
obj.setFieldValue(field, result.getObject(name)); obj.setFieldValue(field, result.getObject(name));
} }
return obj; return obj;
@ -152,4 +175,46 @@ public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{
} }
} }
/**
* 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;
}
} }