From 497f7807f3588f971e3ad664c7157a7302f71c98 Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Mon, 30 Aug 2010 15:23:58 +0000 Subject: [PATCH] Initial upload of DBBean class may contain bugs --- src/zutil/db/bean/DBBean.java | 307 +++++++++++++++++++++ src/zutil/db/bean/DBBeanResultHandler.java | 106 +++++++ 2 files changed, 413 insertions(+) create mode 100644 src/zutil/db/bean/DBBean.java create mode 100644 src/zutil/db/bean/DBBeanResultHandler.java diff --git a/src/zutil/db/bean/DBBean.java b/src/zutil/db/bean/DBBean.java new file mode 100644 index 0000000..0bf753f --- /dev/null +++ b/src/zutil/db/bean/DBBean.java @@ -0,0 +1,307 @@ +package zutil.db.bean; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.NoSuchElementException; + +import zutil.db.DBConnection; + +/** + * The class that extends this will be able to save its state to a DB. + * Fields that are transient will be ignored, and fields that extend + * DBBean will be replaced by the id field of that class. + * + * @author Ziver + */ +public abstract class DBBean { + + /** + * Sets the name of the table in the database + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + public @interface DBTable { + String value(); + } + + /** + * Sets the field as the id column in the table + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface DBTableID {} + + /** + * A Class that contains information about a bean + */ + protected static class DBBeanConfig{ + /** The name of the table in the DB */ + protected String tableName; + /** The id field */ + protected Field id_field; + /** All the fields in the bean */ + protected ArrayList fields; + + protected DBBeanConfig(){ + fields = new ArrayList(); + } + } + /** + * This is a cache of all the initialized beans + */ + private static HashMap,DBBeanConfig> beanConfigs = new HashMap,DBBeanConfig>(); + + + protected DBBean(){ + if( !beanConfigs.containsKey( this.getClass() ) ) + initBeanConfig( this.getClass() ); + } + + /** + * @return the ID field of the bean or null if there is non + */ + public static Field getIDField(Class c){ + if( !beanConfigs.containsKey( c ) ) + initBeanConfig( c ); + return beanConfigs.get( c ).id_field; + } + + /** + * @return all the fields except the ID field + */ + public static ArrayList getFields(Class c){ + if( !beanConfigs.containsKey( c ) ) + initBeanConfig( c ); + return beanConfigs.get( c ).fields; + } + + /** + * @return the configuration object for the specified class + */ + protected static DBBeanConfig getBeanConfig(Class c){ + if( !beanConfigs.containsKey( c ) ) + initBeanConfig( c ); + return beanConfigs.get( c ); + } + + /** + * Caches the fields + */ + private static void initBeanConfig(Class c){ + Field[] fields = c.getDeclaredFields(); + DBBeanConfig config = new DBBeanConfig(); + // Find the table name + if( c.getAnnotation(DBTable.class) != null ) + config.tableName = c.getAnnotation(DBTable.class).value().replace('\"', ' '); + // Add the fields in the bean + for( Field field : fields ){ + if( !Modifier.isTransient(field.getModifiers()) ){ + if(field.getAnnotation(DBBean.DBTableID.class) != null) + config.id_field = field; + config.fields.add( field ); + } + } + + beanConfigs.put(c, config); + } + + /** + * Saves the Object to the DB + */ + public void save(DBConnection sql) throws SQLException{ + Class c = this.getClass(); + DBBeanConfig config = beanConfigs.get(c); + try { + // Generate the SQL + StringBuilder query = new StringBuilder(); + query.append("REPLACE INTO ? "); + + for( Field field : config.fields ){ + query.append(" "); + query.append(field.getName()); + query.append("=?, "); + } + query.delete( query.length()-2, query.length()); + PreparedStatement stmt = sql.getPreparedStatement( sql.toString() ); + // add the table name + stmt.setObject(1, config.tableName); + // Put in the variables in the SQL + for(int i=0; i c = this.getClass(); + DBBeanConfig config = beanConfigs.get(c); + if( config.id_field == null ) + throw new NoSuchElementException("DBTableID annotation missing in bean!"); + try { + PreparedStatement stmt = sql.getPreparedStatement( + "DELETE FROM ? WHERE "+ config.id_field +"=?"); + // Put in the variables in the SQL + stmt.setObject(1, config.tableName ); + stmt.setObject(2, config.id_field.get(this) ); + + // Execute the SQL + DBConnection.exec(stmt); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * Loads all the rows in the table into a LinkedList + * + * @param is the class of the bean + * @param c is the class of the bean + * @return a LinkedList with all the Beans in the DB + */ + public static List load(DBConnection sql, Class c) throws SQLException { + // Initiate a BeanConfig if there is non + if( !beanConfigs.containsKey( c ) ) + initBeanConfig( c ); + DBBeanConfig config = beanConfigs.get(c); + // Generate query + PreparedStatement stmt = sql.getPreparedStatement( "SELECT * FROM ?" ); + stmt.setString(1, config.tableName); + // Run query + List list = DBConnection.exec(stmt, DBBeanResultHandler.createList(c) ); + return list; + } + + /** + * Loads all the rows in the table into a LinkedList + * + * @param is the class of the bean + * @param c is the class of the bean + * @param id is the id value of the bean + * @return a DBBean Object with the specific id or null + */ + public static T load(DBConnection sql, Class c, Object id) throws SQLException { + // Initiate a BeanConfig if there is non + if( !beanConfigs.containsKey( c ) ) + initBeanConfig( c ); + DBBeanConfig config = beanConfigs.get(c); + // Generate query + PreparedStatement stmt = sql.getPreparedStatement( "SELECT * FROM ? WHERE ?=? LIMIT 1" ); + stmt.setString(1, config.tableName); + stmt.setString(2, config.id_field.getName()); + stmt.setObject(3, id ); + // Run query + T obj = DBConnection.exec(stmt, DBBeanResultHandler.create(c) ); + return obj; + } + + /** + * Creates a specific table for the given Bean + */ + public static void create(DBConnection sql, Class c) throws SQLException{ + if( !beanConfigs.containsKey( c ) ) + initBeanConfig( c ); + DBBeanConfig config = beanConfigs.get(c); + + // Generate the SQL + StringBuilder query = new StringBuilder(); + query.append("CREATE TABLE ? ( "); + + for( Field field : config.fields ){ + query.append(" "); + query.append( field.getName() ); + query.append( classToDBName(c) ); + if( config.id_field.equals( field ) ) + query.append(" PRIMARY KEY"); + query.append(", "); + } + query.delete( query.length()-2, query.length()); + query.append(")"); + PreparedStatement stmt = sql.getPreparedStatement( sql.toString() ); + // add the table name + stmt.setObject(1, config.tableName); + + // Execute the SQL + DBConnection.exec(stmt); + } + + private static String classToDBName(Class c){ + if( c == String.class) return "CLOB"; // TEXT + else if(c == Short.class) return "SMALLINT"; + else if(c == short.class) return "SMALLINT"; + else if(c == Integer.class) return "INTEGER"; + else if(c == int.class) return "INTEGER"; + else if(c == BigInteger.class) return "BIGINT"; + else if(c == Long.class) return "DECIMAL"; + else if(c == long.class) return "DECIMAL"; + else if(c == Float.class) return "DOUBLE"; + else if(c == float.class) return "DOUBLE"; + else if(c == Double.class) return "DOUBLE"; + else if(c == double.class) return "DOUBLE"; + else if(c == BigDecimal.class) return "DECIMAL"; + else if(c == Boolean.class) return "BOOLEAN"; + else if(c == boolean.class) return "BOOLEAN"; + else if(c == Byte.class) return "BINARY(1)"; + else if(c == byte.class) return "BINARY(1)"; + return null; + } + + /** + * This is a workaround if the field is not visible to other classes + * + * @param field is the field + * @return the value of the field + */ + protected Object getFieldValue(Field field){ + try { + return field.get(this); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * This is a workaround if the field is not visible to other classes + * + * @param field is the field + * @return the value of the field + */ + protected void setFieldValue(Field field, Object o){ + try { + field.set(this, o); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/src/zutil/db/bean/DBBeanResultHandler.java b/src/zutil/db/bean/DBBeanResultHandler.java new file mode 100644 index 0000000..2d24b5b --- /dev/null +++ b/src/zutil/db/bean/DBBeanResultHandler.java @@ -0,0 +1,106 @@ +package zutil.db.bean; + +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.ResultSet; +import java.util.LinkedList; +import java.util.List; + +import zutil.db.SQLResultHandler; +import zutil.db.bean.DBBean.DBBeanConfig; + +public class DBBeanResultHandler implements SQLResultHandler{ + + private Class bean_class; + private DBBeanConfig bean_config; + 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 DBBeanResultHandler create(Class cl){ + return new DBBeanResultHandler(cl, 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 DBBeanResultHandler> createList(Class cl){ + return new DBBeanResultHandler>(cl, true); + } + + /** + * Creates a new instance of this class + * + * @param cl is the DBBean class that will be parsed from the SQL result + */ + protected DBBeanResultHandler(Class cl, boolean list) { + this.bean_class = cl; + this.list = list; + 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 handle(Statement stmt, ResultSet result) throws SQLException{ + if( list ){ + LinkedList bean_list = new LinkedList(); + 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{ + try { + DBBean obj = bean_class.newInstance(); + + for( Field field : bean_config.fields ){ + String name = field.getName(); + + // Another DBBean class + if( DBBean.class.isAssignableFrom( field.getDeclaringClass() )){ + DBBean subobj = DBBean.load(null, (Class)field.getDeclaringClass(), result.getObject(name)); + obj.setFieldValue(field, subobj); + } + // Normal field + else + obj.setFieldValue(field, result.getObject(name)); + } + return obj; + + } catch (InstantiationException e) { + throw new SQLException(e); + } catch (IllegalAccessException e) { + throw new SQLException(e); + } + } + +}