Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.mentabean.jdbc.AnsiSQLBeanSession Maven / Gradle / Ivy
/*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* MentaBean => http://www.mentabean.org
* Author: Sergio Oliveira Jr. ([email protected] )
*/
package org.mentabean.jdbc;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mentabean.BeanConfig;
import org.mentabean.BeanException;
import org.mentabean.BeanManager;
import org.mentabean.BeanSession;
import org.mentabean.DBField;
import org.mentabean.DBType;
import org.mentabean.type.AutoIncrementType;
import org.mentabean.type.AutoTimestampType;
import org.mentabean.type.NowOnInsertAndUpdateTimestampType;
import org.mentabean.type.NowOnInsertTimestampType;
import org.mentabean.type.NowOnUpdateTimestampType;
import org.mentabean.type.SizedType;
import org.mentabean.util.InjectionUtils;
/**
* The bean session implementation based on JDBC and SQL.
*
* @author soliveira
*/
public class AnsiSQLBeanSession implements BeanSession {
protected static boolean DEBUG = false;
/* The loaded map will be cleared when the session dies */
protected IdentityHashMap> loaded = new IdentityHashMap>();
protected Connection conn;
protected final BeanManager beanManager;
/**
* Creates a JdbcBeanSession with a BeanManager and a Connection.
*
* @param beanManager
* The bean manager
* @param conn
* The database connection
*/
public AnsiSQLBeanSession(final BeanManager beanManager, final Connection conn) {
this.beanManager = beanManager;
this.conn = conn;
}
/**
* Turn SQL debugging on and off.
*
* @param b
* true if it should be debugged
*/
public static void debugSql(boolean b) {
AnsiSQLBeanSession.DEBUG = b;
}
/**
* Get the connection associated with this JdbcBeanSession.
*
* @return the database connection
*/
@Override
public Connection getConnection() {
return conn;
}
/**
* Get the command representing 'now' in this database. This base implementation returns null, in other words, no now command will be used.
*
* @return the command for now in this database (now(), sysdate, etc)
*/
protected String getCurrentTimestampCommand() {
return null;
}
/**
* Get a value from a bean through reflection.
*
* @param bean
* @param fieldName
* @return The value of a bean property
*/
protected static Object getValueFromBean(final Object bean, final String fieldName) {
return getValueFromBean(bean, fieldName, null);
}
/**
* Get a value from a bean through reflection.
*
* @param bean
* @param fieldName
* @param m
* @return The value of a bean property
*/
protected static Object getValueFromBean(final Object bean, final String fieldName, Method m) {
if (m == null) {
m = InjectionUtils.findMethodToGet(bean.getClass(), fieldName);
}
if (m == null) {
throw new BeanException("Cannot find method to get field from bean: " + fieldName);
}
Object value = null;
try {
value = m.invoke(bean, (Object[]) null);
return value;
} catch (Exception e) {
throw new BeanException(e);
}
}
private static void checkPK(final Object value, final DBField dbField) {
if (value == null) {
throw new BeanException("pk is missing: " + dbField);
} else if (value instanceof Number) {
final Number n = (Number) value;
if (n.doubleValue() <= 0) {
throw new BeanException("Number pk is missing: " + dbField);
}
}
}
@Override
public boolean load(final Object bean) {
final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());
if (bc == null) {
throw new BeanException("Cannot find bean config: " + bean.getClass());
}
if (bc.getNumberOfFields() == 0) {
throw new BeanException("BeanConfig has zero fields: " + bc);
}
final StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());
sb.append("SELECT ");
Iterator iter = bc.fields();
int count = 0;
while (iter.hasNext()) {
final String fieldName = iter.next().getDbName();
if (count++ > 0) {
sb.append(',');
}
sb.append(fieldName);
}
sb.append(" FROM ").append(bc.getTableName()).append(" WHERE ");
if (!bc.hasPK()) {
throw new BeanException("Cannot load bean without a PK!");
}
iter = bc.pks();
count = 0;
final List values = new LinkedList();
while (iter.hasNext()) {
final DBField dbField = iter.next();
final String fieldName = dbField.getName();
final String dbFieldName = dbField.getDbName();
final Object value = getValueFromBean(bean, fieldName);
checkPK(value, dbField);
if (count++ > 0) {
sb.append(" AND ");
}
sb.append(dbFieldName).append("=?");
values.add(new Value(dbField, value));
}
if (values.isEmpty()) {
throw new BeanException("Bean is empty: " + bean + " / " + bc);
}
if (conn == null) {
throw new BeanException("Connection is null!");
}
PreparedStatement stmt = null;
ResultSet rset = null;
try {
if (DEBUG) {
System.out.println("LOAD SQL: " + sb.toString());
}
stmt = conn.prepareStatement(sb.toString());
final Iterator iter2 = values.iterator();
int index = 0;
while (iter2.hasNext()) {
final Value v = iter2.next();
v.field.getType().bindToStmt(stmt, ++index, v.value);
}
rset = stmt.executeQuery();
index = 0;
final Map fieldsLoaded = new HashMap();
if (rset.next()) {
iter = bc.fields();
while (iter.hasNext()) {
final DBField f = iter.next();
final String fieldName = f.getName();
final DBType type = f.getType();
final Object value = type.getFromResultSet(rset, ++index);
injectValue(bean, fieldName, value, type.getTypeClass());
fieldsLoaded.put(fieldName, new Value(f, value));
}
} else {
return false;
}
if (rset.next()) {
throw new BeanException("Load returned more than one row!");
}
loaded.put(bean, fieldsLoaded);
return true;
} catch (Exception e) {
throw new BeanException(e);
} finally {
close(stmt, rset);
}
}
/**
* Inject a value in a bean through reflection.
*
* @param bean
* @param fieldName
* @param value
* @param valueType
*/
protected static void injectValue(final Object bean, final String fieldName, final Object value, final Class extends Object> valueType) {
final Method m = InjectionUtils.findMethodToInject(bean.getClass(), fieldName, value == null ? valueType : value.getClass());
if (m == null) {
// try field...
final Field field = InjectionUtils.findFieldToInject(bean.getClass(), fieldName, value == null ? valueType : value.getClass());
if (field != null) {
try {
field.set(bean, value);
} catch (final Exception e) {
e.printStackTrace();
throw new BeanException(e);
}
} else {
// if Long and can be expressed as integer, try integer...
if (value instanceof Long) {
Long l = (Long) value;
if (l.longValue() <= Integer.MAX_VALUE && l.longValue() >= Integer.MIN_VALUE) {
injectValue(bean, fieldName, l.intValue(), Integer.class);
return;
}
}
throw new BeanException("Cannot find field or method to inject: " + bean + " / " + fieldName);
}
} else {
try {
m.invoke(bean, value);
} catch (final Exception e) {
e.printStackTrace();
throw new BeanException(e);
}
}
}
/**
* Some databases will sort before applying the limit (MySql), others will not (Oracle). Handle each one accordingly.
*
* Note: This base implementation does nothing.
*
* @param sb
* @param orderBy
* @param limit
* @return A string builder with the the SQL modified for the limit operation
*/
protected StringBuilder handleLimit(final StringBuilder sb, final String orderBy, final int limit) {
return sb;
}
/**
* Build the column/field list for a SQL SELECT statement based on the bean configuration. Very useful to create select statements.
*
* @param beanClass
* the bean class
* @return the column/field list for a select
*/
@Override
public String buildSelect(final Class extends Object> beanClass) {
return buildSelectImpl(beanClass, null, null, null);
}
@Override
public String buildSelect(final Class extends Object> beanClass, String[] properties) {
return buildSelectImpl(beanClass, null, properties, null);
}
/**
* Build a column/field list for a SQL SELECT statement based on the bean configuration. A table prefix will be used on each field. Very useful to create select statements on multiple tables (joins).
*
* @param beanClass
* the bean class
* @param tablePrefix
* the table prefix to use before each field
* @return the column/field list for a select
*/
@Override
public String buildSelect(final Class extends Object> beanClass, final String tablePrefix) {
return buildSelectImpl(beanClass, tablePrefix, null, null);
}
@Override
public String buildSelect(final Class extends Object> beanClass, final String tablePrefix, String[] properties) {
return buildSelectImpl(beanClass, tablePrefix, properties, null);
}
/**
* Like buildSelect but you can exclude some properties from the resulting list. Useful when you have a bean with too many properties and you just want to fetch a few.
*
* Note: The list of properties to exclude contains 'property names' and NOT database column names.
*
* @param beanClass
* the bean class
* @param minus
* a list for property names to exclude
* @return the column/field list for a select
*/
@Override
public String buildSelectMinus(final Class extends Object> beanClass, final String[] minus) {
return buildSelectImpl(beanClass, null, null, minus);
}
/**
* Same as buildSelectMinus with support for a database table prefix that will be applied on each field.
*
* @param beanClass
* the bean class
* @param tablePrefix
* the database table prefix
* @param minus
* a list of property names to exclude
* @return the column/field list for a select
*/
@Override
public String buildSelectMinus(final Class extends Object> beanClass, final String tablePrefix, final String[] minus) {
return buildSelectImpl(beanClass, tablePrefix, null, minus);
}
private String buildSelectImpl(final Class extends Object> beanClass, final String tablePrefix, final String[] properties, final String[] minus) {
final BeanConfig bc = beanManager.getBeanConfig(beanClass);
if (bc == null) {
return null;
}
final StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());
final Iterator iter = bc.fields();
int count = 0;
while (iter.hasNext()) {
final DBField field = iter.next();
final String dbField = field.getDbName();
if (minus != null && minus.length > 0) {
final String name = field.getName();
if (properties != null && !checkArray(name, properties)) {
continue;
}
if (minus != null && checkArray(name, minus)) {
continue;
}
}
if (count++ > 0) {
sb.append(",");
}
if (tablePrefix != null) {
sb.append(tablePrefix).append('.');
sb.append(dbField).append(' ');
sb.append(tablePrefix).append('_').append(dbField);
} else {
sb.append(dbField);
}
}
return sb.toString();
}
private static boolean checkArray(final String value, final String[] array) {
for (int i = 0; i < array.length; i++) {
if (array[i].equals(value)) {
return true;
}
}
return false;
}
/**
* Populate a bean (insert all its properties) from the results in a result set, based on the bean configuration.
*
* @param rset
* the result set from where to get the property values
* @param bean
* the bean to be populated
* @throws Exception
*/
@Override
public void populateBean(final ResultSet rset, final Object bean) {
populateBeanImpl(rset, bean, null, null, null);
}
@Override
public void populateBean(final ResultSet rset, final Object bean, String[] properties) {
populateBeanImpl(rset, bean, null, properties, null);
}
/**
* Same as populateBean, but use a table prefix before fetching the values from the result set. Useful when there are multiple tables involved and you want to avoid field name clashing.
*
* @param rset
* the result set
* @param bean
* the bean to be populated
* @param tablePrefix
* the table prefix
*/
@Override
public void populateBean(final ResultSet rset, final Object bean, final String tablePrefix) {
populateBeanImpl(rset, bean, tablePrefix, null, null);
}
@Override
public void populateBean(final ResultSet rset, final Object bean, final String tablePrefix, String[] properties) {
populateBeanImpl(rset, bean, tablePrefix, properties, null);
}
/**
* Same as populateBean, but exclude some fields when populating.
*
* @param rset
* @param bean
* @param minus
*/
@Override
public void populateBeanMinus(final ResultSet rset, final Object bean, final String[] minus) {
populateBeanImpl(rset, bean, null, null, minus);
}
/**
* Same as populateBean, but exclude some fields when populating and use a table prefix in front of the field names.
*
* @param rset
* @param bean
* @param tablePrefix
* @param minus
*/
@Override
public void populateBeanMinus(final ResultSet rset, final Object bean, final String tablePrefix, final String[] minus) {
populateBeanImpl(rset, bean, tablePrefix, null, minus);
}
private void populateBeanImpl(final ResultSet rset, final Object bean, final String tablePrefix, final String[] properties, final String[] minus) {
final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());
if (bc == null) {
throw new BeanException("Cannot find bean config: " + bean.getClass());
}
final Iterator iter = bc.fields();
final StringBuilder sbField = new StringBuilder(32);
while (iter.hasNext()) {
final DBField f = iter.next();
final String fieldName = f.getName();
if (minus != null && checkArray(fieldName, minus)) {
continue;
}
final String dbFieldName = f.getDbName();
final DBType type = f.getType();
sbField.setLength(0);
if (tablePrefix != null) {
sbField.append(tablePrefix).append('_').append(dbFieldName);
} else {
sbField.append(dbFieldName);
}
try {
final Object value = type.getFromResultSet(rset, sbField.toString());
injectValue(bean, fieldName, value, type.getTypeClass());
} catch (Exception e) {
throw new BeanException(e);
}
}
}
/**
* Load a list of beans, but exclude some fields.
*
* @param
* @param bean
* @param minus
* @param orderBy
* @param limit
* @return A list of beans
*/
@Override
public List loadListMinus(final E bean, final String orderBy, final int limit, final String[] minus) {
return loadListImpl(bean, orderBy, limit, null, minus);
}
private E checkUnique(final List list) {
if (list == null || list.size() == 0) {
return null;
} else if (list.size() > 1) {
throw new BeanException("Query returned more than one bean!");
} else {
return list.get(0);
}
}
@Override
public List loadList(final E bean, final String orderBy, final int limit) {
return loadListImpl(bean, orderBy, limit, null, null);
}
@Override
public List loadList(final E bean, final String orderBy, final int limit, String[] properties) {
return loadListImpl(bean, orderBy, limit, properties, null);
}
private StringBuilder prepareListQuery(StringBuilder sb, BeanConfig bc, E bean, String orderBy, int limit, List values) {
sb.append(" FROM ").append(bc.getTableName()).append(" ");
Iterator iter = bc.fields();
int count = 0;
while (iter.hasNext()) {
final DBField field = iter.next();
final String dbField = field.getDbName();
final Method m = InjectionUtils.findMethodToGet(bean.getClass(), field.getName());
if (m == null) {
throw new BeanException("Cannot find method to get field from bean: " + field.getName());
}
final Class extends Object> returnType = m.getReturnType();
final Object value = getValueFromBean(bean, field.getName(), m);
if (!isSet(value, returnType)) {
continue;
}
if (count++ > 0) {
sb.append(" AND ");
} else {
sb.append(" WHERE ");
}
sb.append(dbField).append("=?");
values.add(new Value(field, value));
}
if (orderBy != null) {
sb.append(" order by ").append(orderBy).append(" ");
}
sb = handleLimit(sb, orderBy, limit);
return sb;
}
@Override
public int countList(Object bean) {
return countListImpl(bean, null, null, -1);
}
private int countListImpl(final Object bean, final String[] minus, final String orderBy, final int limit) {
if (limit == 0) {
return 0;
}
final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());
if (bc == null) {
throw new BeanException("Cannot find bean config: " + bean.getClass());
}
StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());
sb.append("SELECT count(1)");
final List values = new LinkedList();
sb = prepareListQuery(sb, bc, bean, orderBy, limit, values);
PreparedStatement stmt = null;
ResultSet rset = null;
try {
final String sql = sb.toString();
if (DEBUG) {
System.out.println("COUNT LIST: " + sql);
}
stmt = conn.prepareStatement(sql);
final Iterator iter2 = values.iterator();
int index = 0;
while (iter2.hasNext()) {
final Value v = iter2.next();
v.field.getType().bindToStmt(stmt, ++index, v.value);
}
rset = stmt.executeQuery();
rset.next();
return rset.getInt(1);
} catch (Exception e) {
throw new BeanException(e);
} finally {
close(stmt, rset);
}
}
private List loadListImpl(final E bean, final String orderBy, final int limit, final String[] properties, final String[] minus) {
if (limit == 0) {
return new ArrayList();
}
final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());
if (bc == null) {
throw new BeanException("Cannot find bean config: " + bean.getClass());
}
StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());
Iterator iter = bc.fields();
sb.append("SELECT ");
int count = 0;
while (iter.hasNext()) {
final DBField field = iter.next();
final String dbField = field.getDbName();
final String name = field.getName();
if (properties != null && !checkArray(name, properties)) {
continue;
}
if (minus != null && checkArray(name, minus)) {
continue;
}
if (count++ > 0) {
sb.append(",");
}
sb.append(dbField);
}
final List values = new LinkedList();
sb = prepareListQuery(sb, bc, bean, orderBy, limit, values);
PreparedStatement stmt = null;
ResultSet rset = null;
try {
final String sql = sb.toString();
if (DEBUG) {
System.out.println("LOAD LIST: " + sql);
}
stmt = conn.prepareStatement(sql);
final Iterator iter2 = values.iterator();
int index = 0;
while (iter2.hasNext()) {
final Value v = iter2.next();
v.field.getType().bindToStmt(stmt, ++index, v.value);
}
rset = stmt.executeQuery();
final List results = new LinkedList();
final Class extends Object> beanKlass = bean.getClass();
int total = 0;
while (rset.next()) {
iter = bc.fields();
index = 0;
final E item = (E) beanKlass.newInstance(); // not sure how to
// handle generics
// here...
while (iter.hasNext()) {
final DBField f = iter.next();
final String fieldName = f.getName();
if (properties != null && !checkArray(fieldName, properties)) {
continue;
}
if (minus != null && checkArray(fieldName, minus)) {
continue;
}
final DBType type = f.getType();
final Object value = type.getFromResultSet(rset, ++index);
injectValue(item, fieldName, value, type.getTypeClass());
}
results.add(item);
total++;
if (limit > 0 && total == limit) {
return results;
}
}
return results;
} catch (Exception e) {
throw new BeanException(e);
} finally {
close(stmt, rset);
}
}
/**
* if Boolean consider TRUE to be set and FALSE to be not set.
*
* if Character, cast to integer and assume it is set if different than 0
*
* if Number consider everything different than zero to be set.
*
* Otherwise returns TRUE for anything different than null and FALSE for null.
*
* @param value
* @param returnType
* @return true if is set
*/
protected boolean isSet(final Object value, final Class extends Object> returnType) {
if (value != null) {
if (returnType.equals(boolean.class) && value instanceof Boolean) {
// if Boolean consider TRUE to be set and FALSE to be not set
// (false = default value)
final boolean b = ((Boolean) value).booleanValue();
return b;
} else if (returnType.equals(char.class) && value instanceof Character) {
// if Character, cast to int and assume set if different than
// 0...
final int c = ((Character) value).charValue();
return c != 0;
} else if (returnType.isPrimitive() && !returnType.equals(boolean.class) && !returnType.equals(char.class) && value instanceof Number) {
// if number consider everything different than zero to be
// set...
final Number n = (Number) value;
if (n.intValue() != 0) {
return true;
}
} else {
return true;
}
}
return false;
}
@Override
public int update(final Object bean) {
return update(bean, true);
}
@Override
public int updateAll(final Object bean) {
return update(bean, false);
}
private int update(final Object bean, final boolean dynUpdate) {
final Map fieldsLoaded = loaded.get(bean);
final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());
if (bc == null) {
throw new BeanException("Cannot find bean config: " + bean.getClass());
}
if (bc.getNumberOfFields() == 0) {
throw new BeanException("BeanConfig has zero fields: " + bc);
}
final StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());
sb.append("UPDATE ").append(bc.getTableName()).append(" SET ");
Iterator iter = bc.fields();
int count = 0;
final List values = new LinkedList();
while (iter.hasNext()) {
final DBField dbField = iter.next();
if (dbField.isPK()) {
continue;
}
final DBType type = dbField.getType();
if (type instanceof AutoIncrementType) {
continue;
}
if (type instanceof AutoTimestampType) {
continue;
}
boolean isNowOnUpdate = type instanceof NowOnUpdateTimestampType || type instanceof NowOnInsertAndUpdateTimestampType;
final String fieldName = dbField.getName();
final String dbFieldName = dbField.getDbName();
if (!isNowOnUpdate) {
final Method m = InjectionUtils.findMethodToGet(bean.getClass(), fieldName);
if (m == null) {
throw new BeanException("Cannot find method to get field from bean: " + fieldName);
}
final Class extends Object> returnType = m.getReturnType();
final Object value = getValueFromBean(bean, fieldName, m);
boolean update = false;
if (!dynUpdate) {
// if this is NOT a dynUpdate then update all properties with
// whatever value they have
update = true;
} else if (fieldsLoaded != null) {
// this is a dynUpdate, check if value is dirty, in other words,
// if it has changed since it was loaded...
final Value v = fieldsLoaded.get(fieldName);
if (v != null) {
if (value == null && v.value != null) {
update = true;
} else if (value != null && v.value == null) {
update = true;
} else if (value == null && v.value == null) {
update = false;
} else {
update = !value.equals(v.value);
}
}
} else {
// this is a dynUpdate, but bean was not previously loaded from
// the database...
// in this case only update if the property is considered to be
// SET...
update = isSet(value, returnType);
}
if (update) {
if (count++ > 0) {
sb.append(',');
}
sb.append(dbFieldName).append("=?");
values.add(new Value(dbField, value));
}
} else {
if (count++ > 0) {
sb.append(',');
}
sb.append(dbFieldName).append("=");
String nowCommand = getCurrentTimestampCommand();
if (nowCommand == null) {
sb.append(dbFieldName).append("=?");
values.add(new Value(dbField, new java.util.Date()));
} else {
sb.append(nowCommand);
}
}
}
if (count == 0) {
return 0;
}
sb.append(" WHERE ");
if (!bc.hasPK()) {
throw new BeanException("Cannot update bean without a PK!");
}
iter = bc.pks();
count = 0;
while (iter.hasNext()) {
final DBField dbField = iter.next();
final String fieldName = dbField.getName();
final String dbFieldName = dbField.getDbName();
final Object value = getValueFromBean(bean, fieldName);
if (value == null) {
throw new BeanException("pk is missing: " + dbField);
} else if (value instanceof Number) {
final Number n = (Number) value;
if (n.doubleValue() <= 0) {
throw new BeanException("Number pk is missing: " + dbField);
}
}
if (count++ > 0) {
sb.append(" AND ");
}
sb.append(dbFieldName).append("=?");
values.add(new Value(dbField, value));
}
if (values.isEmpty()) {
throw new BeanException("Bean is empty: " + bean + " / " + bc);
}
if (conn == null) {
throw new BeanException("Connection is null!");
}
PreparedStatement stmt = null;
try {
if (DEBUG) {
System.out.println("UPDATE SQL: " + sb.toString());
}
stmt = conn.prepareStatement(sb.toString());
Iterator iter2 = values.iterator();
int index = 0;
while (iter2.hasNext()) {
final Value v = iter2.next();
v.field.getType().bindToStmt(stmt, ++index, v.value);
}
final int x = stmt.executeUpdate();
if (x > 1) {
throw new BeanException("update modified more than one line: " + x);
}
if (x == 0) {
return 0;
}
if (fieldsLoaded != null) {
iter2 = values.iterator();
while (iter2.hasNext()) {
final Value v = iter2.next();
if (v.field.isPK()) {
continue;
}
final Value vv = fieldsLoaded.get(v.field.getName());
if (vv != null) {
vv.value = v.value;
}
}
}
return 1;
} catch (Exception e) {
throw new BeanException(e);
} finally {
close(stmt);
}
}
protected class QueryAndValues {
public QueryAndValues(StringBuilder sb, List values) {
this.sb = sb;
this.values = values;
}
public StringBuilder sb;
public List values;
}
protected QueryAndValues prepareInsertQuery(Object bean) {
final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());
if (bc == null) {
throw new BeanException("Cannot find bean config: " + bean.getClass());
}
if (bc.getNumberOfFields() == 0) {
throw new BeanException("BeanConfig has zero fields: " + bc);
}
final StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());
sb.append("INSERT INTO ").append(bc.getTableName()).append("(");
Iterator iter = bc.pks();
int count = 0;
final List values = new LinkedList();
while (iter.hasNext()) {
final DBField dbField = iter.next();
final String fieldName = dbField.getName();
final String dbFieldName = dbField.getDbName();
final DBType type = dbField.getType();
if (type instanceof AutoIncrementType) {
continue;
}
if (type instanceof AutoTimestampType) {
continue;
}
if (type instanceof NowOnUpdateTimestampType) {
continue;
}
final Object value = getValueFromBean(bean, fieldName);
if (count++ > 0) {
sb.append(',');
}
sb.append(dbFieldName);
values.add(new Value(dbField, value));
}
iter = bc.fields();
while (iter.hasNext()) {
final DBField dbField = iter.next();
if (dbField.isPK()) {
continue;
}
final String fieldName = dbField.getName();
final String dbFieldName = dbField.getDbName();
final DBType type = dbField.getType();
if (type instanceof AutoIncrementType) {
continue;
}
if (type instanceof AutoTimestampType) {
continue;
}
if (type instanceof NowOnUpdateTimestampType) {
continue;
}
boolean isNowOnInsert = type instanceof NowOnInsertTimestampType || type instanceof NowOnInsertAndUpdateTimestampType;
if (!isNowOnInsert) {
Object value = getValueFromBean(bean, fieldName);
if (count++ > 0) {
sb.append(',');
}
sb.append(dbFieldName);
values.add(new Value(dbField, value));
} else {
if (count++ > 0) {
sb.append(',');
}
sb.append(dbFieldName);
String cmd = getCurrentTimestampCommand();
if (cmd == null) {
values.add(new Value(dbField, new java.util.Date()));
} else {
values.add(new Value(dbField, true));
}
}
}
if (count == 0) {
throw new BeanException("There is nothing to insert!");
}
sb.append(") VALUES(");
final Iterator valuesIter = values.iterator();
int i = 0;
while (valuesIter.hasNext()) {
final Value v = valuesIter.next();
if (i > 0) {
sb.append(',');
}
if (v.isSysdate) {
sb.append(getCurrentTimestampCommand());
} else {
sb.append('?');
}
i++;
}
sb.append(')');
if (values.isEmpty()) {
throw new BeanException("Bean is empty: " + bean + " / " + bc);
}
return new QueryAndValues(sb, values);
}
protected Map bindToInsertStatement(PreparedStatement stmt, List values) {
final Iterator iter2 = values.iterator();
int index = 0;
final Map fieldsLoaded = new HashMap();
while (iter2.hasNext()) {
final Value v = iter2.next();
if (v.isSysdate && getCurrentTimestampCommand() != null) {
continue;
}
try {
v.field.getType().bindToStmt(stmt, ++index, v.value);
} catch (Exception e) {
throw new BeanException(e);
}
fieldsLoaded.put(v.field.getName(), v);
}
return fieldsLoaded;
}
@Override
public void insert(final Object bean) {
QueryAndValues qav = prepareInsertQuery(bean);
StringBuilder sb = qav.sb;
List values = qav.values;
if (conn == null) {
throw new BeanException("Connection is null!");
}
PreparedStatement stmt = null;
try {
if (DEBUG) {
System.out.println("INSERT SQL: " + sb.toString());
}
stmt = conn.prepareStatement(sb.toString());
Map fieldsLoaded = bindToInsertStatement(stmt, values);
final int x = stmt.executeUpdate();
if (x > 1) {
throw new BeanException("insert modified more than one line: " + x);
}
if (x == 0) {
throw new BeanException("Nothing was inserted! Insert returned 0 rows!");
}
loaded.put(bean, fieldsLoaded);
} catch (Exception e) {
throw new BeanException(e);
} finally {
close(stmt);
}
}
@Override
public boolean delete(final Object bean) {
final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());
if (bc.getNumberOfFields() == 0) {
throw new BeanException("BeanConfig has zero fields: " + bc);
}
final StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());
sb.append("DELETE FROM ").append(bc.getTableName()).append(" WHERE ");
if (!bc.hasPK()) {
throw new BeanException("Cannot delete bean without a PK!");
}
final Iterator iter = bc.pks();
final List values = new LinkedList();
int count = 0;
while (iter.hasNext()) {
final DBField dbField = iter.next();
final String fieldName = dbField.getName();
final String dbFieldName = dbField.getDbName();
final Object value = getValueFromBean(bean, fieldName);
if (value == null) {
throw new BeanException("pk is missing: " + dbField);
} else if (value instanceof Number) {
final Number n = (Number) value;
if (n.doubleValue() <= 0) {
throw new BeanException("Number pk is missing: " + dbField);
}
}
if (count++ > 0) {
sb.append(" AND ");
}
sb.append(dbFieldName).append("=?");
values.add(new Value(dbField, value));
}
if (values.isEmpty()) {
throw new BeanException("Bean is empty: " + bean + " / " + bc);
}
if (conn == null) {
throw new BeanException("Connection is null!");
}
PreparedStatement stmt = null;
try {
if (DEBUG) {
System.out.println("DELETE SQL: " + sb.toString());
}
stmt = conn.prepareStatement(sb.toString());
final Iterator iter2 = values.iterator();
int index = 0;
while (iter2.hasNext()) {
final Value v = iter2.next();
v.field.getType().bindToStmt(stmt, ++index, v.value);
}
final int x = stmt.executeUpdate();
if (x > 1) {
throw new BeanException("delete modified more than one line: " + x);
}
if (x == 0) {
return false;
}
loaded.remove(bean);
return true;
} catch (Exception e) {
throw new BeanException(e);
} finally {
close(stmt);
}
}
@Override
public List loadList(final E bean) {
return loadList(bean, null, -1);
}
@Override
public List loadList(final E bean, String[] properties) {
return loadListImpl(bean, null, -1, properties, null);
}
@Override
public E loadUnique(final E bean) {
E o = checkUnique(loadList(bean, null, 2));
if (o != null) {
load(o); // load twice to attach to session so dynamic update is by default!
}
return o;
}
@Override
public List loadList(final E bean, final String orderBy) {
return loadList(bean, orderBy, -1);
}
@Override
public List loadList(final E bean, final String orderBy, String[] properties) {
return loadListImpl(bean, orderBy, -1, properties, null);
}
@Override
public List loadList(final E bean, final int limit) {
return loadList(bean, null, limit);
}
@Override
public List loadList(final E bean, final int limit, String[] properties) {
return loadListImpl(bean, null, limit, properties, null);
}
/**
* Load a list of beans, but exclude some fields. Useful when the bean has too many properties and you don't want to fetch everything from the database.
*
* @param
* @param bean
* @param minus
* @return A list of beans
*/
@Override
public List loadListMinus(final E bean, final String[] minus) {
return loadListMinus(bean, null, -1, minus);
}
/**
* Load a list of beans, but exclude some fields. Useful when the bean has too many properties and you don't want to fetch everything from the database.
*
* @param
* @param bean
* @param minus
* @param orderBy
* @return A list of beans
*/
@Override
public List loadListMinus(final E bean, final String orderBy, final String[] minus) {
return loadListMinus(bean, orderBy, -1, minus);
}
/**
* Load a list of beans, but exclude some fields. Useful when the bean has too many properties and you don't want to fetch everything from the database.
*
* @param
* @param bean
* @param minus
* @param limit
* @return A list of beans
*/
@Override
public List loadListMinus(final E bean, final int limit, final String[] minus) {
return loadListMinus(bean, null, limit, minus);
}
/**
* Each dialect can override this to return the database column type it supports other than the ANSI standard.
*
* @param dbType
* @return The string representation of this database type to be used with create table statement
*/
protected String getDatabaseType(DBType> dbType) {
return dbType.getAnsiType();
}
@Override
public void createTable(Class extends Object> beanKlass) {
BeanConfig bc = beanManager.getBeanConfig(beanKlass);
if (bc == null) {
throw new BeanException("Cannot find bean config: " + beanKlass);
}
if (bc.getNumberOfFields() == 0) {
throw new BeanException("Cannot create table with zero columns: " + beanKlass);
}
if (bc.getNumberOfPKs() > 1) {
throw new BeanException("Cannot create table with composite primary key in an ANSI way: " + beanKlass);
}
StringBuilder sb = new StringBuilder(1024);
sb.append("create table ").append(bc.getTableName()).append(" (");
Iterator iter = bc.fields();
int count = 0;
while (iter.hasNext()) {
DBField dbField = iter.next();
DBType> dbType = dbField.getType();
if (count++ > 0) {
sb.append(", ");
}
sb.append(dbField.getDbName()).append(" ").append(getDatabaseType(dbType));
if (dbType instanceof SizedType) {
SizedType st = (SizedType) dbType;
sb.append("(").append(st.getSize()).append(")");
}
if (dbField.isPK()) {
sb.append(" PRIMARY KEY");
} else if (dbType.canBeNull() == false) {
sb.append(" NOT NULL");
}
}
sb.append(")");
if (DEBUG) {
System.out.println("CREATE TABLE SQL: " + sb.toString());
}
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(sb.toString());
stmt.executeUpdate();
} catch (Exception e) {
throw new BeanException(e);
} finally {
close(stmt);
}
}
@Override
public void createTables() {
Set all = beanManager.getBeanConfigs();
for (BeanConfig bc : all) {
createTable(bc.getBeanClass());
}
}
private int getSize(DBType> dbType) {
if (dbType instanceof SizedType) {
SizedType st = (SizedType) dbType;
return st.getSize();
}
throw new IllegalStateException("Cannot get size from type: " + dbType);
}
protected class Value {
public Object value;
public DBField field;
public boolean isSysdate;
private Value(final DBField field, Object value, final boolean isSysdate) {
this.field = field;
this.value = value;
this.isSysdate = isSysdate;
}
public Value(final DBField field, final Object value) {
this(field, value, false);
}
public Value(final DBField field, final boolean isSysdate) {
this(field, null, isSysdate);
}
}
static void close(PreparedStatement stmt) {
close(stmt, null);
}
static void close(PreparedStatement stmt, ResultSet rset) {
if (rset != null) {
try {
rset.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}