More Refactoring and added delete TC

This commit is contained in:
Ziver Koc 2017-02-17 17:43:23 +01:00
parent 5b15515432
commit f9dc882c1c
6 changed files with 456 additions and 263 deletions

View file

@ -25,23 +25,21 @@
package zutil.db.bean;
import zutil.db.DBConnection;
import zutil.db.bean.DBBeanConfig.DBBeanFieldConfig;
import zutil.db.bean.DBBeanConfig.DBBeanSubBeanConfig;
import zutil.log.LogUtil;
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.sql.Timestamp;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
@ -102,7 +100,7 @@ public abstract class DBBean {
String table();
/** The class of the linked bean */
Class<? extends DBBean> beanClass();
/** The name of the column that contains the main objects id, SQL rules apply, should not contain any strange characters or spaces */
/** The name of the column that contains the parent beans id, SQL rules apply, should not contain any strange characters or spaces */
String idColumn() default "";
}
@ -126,7 +124,7 @@ public abstract class DBBean {
/**
* Saves the object and all the sub objects to the DB
* Saves the bean and all its sub beans to the DB
*
* @param db is the DBMS connection
*/
@ -135,10 +133,10 @@ public abstract class DBBean {
}
/**
* Saves the Object to the DB
* Saves the bean to the DB
*
* @param db is the DBMS connection
* @param recursive is if all sub object also should be saved
* @param db the DBMS connection
* @param recursive if all sub beans should be saved also
*/
@SuppressWarnings("unchecked")
public void save(DBConnection db, boolean recursive) throws SQLException{
@ -151,35 +149,35 @@ public abstract class DBBean {
// Generate the SQL
StringBuilder query = new StringBuilder();
if (this.id == null) {
query.append("INSERT INTO ").append(config.tableName);
query.append("INSERT INTO ").append(config.getTableName());
StringBuilder sqlCols = new StringBuilder();
StringBuilder sqlValues = new StringBuilder();
for (Field field : config.fields) {
for (DBBeanFieldConfig field : config.getFields()) {
if (sqlCols.length() > 0)
sqlCols.append(", ");
sqlCols.append(DBBeanConfig.getFieldName(field));
sqlCols.append(field.getName());
if (sqlValues.length() > 0)
sqlValues.append(", ");
sqlValues.append("?");
}
if (config.fields.size() > 0) { // is there any fields?
if (config.getFields().size() > 0) { // is there any fields?
query.append(" (").append(sqlCols).append(")");
query.append(" VALUES(").append(sqlValues).append(")");
} else
query.append(" DEFAULT VALUES");
}
else if (config.fields.size() > 0) { // Is there any fields to update?
query.append("UPDATE ").append(config.tableName);
else if (config.getFields().size() > 0) { // Is there any fields to update?
query.append("UPDATE ").append(config.getTableName());
StringBuilder sqlSets = new StringBuilder();
for (Field field : config.fields) {
for (DBBeanFieldConfig field : config.getFields()) {
if (sqlSets.length() > 0)
sqlSets.append(", ");
sqlSets.append(DBBeanConfig.getFieldName(field));
sqlSets.append(field.getName());
sqlSets.append("=?");
}
query.append(" SET ").append(sqlSets);
query.append(" WHERE ").append(config.idColumn).append("=?");
query.append(" WHERE ").append(config.getIdColumnName()).append("=?");
}
// Check if we have a valid query to run, skip otherwise
@ -189,10 +187,10 @@ public abstract class DBBean {
PreparedStatement stmt = db.getPreparedStatement(sql);
// Put in the variables in the SQL
int index = 1;
for (Field field : config.fields) {
for (DBBeanFieldConfig field : config.getFields()) {
// Another DBBean class
if (DBBean.class.isAssignableFrom(field.getType())) {
DBBean subObj = (DBBean) getFieldValue(field);
DBBean subObj = (DBBean) field.getValue(this);
if (subObj != null) {
if (recursive || subObj.getId() == null)
subObj.save(db);
@ -203,7 +201,7 @@ public abstract class DBBean {
}
// Normal field
else {
Object value = getFieldValue(field);
Object value = field.getValue(this);
stmt.setObject(index, value);
index++;
}
@ -213,58 +211,50 @@ public abstract class DBBean {
// Execute the SQL
DBConnection.exec(stmt);
if (this.id == null) {
if (this.id == null)
this.id = db.getLastInsertID(stmt);
// Add this bean to the cache
DBBeanCache.add(this);
}
}
// Update cache
DBBeanCache.add(this);
// Save sub beans, after we get the parent object id
for (Field field : config.subBeanFields) {
if (this.id == null)
throw new SQLException("Unknown parent object id");
// Save sub beans, after we get the parent beans id
if (recursive){
for (DBBeanSubBeanConfig subBeanField : config.getSubBeans()) {
if (this.id == null)
throw new SQLException("Unknown parent bean id");
List<DBBean> list = (List<DBBean>) getFieldValue(field);
if (list != null) {
DBLinkTable linkTableAnnotation = field.getAnnotation(DBLinkTable.class);
String linkTable = linkTableAnnotation.table();
String idCol = (linkTableAnnotation.idColumn().isEmpty() ? config.tableName : linkTableAnnotation.idColumn());
String subIdCol = "id";
List<DBBean> list = (List<DBBean>) subBeanField.getValue(this);
if (list != null) {
for (DBBean subObj : list) {
// Save the sub bean
subObj.save(db);
if (subObj.getId() == null) {
logger.severe("Unable to save field " + c.getSimpleName() + "." + subBeanField.getName() +
" with " + subObj.getClass().getSimpleName() + " because sub bean id is null");
continue;
}
// Get the Sub bean configuration
String subIdCol = subBeanField.getSubBeanConfig().getIdColumnName();
DBBeanConfig subObjConfig = null;
for (DBBean subObj : list) {
// Save the sub bean
if (recursive || subObj.getId() == null)
subObj.save(db);
if (subObj.getId() == null) {
logger.severe("Unable to save field "+ c.getSimpleName()+"."+field.getName() +
" with "+ subObj.getClass().getSimpleName() +" because sub bean id is null");
continue;
}
// Get the Sub object configuration
if (subObjConfig == null) {
subObjConfig = DBBeanConfig.getBeanConfig(subObj.getClass());
subIdCol = subObjConfig.idColumn;
}
// Save links in link table
String sql;
if (linkTable.equals(subObjConfig.tableName))
sql = "UPDATE " + linkTable + " SET " + idCol + "=? WHERE " + subIdCol + "=?";
else
sql = "INSERT INTO " + linkTable + " (" + idCol + ", " + subIdCol + ") SELECT ?,? " +
"WHERE NOT EXISTS(SELECT 1 FROM " + linkTable + " WHERE " + idCol + "=? AND " + subIdCol + "=?);";
logger.finest("Save sub Bean(" + c.getName() + ", id: " + subObj.getId() + ") query: " + sql);
PreparedStatement subStmt = db.getPreparedStatement(sql);
subStmt.setLong(1, this.id);
subStmt.setLong(2, subObj.getId());
if (subStmt.getParameterMetaData().getParameterCount() > 2) {
subStmt.setLong(3, this.id);
subStmt.setLong(4, subObj.getId());
}
DBConnection.exec(subStmt);
}
}
// Save links in link table
String sql;
if (!subBeanField.isStandaloneLinkTable()) // Sub Bean and link table is the same table
sql = "UPDATE "+ subBeanField.getLinkTableName() +" SET "+ subBeanField.getParentIdColumnName() +"=? WHERE "+ subIdCol +"=?";
else
sql = "INSERT INTO " + subBeanField.getLinkTableName() + " (" + subBeanField.getParentIdColumnName() + ", " + subIdCol + ") SELECT ?,? " +
"WHERE NOT EXISTS(SELECT 1 FROM " + subBeanField.getLinkTableName() + " WHERE " + subBeanField.getParentIdColumnName() + "=? AND " + subIdCol + "=?);";
logger.finest("Save sub Bean(" + c.getName() + ", id: " + subObj.getId() + ") query: " + sql);
PreparedStatement subStmt = db.getPreparedStatement(sql);
subStmt.setLong(1, this.id);
subStmt.setLong(2, subObj.getId());
if (subStmt.getParameterMetaData().getParameterCount() > 2) {
subStmt.setLong(3, this.id);
subStmt.setLong(4, subObj.getId());
}
DBConnection.exec(subStmt);
}
}
}
}
} catch (SQLException e) {
throw e;
@ -280,25 +270,53 @@ public abstract class DBBean {
}
}
/**
* Deletes the bean from the DB and all its sub beans and links.
*/
public void delete(DBConnection db) throws SQLException{
delete(db, true);
}
/**
* Deletes the object from the DB, WARNING will not delete sub beans
*
* @throws SQLException
* Deletes the bean from the DB and the links to sub beans.
*
* @param recursive if all sub beans should be deleted also
*/
public void delete(DBConnection db) throws SQLException{
public void delete(DBConnection db, boolean recursive) throws SQLException{
Class<? extends DBBean> c = this.getClass();
DBBeanConfig config = DBBeanConfig.getBeanConfig( c );
if( this.getId() == null )
throw new NullPointerException("ID field is null! (Has the bean been saved?)");
String sql = "DELETE FROM "+config.tableName+" WHERE "+config.idColumn+"=?";
// Delete sub beans
for (DBBeanSubBeanConfig subBeanField : config.getSubBeans()) {
List<DBBean> list = (List<DBBean>) subBeanField.getValue(this);
if (list != null) {
for (DBBean subObj : list) {
// Delete links
if (subBeanField.isStandaloneLinkTable()) {
String sql = "DELETE FROM "+subBeanField.getLinkTableName()+" WHERE "+subBeanField.getParentIdColumnName()+"=?";
logger.finest("Delete link, query: "+sql);
PreparedStatement stmt = db.getPreparedStatement( sql );
stmt.setLong(1, this.getId() );
DBConnection.exec(stmt);
}
// Delete sub beans
if (recursive)
subObj.delete(db);
}
}
}
// Delete this bean from DB
String sql = "DELETE FROM "+config.getTableName()+" WHERE "+config.getIdColumnName()+"=?";
logger.finest("Delete Bean("+c.getName()+", id: "+this.getId()+") query: "+sql);
PreparedStatement stmt = db.getPreparedStatement( sql );
// Put in the variables in the SQL
stmt.setObject(1, this.getId() );
// Execute the SQL
stmt.setLong(1, this.getId() );
DBConnection.exec(stmt);
// Clear cache and reset id
DBBeanCache.remove(this);
this.id = null;
}
@ -313,7 +331,7 @@ public abstract class DBBean {
// Initiate a BeanConfig if there is non
DBBeanConfig config = DBBeanConfig.getBeanConfig( c );
// Generate query
String sql = "SELECT * FROM "+config.tableName;
String sql = "SELECT * FROM "+config.getTableName();
logger.finest("Load all Beans("+c.getName()+") query: "+sql);
PreparedStatement stmt = db.getPreparedStatement( sql );
// Run query
@ -333,7 +351,7 @@ public abstract class DBBean {
// Initiate a BeanConfig if there is non
DBBeanConfig config = DBBeanConfig.getBeanConfig( c );
// Generate query
String sql = "SELECT * FROM "+config.tableName+" WHERE "+config.idColumn+"=? LIMIT 1";
String sql = "SELECT * FROM "+config.getTableName()+" WHERE "+config.getIdColumnName()+"=? LIMIT 1";
logger.finest("Load Bean("+c.getName()+", id: "+id+") query: "+sql);
PreparedStatement stmt = db.getPreparedStatement( sql );
stmt.setObject(1, id );
@ -351,20 +369,20 @@ public abstract class DBBean {
// Generate the SQL
StringBuilder query = new StringBuilder();
query.append("CREATE TABLE "+config.tableName+" ( ");
query.append("CREATE TABLE "+config.getTableName()+" ( ");
// ID
query.append(" ").append(config.idColumn).append(" ");
query.append( classToColType( Long.class ) );
query.append(" ").append(config.getIdColumnName()).append(" ");
query.append( classToDBType( Long.class ) );
query.append(" PRIMARY KEY AUTO_INCREMENT, ");
for( Field field : config.fields ){
for( DBBeanFieldConfig field : config.getFields() ){
query.append(" ");
query.append( DBBeanConfig.getFieldName(field) );
query.append( classToColType(c) );
query.append(field.getName());
query.append(classToDBType(c));
query.append(", ");
}
query.delete( query.length()-2, query.length());
query.delete(query.length()-2, query.length());
query.append(")");
logger.finest("Create Bean("+c.getName()+") query: "+sql.toString());
@ -374,7 +392,7 @@ public abstract class DBBean {
DBConnection.exec(stmt);
}
private static String classToColType(Class<?> c){
private static String classToDBType(Class<?> c){
if( c == String.class) return "CLOB"; // TEXT
else if(c == Short.class) return "SMALLINT";
else if(c == short.class) return "SMALLINT";
@ -394,64 +412,12 @@ public abstract class DBBean {
else if(c == byte.class) return "BINARY(1)";
else if(c == Timestamp.class) return "DATETIME";
else if(DBBean.class.isAssignableFrom(c))
return classToColType(Long.class);
return classToDBType(Long.class);
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 {
if( !Modifier.isPublic( field.getModifiers()))
field.setAccessible(true);
return field.get(this);
} catch (Exception e) {
logger.log(Level.WARNING, e.getMessage(), e);
}
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 {
if( !Modifier.isPublic( field.getModifiers()))
field.setAccessible(true);
// Set basic data type
if( o == null && !Object.class.isAssignableFrom( field.getType() ) ){
if( field.getType() == Integer.TYPE ) field.setInt(this, 0);
else if( field.getType() == Character.TYPE )field.setChar(this, (char) 0);
else if( field.getType() == Byte.TYPE ) field.setByte(this, (byte) 0);
else if( field.getType() == Short.TYPE ) field.setShort(this, (short) 0);
else if( field.getType() == Long.TYPE ) field.setLong(this, 0l);
else if( field.getType() == Float.TYPE ) field.setFloat(this, 0f);
else if( field.getType() == Double.TYPE ) field.setDouble(this, 0d);
else if( field.getType() == Boolean.TYPE ) field.setBoolean(this, false);
}
else {
// Some special cases
if(field.getType() == Boolean.TYPE && o instanceof Integer)
field.setBoolean(this, ((Integer)o) > 0 ); // Convert an Integer to boolean
else
field.set(this, o);
}
} catch (Exception e) {
logger.log(Level.SEVERE, e.getMessage(), e);
}
}
/**
* @return the object id or null if the bean has not bean saved yet
* @return the bean id or null if the bean has not bean saved yet
*/
public final Long getId(){
return id;

View file

@ -30,7 +30,7 @@ class DBBeanCache {
}
/**
* A cache container that contains a object and last read time
* A cache container that contains a bean and its last filed update time
*/
private static class CacheItem{
public long updateTimestamp;
@ -113,41 +113,16 @@ class DBBeanCache {
}
/**
* Will check the cache if the given object exists
* Will check the cache if the given bean exists
*
* @param c is the class of the bean
* @param id is the id of the bean
* @return a cached DBBean object, or null if there is no cached object or if the cache is to old
* @return a cached DBBean object, null if there is a cache miss
*/
public static DBBean get(Class<?> c, Long id){
try{
return get( c, id, null );
}catch(SQLException e){
throw new RuntimeException("This exception should not be thrown, Something went really wrong!", e);
}
}
/**
* Will check the cache if the given object exists and will update it if its old
*
* @param c is the class of the bean
* @param id is the id of the bean
* @param result is the ResultSet for this object, the object will be updated from this ResultSet if the object is to old, there will be no update if this parameter is null
* @return a cached DBBean object, might update the cached object if its old but only if the ResultSet parameter is set
*/
public static DBBean get(Class<?> c, Long id, ResultSet result) throws SQLException{
public static DBBean get(Class<?> c, Long id) {
if(contains(c, id)){
CacheItem cacheItem = cache.get(c).get(id);
DBBean bean = cacheItem.bean.get();
// The cache is old, update and return it
if (cacheItem.updateTimestamp + CACHE_DATA_TTL < System.currentTimeMillis()) {
// There is no ResultSet to update from
if (result == null)
return null;
// Only update object if there is no update running now
logger.finer("Bean(" + c.getName() + ") cache to old for id: " + id);
// TODO:updateBean(result, bean);
}
return bean;
}
logger.finer("Bean("+c.getName()+") cache miss for id: "+id);
@ -155,14 +130,25 @@ class DBBeanCache {
}
/**
* Will check if the object with the id already exists in the cahce,
* if not then it will add the given object to the cache.
*
* @param obj is the object to cache
* @return true if the bean data is outdated, false if the data is current or if the bean was not found in the cache
*/
public static boolean isOutDated(DBBean obj){
if(contains(obj)) {
CacheItem cacheItem = cache.get(obj.getClass()).get(obj.getId());
return cacheItem.updateTimestamp + CACHE_DATA_TTL < System.currentTimeMillis();
}
return false;
}
/**
* Will add a bean to the cache. If the bean already is in
* the cache then its TTL timer will be reset
*/
public synchronized static void add(DBBean obj) {
if (contains(obj))
if (contains(obj)) {
cache.get(obj.getClass()).get(obj.getId()).updateTimestamp = System.currentTimeMillis();
return;
}
CacheItem cacheItem = new CacheItem();
cacheItem.updateTimestamp = System.currentTimeMillis();
cacheItem.bean = new WeakReference<>(obj);
@ -174,4 +160,10 @@ class DBBeanCache {
cache.put(obj.getClass(), map);
}
}
public static void remove(DBBean obj){
if (obj != null)
if( cache.containsKey(obj.getClass()) )
cache.get(obj.getClass()).remove(obj.getId());
}
}

View file

@ -24,6 +24,8 @@
package zutil.db.bean;
import zutil.ClassUtil;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
@ -39,13 +41,13 @@ class DBBeanConfig{
/** The name of the table in the DB **/
public String tableName;
private String tableName;
/** The name of the id column **/
public String idColumn;
private String idColumnName;
/** All normal fields in the bean **/
public ArrayList<Field> fields = new ArrayList<>();
private ArrayList<DBBeanFieldConfig> fields = new ArrayList<>();
/** All sub bean fields in the bean **/
public ArrayList<Field> subBeanFields = new ArrayList<>();
private ArrayList<DBBeanSubBeanConfig> subBeanFields = new ArrayList<>();
private DBBeanConfig(){ }
@ -70,12 +72,12 @@ class DBBeanConfig{
DBBean.DBTable tableAnn = c.getAnnotation(DBBean.DBTable.class);
if( tableAnn != null ){
config.tableName = tableAnn.value();
config.idColumn = tableAnn.idColumn();
}
else{
config.idColumnName = tableAnn.idColumn();
} else {
config.tableName = c.getSimpleName();
config.idColumn = "id";
config.idColumnName = "id";
}
// Add the fields in the bean and all the super classes fields
for(Class<?> cc = c; cc != DBBean.class ;cc = cc.getSuperclass()){
Field[] fields = cc.getDeclaredFields();
@ -87,9 +89,9 @@ class DBBeanConfig{
!config.fields.contains( field )){
if (List.class.isAssignableFrom(field.getType()) &&
field.getAnnotation(DBBean.DBLinkTable.class) != null)
config.subBeanFields.add( field );
config.subBeanFields.add(new DBBeanSubBeanConfig(field));
else
config.fields.add( field );
config.fields.add(new DBBeanFieldConfig(field));
}
}
if( tableAnn == null || !tableAnn.superBean() )
@ -99,11 +101,122 @@ class DBBeanConfig{
beanConfigs.put(c.getName(), config);
}
public static String getFieldName(Field field){
DBBean.DBColumn colAnnotation = field.getAnnotation(DBBean.DBColumn.class);
if(colAnnotation != null)
return colAnnotation.value();
return field.getName();
public String getTableName(){
return tableName;
}
public String getIdColumnName(){
return idColumnName;
}
public List<DBBeanFieldConfig> getFields(){
return fields;
}
public List<DBBeanSubBeanConfig> getSubBeans(){
return subBeanFields;
}
public static class DBBeanFieldConfig {
private Field field;
private String fieldName;
private DBBeanFieldConfig(Field field){
this.field = field;
if( !Modifier.isPublic( field.getModifiers()))
field.setAccessible(true);
DBBean.DBColumn colAnnotation = field.getAnnotation(DBBean.DBColumn.class);
if(colAnnotation != null)
fieldName = colAnnotation.value();
else
fieldName = field.getName();
}
public String getName(){
return fieldName;
}
public Class<?> getType(){
return field.getType();
}
public Object getValue(Object obj) {
try {
return field.get(obj);
} catch (Exception e){
e.printStackTrace();
}
return null;
}
public void setValue(Object obj, Object fieldValue) {
try {
if (!Modifier.isPublic(field.getModifiers()))
field.setAccessible(true);
// Set basic data type
if (fieldValue == null && ClassUtil.isPrimitive(field.getType())) {
if (field.getType() == Integer.TYPE) field.setInt(obj, 0);
else if (field.getType() == Character.TYPE) field.setChar(obj, (char) 0);
else if (field.getType() == Byte.TYPE) field.setByte(obj, (byte) 0);
else if (field.getType() == Short.TYPE) field.setShort(obj, (short) 0);
else if (field.getType() == Long.TYPE) field.setLong(obj, 0l);
else if (field.getType() == Float.TYPE) field.setFloat(obj, 0f);
else if (field.getType() == Double.TYPE) field.setDouble(obj, 0d);
else if (field.getType() == Boolean.TYPE) field.setBoolean(obj, false);
} else {
// Some special cases
if (field.getType() == Boolean.TYPE && fieldValue instanceof Integer)
field.setBoolean(obj, ((Integer) fieldValue) > 0); // Convert an Integer to boolean
else
field.set(obj, fieldValue);
}
} catch (Exception e){
e.printStackTrace();
}
}
}
public static class DBBeanSubBeanConfig extends DBBeanFieldConfig{
private String linkTableName;
private Class<? extends DBBean> subBeanClass;
private DBBeanConfig subBeanConfig;
private String parentIdCol;
private DBBeanSubBeanConfig(Field field){
super(field);
DBBean.DBLinkTable linkAnnotation = field.getAnnotation(DBBean.DBLinkTable.class);
this.linkTableName = linkAnnotation.table();
this.subBeanClass = linkAnnotation.beanClass();
this.subBeanConfig = DBBeanConfig.getBeanConfig(subBeanClass);
this.parentIdCol = linkAnnotation.idColumn();
}
public String getLinkTableName() {
return linkTableName;
}
public boolean isStandaloneLinkTable(){
return !linkTableName.equals(subBeanConfig.tableName);
}
public Class<? extends DBBean> getSubBeanClass() {
return subBeanClass;
}
public DBBeanConfig getSubBeanConfig() {
return subBeanConfig;
}
public String getParentIdColumnName() {
return parentIdCol;
}
}
}

View file

@ -26,10 +26,10 @@ package zutil.db.bean;
import zutil.db.DBConnection;
import zutil.db.SQLResultHandler;
import zutil.db.bean.DBBean.DBLinkTable;
import zutil.db.bean.DBBeanConfig.DBBeanFieldConfig;
import zutil.db.bean.DBBeanConfig.DBBeanSubBeanConfig;
import zutil.log.LogUtil;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -55,7 +55,7 @@ public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{
* @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);
return new DBBeanSQLResultHandler<>(cl, null, false);
}
/**
@ -115,7 +115,7 @@ public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{
@SuppressWarnings("unchecked")
public T handleQueryResult(Statement stmt, ResultSet result) throws SQLException{
if( list ){
LinkedList<DBBean> bean_list = new LinkedList<DBBean>();
List<DBBean> bean_list = new LinkedList<>();
while( result.next() ){
DBBean obj = createBean(result);
bean_list.add( obj );
@ -138,21 +138,23 @@ public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{
* @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
*/
protected DBBean createBean(ResultSet result) throws SQLException{
private DBBean createBean(ResultSet result) throws SQLException{
try {
Long id = result.getLong( "id" );
// Check cache first
DBBean obj = DBBeanCache.get(beanClass, id, result);
DBBean obj = DBBeanCache.get(beanClass, id);
if ( obj == null ) {
// Cache miss create a new object
// Cache miss create a new bean
logger.fine("Creating new Bean(" + beanClass.getName() + ") with id: " + id);
obj = beanClass.newInstance();
obj.setId(id);
DBBeanCache.add(obj);
updateBean( result, obj );
}
else if (DBBeanCache.isOutDated(obj)){
// Update fields
logger.finer("Bean(" + beanClass.getName() + ") cache to old for id: " + id);
updateBean(result, obj);
}
// Update fields
updateBean( result, obj );
return obj;
} catch (SQLException e) {
throw e;
@ -164,17 +166,17 @@ public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{
/**
* Updates an existing 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
* @param obj is the object that will be updated
* @param result is where the field values for the bean will be read from, the cursor should be in front of the data
* @param obj is the bean that will be updated
*/
@SuppressWarnings("unchecked")
protected void updateBean(ResultSet result, DBBean obj) throws SQLException{
private void updateBean(ResultSet result, DBBean obj) throws SQLException{
if (obj.readLock.tryLock()) {
try {
logger.fine("Updating Bean("+ beanClass.getName() +") with id: "+ obj.getId());
// Read fields
for (Field field : beanConfig.fields) {
String name = DBBeanConfig.getFieldName(field);
for (DBBeanFieldConfig field : beanConfig.getFields()) {
String name = field.getName();
// Inline DBBean class
if (DBBean.class.isAssignableFrom(field.getType())) {
@ -183,32 +185,35 @@ public class DBBeanSQLResultHandler<T> implements SQLResultHandler<T>{
DBBean subObj = DBBeanCache.get(field.getType(), subId);
if (subObj == null)
subObj = DBBean.load(db, (Class<? extends DBBean>) field.getType(), subId);
obj.setFieldValue(field, subObj);
field.setValue(obj, subObj);
} else
logger.warning("No DB available to read sub beans");
}
// Normal field
else {
obj.setFieldValue(field, result.getObject(name));
field.setValue(obj, result.getObject(name));
}
}
// Update cache
DBBeanCache.add(obj);
// Read sub beans
if (db != null) {
for (Field field : beanConfig.subBeanFields) {
DBLinkTable linkTable = field.getAnnotation(DBLinkTable.class);
DBBeanConfig subConfig = DBBeanConfig.getBeanConfig(linkTable.beanClass());
String linkTableName = linkTable.table();
String subTable = subConfig.tableName;
String idCol = (linkTable.idColumn().isEmpty() ? beanConfig.tableName : linkTable.idColumn());
for (DBBeanSubBeanConfig subBeanField : beanConfig.getSubBeans()) {
DBBeanConfig subBeanConfig = subBeanField.getSubBeanConfig();
// Load list from link table
String subSql = "SELECT subObjTable.* FROM "+ linkTableName +" as linkTable, "+ subTable +" as subObjTable WHERE linkTable."+idCol+"=? AND linkTable."+subConfig.idColumn+"=subObjTable."+subConfig.idColumn;
// Load List from link table
String subSql = "SELECT subBeanTable.* FROM "+
subBeanField.getLinkTableName() +" as linkTable, "+
subBeanConfig.getTableName() +" as subBeanTable " +
"WHERE linkTable."+subBeanField.getParentIdColumnName()+"=? AND " +
"linkTable."+subBeanConfig.getIdColumnName()+"=subBeanTable."+subBeanConfig.getIdColumnName();
logger.finest("List Load Query: " + subSql);
PreparedStatement subStmt = db.getPreparedStatement(subSql);
subStmt.setObject(1, obj.getId());
List<? extends DBBean> list = DBConnection.exec(subStmt,
DBBeanSQLResultHandler.createList(linkTable.beanClass(), db));
obj.setFieldValue(field, list);
DBBeanSQLResultHandler.createList(subBeanField.getSubBeanClass(), db));
subBeanField.setValue(obj, list);
}
} else
logger.warning("No DB available to read sub beans");

View file

@ -0,0 +1,117 @@
package zutil.db.bean;
import org.junit.BeforeClass;
import org.junit.Test;
import zutil.db.DBConnection;
import zutil.log.CompactLogFormatter;
import zutil.log.LogUtil;
import java.sql.SQLException;
import java.util.logging.Level;
import static junit.framework.TestCase.assertNull;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static zutil.db.bean.DBBeanTestBase.*;
/**
*
*/
public class DBBeanDeleteTest {
private DBConnection db = new DBConnection(DBConnection.DBMS.SQLite, ":memory:");
public DBBeanDeleteTest() throws Exception {}
@BeforeClass
public static void init(){
LogUtil.setGlobalFormatter(new CompactLogFormatter());
LogUtil.setGlobalLevel(Level.ALL);
}
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
@Test
public void simpleClassDelete() throws SQLException {
SimpleTestClass obj = simpleClassInit(db);
obj.save(db);
obj.delete(db);
assertEquals(0, DBBeanTestBase.getRowCount(db, "SimpleTestClass"));
assertNull(obj.getId());
assertNull(DBBeanCache.get(SimpleTestClass.class, 1L));
}
@Test(expected=NullPointerException.class)
public void notSavedSimpleClassDelete() throws SQLException {
simpleClassInit(db).save(db);
SimpleTestClass obj = new SimpleTestClass();
obj.delete(db); // Exception
}
@Test
public void multiSimpleClassDelete() throws SQLException {
SimpleTestClass obj = simpleClassInit(db);
obj.save(db);
for(int i=0; i<5; ++i)
new SimpleTestClass().save(db);
obj.delete(db);
assertEquals(5, DBBeanTestBase.getRowCount(db, "SimpleTestClass"));
assertNull(obj.getId());
}
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
@Test
public void emptySubObjectDelete() throws SQLException {
subObjectInit(db).save(db);
ParentTestClass obj = new ParentTestClass();
obj.save(db);
obj.delete(db);
assertEquals(1, DBBeanTestBase.getRowCount(db, "parent"));
assertEquals(1, DBBeanTestBase.getRowCount(db, "subobject"));
}
@Test
public void subObjectDelete() throws SQLException {
ParentTestClass obj = subObjectInit(db);
obj.save(db);
obj.delete(db);
assertEquals("Parent table size", 0, DBBeanTestBase.getRowCount(db, "parent"));
assertEquals("SubObject table size", 0, DBBeanTestBase.getRowCount(db, "subobject"));
assertNull(DBBeanCache.get(ParentTestClass.class, 1L));
assertNull(DBBeanCache.get(SubObjectTestClass.class, 1L));
}
////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
@Test
public void emptySubLinkObjectDelete() throws SQLException {
subLinkObjectInit(db).save(db);
ParentLinkTestClass obj = new ParentLinkTestClass();
obj.save(db);
obj.delete(db);
assertEquals("Parent table size", 1, DBBeanTestBase.getRowCount(db, "parent"));
assertEquals("Link table size", 1, DBBeanTestBase.getRowCount(db, "link"));
assertEquals("SubObject table size", 1, DBBeanTestBase.getRowCount(db, "subobject"));
}
@Test
public void subLinkObjectDelete() throws SQLException {
ParentLinkTestClass obj = subLinkObjectInit(db);
obj.save(db);
obj.delete(db);
assertEquals("Parent table size", 0, DBBeanTestBase.getRowCount(db, "parent"));
assertEquals("Link table size", 0, DBBeanTestBase.getRowCount(db, "link"));
assertEquals("SubObject table size", 0, DBBeanTestBase.getRowCount(db, "subobject"));
assertNull(DBBeanCache.get(ParentTestClass.class, 1L));
assertNull(DBBeanCache.get(SubObjectTestClass.class, 1L));
}
}

View file

@ -33,10 +33,10 @@ public class DBBeanLoadTest {
@Test
public void simpleClassLoad() throws SQLException {
simpleClassInit(db);
insert(db, "SimpleTestClass", "5", "1234", "\"helloworld\"");
SimpleTestClass obj = DBBean.load(db, SimpleTestClass.class, 5);
insert(db, "SimpleTestClass", "10", "1234", "\"helloworld\"");
SimpleTestClass obj = DBBean.load(db, SimpleTestClass.class, 10);
assertEquals((Long)5L, obj.getId());
assertEquals((Long)10L, obj.getId());
assertEquals(1234, obj.intField);
assertEquals("helloworld", obj.strField);
}
@ -45,9 +45,9 @@ public class DBBeanLoadTest {
@Test
public void simpleClassCache() throws SQLException {
simpleClassInit(db);
insert(db, "SimpleTestClass", "5", "1234", "\"helloworld\"");
SimpleTestClass obj1 = DBBean.load(db, SimpleTestClass.class, 5);
SimpleTestClass obj2 = DBBean.load(db, SimpleTestClass.class, 5);
insert(db, "SimpleTestClass", "11", "1234", "\"helloworld\"");
SimpleTestClass obj1 = DBBean.load(db, SimpleTestClass.class, 11);
SimpleTestClass obj2 = DBBean.load(db, SimpleTestClass.class, 11);
assertSame(obj1, obj2);
}
@ -58,10 +58,10 @@ public class DBBeanLoadTest {
@Test
public void aliasFieldsLoad() throws SQLException {
aliasFieldsInit(db);
insert(db, "aliasTable", "5", "1234", "\"helloworld\"");
AliasFieldsTestClass obj = DBBean.load(db, AliasFieldsTestClass.class, 5);
insert(db, "aliasTable", "20", "1234", "\"helloworld\"");
AliasFieldsTestClass obj = DBBean.load(db, AliasFieldsTestClass.class, 20);
assertEquals((Long)5L, obj.getId());
assertEquals((Long)20L, obj.getId());
assertEquals(1234, obj.intField);
assertEquals("helloworld", obj.strField);
}
@ -72,43 +72,43 @@ public class DBBeanLoadTest {
@Test
public void emptySubObjectLoad() throws SQLException {
subObjectInit(db);
insert(db, "parent", "5");
ParentTestClass obj = DBBean.load(db, ParentTestClass.class, 5);
insert(db, "parent", "30");
ParentTestClass obj = DBBean.load(db, ParentTestClass.class, 30);
assertEquals((Long)5L, obj.getId());
assertEquals((Long)30L, obj.getId());
assertEquals(0, obj.subobjs.size());
}
@Test
public void subObjectLoad() throws SQLException {
subObjectInit(db);
insert(db, "parent", "5");
insert(db, "subobject", "10", "5", "1234");
ParentTestClass obj = DBBean.load(db, ParentTestClass.class, 5);
insert(db, "parent", "31");
insert(db, "subobject", "310", "31", "1234");
ParentTestClass obj = DBBean.load(db, ParentTestClass.class, 31);
assertEquals(1, obj.subobjs.size());
assertEquals((Long)10L, obj.subobjs.get(0).getId());
assertEquals((Long)310L, obj.subobjs.get(0).getId());
assertEquals(1234, obj.subobjs.get(0).intField);
}
@Test
public void multiSubObjectLoad() throws SQLException {
subObjectInit(db);
insert(db, "parent", "5");
insert(db, "subobject", "10", "5", "1001");
insert(db, "subobject", "11", "5", "1002");
insert(db, "subobject", "12", "5", "1003");
insert(db, "subobject", "13", "5", "1004");
ParentTestClass obj = DBBean.load(db, ParentTestClass.class, 5);
insert(db, "parent", "32");
insert(db, "subobject", "320", "32", "1001");
insert(db, "subobject", "321", "32", "1002");
insert(db, "subobject", "322", "32", "1003");
insert(db, "subobject", "323", "32", "1004");
ParentTestClass obj = DBBean.load(db, ParentTestClass.class, 32);
assertEquals(4, obj.subobjs.size());
assertEquals((Long)10L, obj.subobjs.get(0).getId());
assertEquals((Long)320L, obj.subobjs.get(0).getId());
assertEquals(1001, obj.subobjs.get(0).intField);
assertEquals((Long)11L, obj.subobjs.get(1).getId());
assertEquals((Long)321L, obj.subobjs.get(1).getId());
assertEquals(1002, obj.subobjs.get(1).intField);
assertEquals((Long)12L, obj.subobjs.get(2).getId());
assertEquals((Long)322L, obj.subobjs.get(2).getId());
assertEquals(1003, obj.subobjs.get(2).intField);
assertEquals((Long)13L, obj.subobjs.get(3).getId());
assertEquals((Long)323L, obj.subobjs.get(3).getId());
assertEquals(1004, obj.subobjs.get(3).intField);
}
@ -118,44 +118,44 @@ public class DBBeanLoadTest {
@Test
public void emptySubLinkObjectLoad() throws SQLException {
subLinkObjectInit(db);
insert(db, "parent", "5");
ParentLinkTestClass obj = DBBean.load(db, ParentLinkTestClass.class, 5);
insert(db, "parent", "40");
ParentLinkTestClass obj = DBBean.load(db, ParentLinkTestClass.class, 40);
assertEquals((Long)5L, obj.getId());
assertEquals((Long)40L, obj.getId());
assertEquals(0, obj.subobjs.size());
}
@Test
public void subLinkObjectLoad() throws SQLException {
subLinkObjectInit(db);
insert(db, "parent", "5");
insert(db, "link", "5", "10");
insert(db, "subobject", "10", "1234");
ParentLinkTestClass obj = DBBean.load(db, ParentLinkTestClass.class, 5);
insert(db, "parent", "41");
insert(db, "link", "41", "410");
insert(db, "subobject", "410", "1234");
ParentLinkTestClass obj = DBBean.load(db, ParentLinkTestClass.class, 41);
assertEquals(1, obj.subobjs.size());
assertEquals((Long)10L, obj.subobjs.get(0).getId());
assertEquals((Long)410L, obj.subobjs.get(0).getId());
assertEquals(1234, obj.subobjs.get(0).intField);
}
@Test
public void multiSubLinkObjectLoad() throws SQLException {
subLinkObjectInit(db);
insert(db, "parent", "5");
insert(db, "link", "5", "10");
insert(db, "link", "5", "11");
insert(db, "link", "5", "12");
insert(db, "subobject", "10", "1001");
insert(db, "subobject", "11", "1002");
insert(db, "subobject", "12", "1003");
ParentLinkTestClass obj = DBBean.load(db, ParentLinkTestClass.class, 5);
insert(db, "parent", "42");
insert(db, "link", "42", "420");
insert(db, "link", "42", "421");
insert(db, "link", "42", "422");
insert(db, "subobject", "420", "1001");
insert(db, "subobject", "421", "1002");
insert(db, "subobject", "422", "1003");
ParentLinkTestClass obj = DBBean.load(db, ParentLinkTestClass.class, 42);
assertEquals(3, obj.subobjs.size());
assertEquals((Long)10L, obj.subobjs.get(0).getId());
assertEquals((Long)420L, obj.subobjs.get(0).getId());
assertEquals(1001, obj.subobjs.get(0).intField);
assertEquals((Long)11L, obj.subobjs.get(1).getId());
assertEquals((Long)421L, obj.subobjs.get(1).getId());
assertEquals(1002, obj.subobjs.get(1).intField);
assertEquals((Long)12L, obj.subobjs.get(2).getId());
assertEquals((Long)422L, obj.subobjs.get(2).getId());
assertEquals(1003, obj.subobjs.get(2).intField);
}
}