/*
* Copyright 2008 Alberto Gimeno <gimenete at gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package siena.jdbc;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import siena.AbstractPersistenceManager;
import siena.ClassInfo;
import siena.Generator;
import siena.Id;
import siena.Json;
import siena.Query;
import siena.QueryFilter;
import siena.QueryFilterSearch;
import siena.QueryFilterSimple;
import siena.SienaException;
import siena.SienaRestrictedApiException;
import siena.Util;
import siena.core.DecimalPrecision;
import siena.core.Polymorphic;
import siena.core.async.PersistenceManagerAsync;
import siena.core.options.QueryOption;
import siena.core.options.QueryOptionFetchType;
import siena.core.options.QueryOptionOffset;
import siena.core.options.QueryOptionPage;
import siena.core.options.QueryOptionState;
import siena.embed.Embedded;
import siena.embed.JsonSerializer;
public class JdbcPersistenceManager extends AbstractPersistenceManager {
private static final String DB = "JDBC";
private ConnectionManager connectionManager;
public JdbcPersistenceManager() {
}
public JdbcPersistenceManager(ConnectionManager connectionManager, Class<?> listener) {
this.connectionManager = connectionManager;
}
public void init(Properties p) {
if(p != null) {
String cm = p.getProperty("transactions");
if(cm != null) {
try {
connectionManager = (ConnectionManager) Class.forName(cm).newInstance();
} catch (Exception e) {
throw new SienaException(e);
}
} else {
connectionManager = new ThreadedConnectionManager();
}
}
if(connectionManager == null){
connectionManager = new ThreadedConnectionManager();
}
connectionManager.init(p);
}
protected Connection getConnection() throws SQLException {
return connectionManager.getConnection();
}
public void delete(Object obj) {
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
PreparedStatement ps = null;
try {
ps = getConnection().prepareStatement(classInfo.deleteSQL);
addParameters(obj, classInfo.keys, ps, 1);
int n = ps.executeUpdate();
if(n == 0) {
throw new SienaException("No updated rows");
}
if(n > 1) {
throw new SienaException(n+" rows deleted");
}
} catch(SQLException e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public void get(Object obj) {
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = getConnection().prepareStatement(classInfo.selectSQL);
addParameters(obj, classInfo.keys, ps, 1);
rs = ps.executeQuery();
if(rs.next()) {
JdbcMappingUtils.mapObject(obj, rs, null, null);
} else {
throw new SienaException("No such object");
}
} catch(SQLException e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeResultSet(rs);
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public void insert(Object obj) {
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
PreparedStatement ps = null;
try {
for (Field field : classInfo.keys) {
Id id = field.getAnnotation(Id.class);
if (id.value() == Generator.UUID) {
field.set(obj, UUID.randomUUID().toString());
}
}
// TODO: implement primary key generation: SEQUENCE
if (!classInfo.generatedKeys.isEmpty()) {
insertWithAutoIncrementKey(classInfo, obj);
} else {
ps = getConnection().prepareStatement(classInfo.insertSQL);
addParameters(obj, classInfo.insertFields, ps, 1);
ps.executeUpdate();
}
} catch (SienaException e) {
throw e;
} catch (Exception e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public void update(Object obj) {
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
PreparedStatement ps = null;
try {
ps = getConnection().prepareStatement(classInfo.updateSQL);
int i = 1;
i = addParameters(obj, classInfo.updateFields, ps, i);
addParameters(obj, classInfo.keys, ps, i);
ps.executeUpdate();
} catch(SQLException e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public void save(Object obj) {
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
PreparedStatement ps = null;
try {
Field idField = classInfo.info.getIdField();
Object idVal = Util.readField(obj, idField);
if(idVal == null) {
for (Field field : classInfo.keys) {
Id id = field.getAnnotation(Id.class);
if (id.value() == Generator.UUID) {
field.set(obj, UUID.randomUUID().toString());
}
}
}
// TODO: implement primary key generation: SEQUENCE
if (idVal == null && !classInfo.generatedKeys.isEmpty()) {
ps = getConnection().prepareStatement(classInfo.insertOrUpdateSQL,
Statement.RETURN_GENERATED_KEYS);
//insertWithAutoIncrementKey(classInfo, obj);
} else {
ps = getConnection().prepareStatement(classInfo.insertOrUpdateSQL);
}
int i = 1;
i = addParameters(obj, classInfo.allFields, ps, i);
addParameters(obj, classInfo.updateFields, ps, i);
ps.executeUpdate();
if (idVal == null && !classInfo.generatedKeys.isEmpty()) {
ResultSet gk = ps.getGeneratedKeys();
if (!gk.next())
throw new SienaException("No such generated keys");
i = 1;
for (Field field : classInfo.generatedKeys) {
field.setAccessible(true);
JdbcMappingUtils.setFromObject(obj, field, gk.getObject(i));
// field.set(obj, gk.getObject(i));
i++;
}
}
} catch (SienaException e) {
throw e;
} catch (Exception e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public void beginTransaction(int isolationLevel) {
connectionManager.beginTransaction(isolationLevel);
}
public void beginTransaction() {
connectionManager.beginTransaction();
}
public void commitTransaction() {
connectionManager.commitTransaction();
}
public void rollbackTransaction() {
connectionManager.rollbackTransaction();
}
public void closeConnection() {
connectionManager.closeConnection();
}
private PreparedStatement createStatement(String sql,
List<Object> parameters) throws SQLException {
PreparedStatement statement = getConnection().prepareStatement(sql);
if(parameters != null) {
int i = 1;
for (Object parameter : parameters) {
setParameter(statement, i++, parameter);
}
}
return statement;
}
/**
* required to be overriden for Postgres
*
* @param classInfo
* @param obj
* @throws SQLException
* @throws IllegalAccessException
*/
protected void insertWithAutoIncrementKey(JdbcClassInfo classInfo, Object obj) throws SQLException, IllegalAccessException {
ResultSet gk = null;
PreparedStatement ps = null;
try {
ps = getConnection().prepareStatement(classInfo.insertSQL,
Statement.RETURN_GENERATED_KEYS);
addParameters(obj, classInfo.insertFields, ps, 1);
ps.executeUpdate();
gk = ps.getGeneratedKeys();
if (!gk.next())
throw new SienaException("No such generated keys");
int i = 1;
for (Field field : classInfo.generatedKeys) {
field.setAccessible(true);
JdbcMappingUtils.setFromObject(obj, field, gk.getObject(i));
// field.set(obj, gk.getObject(i));
i++;
}
} finally {
JdbcDBUtils.closeResultSet(gk);
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
/**
* required to be overriden for Postgres
*
* @param classInfo
* @param objMap
* @throws SQLException
* @throws IllegalAccessException
*/
protected int insertBatchWithAutoIncrementKey(JdbcClassInfo classInfo, Map<JdbcClassInfo, List<Object>> objMap) throws SQLException, IllegalAccessException {
PreparedStatement ps = null;
ps = getConnection().prepareStatement(classInfo.insertSQL,
Statement.RETURN_GENERATED_KEYS);
for(Object obj: objMap.get(classInfo)){
for (Field field : classInfo.keys) {
Id id = field.getAnnotation(Id.class);
if (id.value() == Generator.UUID) {
field.set(obj, UUID.randomUUID().toString());
}
}
// TODO: implement primary key generation: SEQUENCE
addParameters(obj, classInfo.insertFields, ps, 1);
ps.addBatch();
}
// TODO what to do with results of executeBatch ??????
int[] res = ps.executeBatch();
if(!classInfo.generatedKeys.isEmpty()){
ResultSet gk = ps.getGeneratedKeys();
int i;
int idx = 0;
while(gk.next()) {
i=1;
for (Field field : classInfo.generatedKeys) {
field.setAccessible(true);
JdbcMappingUtils.setFromObject(objMap.get(classInfo).get(idx++), field, gk.getObject(i++));
}
}
}
return res.length;
}
protected int addParameters(Object obj, List<Field> fields, PreparedStatement ps, int i) throws SQLException {
for (Field field : fields) {
Class<?> type = field.getType();
if(ClassInfo.isModel(type) && ! ClassInfo.isEmbedded(field)) {
JdbcClassInfo ci = JdbcClassInfo.getClassInfo(type);
Object rel = Util.readField(obj, field);
for(Field f : ci.keys) {
if(rel != null) {
Object value = Util.readField(rel, f);
if(value instanceof Json)
value = ((Json)value).toString();
setParameter(ps, i++, value);
} else {
setParameter(ps, i++, null);
}
}
} else {
Object value = Util.readField(obj, field);
if(value != null){
if(Json.class.isAssignableFrom(type)){
value = ((Json)value).toString();
}
else if(field.getAnnotation(Embedded.class) != null){
value = JsonSerializer.serialize(value).toString();
}
else if(field.getAnnotation(Polymorphic.class) != null){
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out;
try {
out = new ObjectOutputStream(bos);
out.writeObject(value);
out.close();
} catch (IOException e) {
throw new SienaException(e);
}
value = bos.toByteArray();
}
else if(Enum.class.isAssignableFrom(type)){
value = value.toString();
}
else if(BigDecimal.class == type){
DecimalPrecision ann = field.getAnnotation(DecimalPrecision.class);
if(ann == null) {
value = (BigDecimal)value;
}else {
switch(ann.storageType()){
case DOUBLE:
value = ((BigDecimal)value).doubleValue();
break;
case STRING:
value = ((BigDecimal)value).toPlainString();
break;
case NATIVE:
value = (BigDecimal)value;
break;
}
}
}
}
setParameter(ps, i++, value);
}
}
return i;
}
protected void setParameter(PreparedStatement ps, int index, Object value) throws SQLException {
ps.setObject(index, value);
}
public <T> void appendSqlSearch(QueryFilterSearch qf, Class<?> clazz, JdbcClassInfo info, StringBuilder sql, List<Object> parameters) {
List<String> cols = new ArrayList<String>();
try {
for (String field : qf.fields) {
Field f = Util.getField(clazz, field);
String[] columns = ClassInfo.getColumnNames(f, info.tableName);
for (String col : columns) {
cols.add(col);
}
}
QueryOption opt = qf.option;
if(opt != null){
// only manages QueryOptionJdbcSearch
if(QueryOptionJdbcSearch.class.isAssignableFrom(opt.getClass())){
if(((QueryOptionJdbcSearch)opt).booleanMode){
sql.append("MATCH("+Util.join(cols, ",")+") AGAINST(? IN BOOLEAN MODE)");
}
else {
}
}else{
sql.append("MATCH("+Util.join(cols, ",")+") AGAINST(?)");
}
}else {
// as mysql default search is fulltext and as it requires a FULLTEXT index,
// by default, we use boolean mode which works without fulltext index
sql.append("MATCH("+Util.join(cols, ",")+") AGAINST(? IN BOOLEAN MODE)");
}
parameters.add(qf.match);
}catch(Exception e){
throw new SienaException(e);
}
}
public <T> void appendSqlWhere(Query<T> query, StringBuilder sql, List<Object> parameters) {
Class<T> clazz = query.getQueriedClass();
JdbcClassInfo info = JdbcClassInfo.getClassInfo(clazz);
List<QueryFilter> filters = query.getFilters();
if(filters.isEmpty()) { return; }
sql.append(JdbcDBUtils.WHERE);
boolean first = true;
for (QueryFilter filter : filters) {
if(QueryFilterSimple.class.isAssignableFrom(filter.getClass())){
QueryFilterSimple qf = (QueryFilterSimple)filter;
String op = qf.operator;
Object value = qf.value;
Field f = qf.field;
if(!first) {
sql.append(JdbcDBUtils.AND);
}
first = false;
String[] columns = ClassInfo.getColumnNames(f, info.tableName);
if("IN".equals(op)) {
if(!Collection.class.isAssignableFrom(value.getClass()))
throw new SienaException("Collection needed when using IN operator in filter() query");
StringBuilder s = new StringBuilder();
Collection<?> col = (Collection<?>) value;
for (Object object : col) {
// TODO: if object isModel
parameters.add(object);
s.append(",?");
}
sql.append(columns[0]+" IN("+s.toString().substring(1)+")");
} else if(ClassInfo.isModel(f.getType())) {
if(!op.equals("=")) {
throw new SienaException("Unsupported operator for relationship: "+op);
}
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(f.getType());
int i = 0;
JdbcMappingUtils.checkForeignKeyMapping(classInfo.keys, columns, query.getQueriedClass(), f);
for (Field key : classInfo.keys) {
if(value == null) {
sql.append(columns[i++]+JdbcDBUtils.IS_NULL);
} else {
sql.append(columns[i++]+"=?");
key.setAccessible(true);
Object o;
try {
o = key.get(value);
parameters.add(o);
} catch (Exception e) {
throw new SienaException(e);
}
}
}
} else {
if(value == null && op.equals("=")) {
sql.append(columns[0]+JdbcDBUtils.IS_NULL);
} else if(value == null && op.equals("!=")) {
sql.append(columns[0]+JdbcDBUtils.IS_NOT_NULL);
} else {
sql.append(columns[0]+op+"?");
if(value == null) {
parameters.add(Types.NULL);
} else {
if (value instanceof Date) {
value = Util.translateDate(f, (Date) value);
} else if(value instanceof Enum) {
value = value.toString();
}
parameters.add(value);
}
}
}
}else if(QueryFilterSearch.class.isAssignableFrom(filter.getClass())){
// TODO MYSQL implementation manages only 1 search in a query
if(query.getSearches().size()>1){
throw new SienaRestrictedApiException(DB, "search", "MySQL implementation manages only on single search at a time in a query");
}
// adds querysearch
QueryFilterSearch qf = (QueryFilterSearch)filter;
appendSqlSearch(qf, clazz, info, sql, parameters);
}
}
}
public void setConnectionManager(ConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}
private <T> List<T> doFetch(Query<T> query, int limit, int offset) {
QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
if(jdbcCtx==null){
jdbcCtx = new QueryOptionJdbcContext();
query.customize(jdbcCtx);
}
// activates page and offset options as there are always used in SQL requests
QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
if(!pag.isPaginating()){
if(pag.isActive()){
if(limit!=Integer.MAX_VALUE){
jdbcCtx.realPageSize = limit;
}
else {
jdbcCtx.realPageSize = pag.pageSize;
}
}
else {
jdbcCtx.realPageSize = limit;
}
}else {
jdbcCtx.realPageSize = pag.pageSize;
}
QueryOptionOffset offsetOpt = (QueryOptionOffset)(query.option(QueryOptionOffset.ID));
// if local offset has been set, uses it
if(offset!=0){
offsetOpt.activate();
offsetOpt.offset = offset;
}
QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
// if previousPage has detected there is no more data, simply returns an empty list
if(jdbcCtx.noMoreDataBefore){
return new ArrayList<T>();
}
if(state.isStateless()
|| (state.isStateful() && !jdbcCtx.isActive())
|| (state.isStateful() && jdbcCtx.isActive() && jdbcCtx.isClosed())) {
if(state.isStateless()){
if(pag.isPaginating()){
if(offsetOpt.isActive()){
jdbcCtx.realOffset+=offsetOpt.offset;
offsetOpt.passivate();
}else {
// keeps realOffset
}
}else {
// if page is active, immediately passivates it not to keep is active
if(pag.isActive()) {
pag.passivate();
}
if(offsetOpt.isActive()){
jdbcCtx.realOffset=offsetOpt.offset;
offsetOpt.passivate();
}else{
jdbcCtx.realOffset = 0;
}
}
} else {
if(offsetOpt.isActive()){
jdbcCtx.realOffset+=offsetOpt.offset;
offsetOpt.passivate();
}else {
// keeps realOffset
}
}
Class<T> clazz = query.getQueriedClass();
List<Object> parameters = new ArrayList<Object>();
StringBuilder sql = JdbcDBUtils.buildSqlSelect(query);
appendSqlWhere(query, sql, parameters);
JdbcDBUtils.appendSqlOrder(query, sql);
JdbcDBUtils.appendSqlLimitOffset(query, sql, parameters);
//sql.append(suffix);
PreparedStatement statement = null;
ResultSet rs = null;
try {
statement = createStatement(sql.toString(), parameters);
if(pag.isPaginating()) {
// this is just a hint to the DB so wonder if it should be used
statement.setFetchSize(jdbcCtx.realPageSize);
}
rs = statement.executeQuery();
List<T> result = JdbcMappingUtils.mapList(clazz, rs, ClassInfo.getClassInfo(clazz).tableName,
JdbcMappingUtils.getJoinFields(query), jdbcCtx.realPageSize);
if(pag.isPaginating()){
if(result.size() == 0){
jdbcCtx.noMoreDataAfter = true;
}
else {
jdbcCtx.noMoreDataAfter = false;
}
}else {
if(state.isStateful()){
jdbcCtx.realOffset += result.size();
}
}
if(state.isStateless()){
JdbcDBUtils.closeResultSet(rs);
JdbcDBUtils.closeStatementAndConnection(this, statement);
}else {
Integer offsetParamIdx = parameters.size();
Integer limitParamIdx = offsetParamIdx - 1;
// store indexes of offset and limit for reuse
jdbcCtx.activate();
jdbcCtx.statement = statement;
jdbcCtx.limitParamIdx = limitParamIdx;
jdbcCtx.offsetParamIdx = offsetParamIdx;
}
return result;
}
catch(SQLException e) {
JdbcDBUtils.closeResultSet(rs);
JdbcDBUtils.closeStatementAndConnection(this, statement);
throw new SienaException(e);
}
}else {
// payload has been initialized so goes on
Class<T> clazz = query.getQueriedClass();
if(offsetOpt.isActive()){
jdbcCtx.realOffset+=offsetOpt.offset;
offsetOpt.passivate();
}else {
// keeps realOffset
}
ResultSet rs = null;
try {
// when paginating, should update limit and offset
//if(pag.isActive()){
// update limit and offset
jdbcCtx.statement.setObject(jdbcCtx.limitParamIdx, jdbcCtx.realPageSize);
//}
//if(offsetOpt.isActive()){
jdbcCtx.statement.setObject(jdbcCtx.offsetParamIdx, jdbcCtx.realOffset);
//}
rs = jdbcCtx.statement.executeQuery();
List<T> result = JdbcMappingUtils.mapList(clazz, rs, ClassInfo.getClassInfo(clazz).tableName,
JdbcMappingUtils.getJoinFields(query), jdbcCtx.realPageSize);
// increases offset
if(pag.isPaginating()){
if(result.size() == 0){
jdbcCtx.noMoreDataAfter = true;
}
else {
jdbcCtx.noMoreDataAfter = false;
}
}else {
jdbcCtx.realOffset += result.size();
}
return result;
}catch(SQLException ex){
JdbcDBUtils.closeResultSet(rs);
JdbcDBUtils.closeStatementAndConnection(this, jdbcCtx.statement);
throw new SienaException(ex);
}
}
}
@Override
public <T> List<T> fetch(Query<T> query) {
List<T> result = doFetch(query, Integer.MAX_VALUE, 0);
return result;
}
@Override
public <T> List<T> fetch(Query<T> query, int limit) {
//((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
List<T> result = doFetch(query, limit, 0);
//List<T> result = fetch(query, " LIMIT "+limit);
return result;
}
public <T> List<T> fetch(Query<T> query, int limit, Object offset) {
//((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
//((QueryOptionOffset)query.option(QueryOptionOffset.ID).activate()).offset=(Integer)offset;
List<T> result = doFetch(query, limit, (Integer)offset);
//List<T> result = fetch(query, " LIMIT "+limit+" OFFSET "+offset);
//query.setNextOffset(result.size());
return result;
}
public <T> int count(Query<T> query) {
ClassInfo info = ClassInfo.getClassInfo(query.getQueriedClass());
List<Object> parameters = new ArrayList<Object>();
StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM ");
sql.append(info.tableName);
appendSqlWhere(query, sql, parameters);
PreparedStatement statement = null;
ResultSet rs = null;
try {
statement = createStatement(sql.toString(), parameters);
rs = statement.executeQuery();
rs.next();
return rs.getInt(1);
} catch(SQLException e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeResultSet(rs);
JdbcDBUtils.closeStatementAndConnection(this, statement);
}
}
public <T> int delete(Query<T> query) {
ClassInfo info = ClassInfo.getClassInfo(query.getQueriedClass());
List<Object> parameters = new ArrayList<Object>();
StringBuilder sql = new StringBuilder("DELETE FROM ");
sql.append(info.tableName);
appendSqlWhere(query, sql, parameters);
PreparedStatement statement = null;
ResultSet rs = null;
try {
statement = createStatement(sql.toString(), parameters);
return statement.executeUpdate();
} catch(SQLException e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeResultSet(rs);
JdbcDBUtils.closeStatementAndConnection(this, statement);
}
}
private <T> List<T> doFetchKeys(Query<T> query, int limit, int offset) {
QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
if(jdbcCtx==null){
jdbcCtx = new QueryOptionJdbcContext();
query.customize(jdbcCtx);
}
// activates page and offset options as there are always used in SQL requests
QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
if(!pag.isPaginating()){
if(pag.isActive()){
if(limit!=Integer.MAX_VALUE){
jdbcCtx.realPageSize = limit;
}
else {
jdbcCtx.realPageSize = pag.pageSize;
}
}
else {
jdbcCtx.realPageSize = limit;
}
}else {
jdbcCtx.realPageSize = pag.pageSize;
}
QueryOptionOffset offsetOpt = (QueryOptionOffset)(query.option(QueryOptionOffset.ID));
// if local offset has been set, uses it
if(offset!=0){
offsetOpt.activate();
offsetOpt.offset = offset;
}
QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
// if previousPage has detected there is no more data, simply returns an empty list
if(jdbcCtx.noMoreDataBefore){
return new ArrayList<T>();
}
if(state.isStateless() || (state.isStateful() && !jdbcCtx.isActive())) {
if(state.isStateless()){
if(pag.isPaginating()){
if(offsetOpt.isActive()){
jdbcCtx.realOffset+=offsetOpt.offset;
offsetOpt.passivate();
}else {
// keeps realOffset
}
}else {
// if page is active, immediately passivates it not to keep is active
if(pag.isActive()) {
pag.passivate();
}
if(offsetOpt.isActive()){
jdbcCtx.realOffset=offsetOpt.offset;
offsetOpt.passivate();
}else{
jdbcCtx.realOffset = 0;
}
}
} else {
if(offsetOpt.isActive()){
jdbcCtx.realOffset+=offsetOpt.offset;
offsetOpt.passivate();
}else {
// keeps realOffset
}
}
Class<T> clazz = query.getQueriedClass();
List<Object> parameters = new ArrayList<Object>();
StringBuilder sql = JdbcDBUtils.buildSqlSelect(query);
appendSqlWhere(query, sql, parameters);
JdbcDBUtils.appendSqlOrder(query, sql);
JdbcDBUtils.appendSqlLimitOffset(query, sql, parameters);
//sql.append(suffix);
PreparedStatement statement = null;
ResultSet rs = null;
try {
statement = createStatement(sql.toString(), parameters);
if(pag.isActive()) {
// this is just a hint to the DB so wonder if it should be used
statement.setFetchSize(jdbcCtx.realPageSize);
}
rs = statement.executeQuery();
List<T> result = JdbcMappingUtils.mapListKeys(clazz, rs, ClassInfo.getClassInfo(clazz).tableName,
JdbcMappingUtils.getJoinFields(query), jdbcCtx.realPageSize);
if(pag.isPaginating()){
if(result.size() == 0){
jdbcCtx.noMoreDataAfter = true;
}
else {
jdbcCtx.noMoreDataAfter = false;
}
}else {
if(state.isStateful()){
jdbcCtx.realOffset += result.size();
}
}
if(state.isStateless()){
JdbcDBUtils.closeResultSet(rs);
JdbcDBUtils.closeStatementAndConnection(this, statement);
}else {
Integer offsetParamIdx = parameters.size();
Integer limitParamIdx = offsetParamIdx - 1;
// store indexes of offset and limit for reuse
jdbcCtx.activate();
jdbcCtx.statement = statement;
jdbcCtx.limitParamIdx = limitParamIdx;
jdbcCtx.offsetParamIdx = offsetParamIdx;
}
return result;
} catch(SQLException e) {
JdbcDBUtils.closeResultSet(rs);
JdbcDBUtils.closeStatementAndConnection(this, statement);
throw new SienaException(e);
}
}else {
// payload has been initialized so goes on
Class<T> clazz = query.getQueriedClass();
if(offsetOpt.isActive()){
jdbcCtx.realOffset+=offsetOpt.offset;
offsetOpt.passivate();
}else {
// keeps realOffset
}
ResultSet rs = null;
try {
// when paginating, should update limit and offset
//if(pag.isActive()){
// update limit and offset
jdbcCtx.statement.setObject(jdbcCtx.limitParamIdx, jdbcCtx.realPageSize);
//}
//if(offsetOpt.isActive()){
jdbcCtx.statement.setObject(jdbcCtx.offsetParamIdx, jdbcCtx.realOffset);
//}
rs = jdbcCtx.statement.executeQuery();
List<T> result = JdbcMappingUtils.mapListKeys(clazz, rs, ClassInfo.getClassInfo(clazz).tableName,
JdbcMappingUtils.getJoinFields(query), jdbcCtx.realPageSize);
if(pag.isPaginating()){
if(result.size() == 0){
jdbcCtx.noMoreDataAfter = true;
}
else {
jdbcCtx.noMoreDataAfter = false;
}
}else {
jdbcCtx.realOffset += result.size();
}
return result;
}catch(SQLException ex){
JdbcDBUtils.closeResultSet(rs);
JdbcDBUtils.closeStatementAndConnection(this, jdbcCtx.statement);
throw new SienaException(ex);
}
}
}
public <T> List<T> fetchKeys(Query<T> query) {
((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY;
List<T> result = doFetchKeys(query, Integer.MAX_VALUE, 0);
//query.setNextOffset(result.size());
return result;
}
public <T> List<T> fetchKeys(Query<T> query, int limit) {
((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY;
//((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
List<T> result = doFetchKeys(query, limit, 0);
//query.setNextOffset(result.size());
return result;
}
public <T> List<T> fetchKeys(Query<T> query, int limit, Object offset) {
((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY;
// ((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
// ((QueryOptionOffset)query.option(QueryOptionOffset.ID).activate()).offset=(Integer)offset;
List<T> result = doFetchKeys(query, limit, (Integer)offset);
//query.setNextOffset(result.size());
return result;
}
private <T> Iterable<T> doIter(Query<T> query, int limit, int offset) {
QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
if(jdbcCtx==null){
jdbcCtx = new QueryOptionJdbcContext();
query.customize(jdbcCtx);
}
// activates page and offset options as there are always used in SQL requests
QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
// QueryOptionFetchType fetchType = (QueryOptionFetchType)query.option(QueryOptionFetchType.ID);
// in iter_per_page mode, always trigger pagination
// if(fetchType.fetchType == QueryOptionFetchType.Type.ITER_PER_PAGE){
// pag.pageType = QueryOptionPage.PageType.PAGINATING;
// pag.pageSize = limit;
// jdbcCtx.realPageSize = limit;
// }
// else {
if(!pag.isPaginating()){
if(pag.isActive()){
if(limit!=Integer.MAX_VALUE){
jdbcCtx.realPageSize = limit;
}
else {
jdbcCtx.realPageSize = pag.pageSize;
}
}
else {
jdbcCtx.realPageSize = limit;
}
}else {
jdbcCtx.realPageSize = pag.pageSize;
}
// }
QueryOptionOffset offsetOpt = (QueryOptionOffset)(query.option(QueryOptionOffset.ID));
// if local offset has been set, uses it
if(offset!=0){
offsetOpt.activate();
offsetOpt.offset = offset;
}
QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
// if previousPage has detected there is no more data, simply returns an empty list
if(jdbcCtx.noMoreDataBefore){
return new ArrayList<T>();
}
// forces the reusable option since iteration requires it!!!
//query.stateful();
if(state.isStateless() || (state.isStateful() && !jdbcCtx.isActive())) {
if(state.isStateless()){
if(pag.isPaginating()){
if(offsetOpt.isActive()){
jdbcCtx.realOffset+=offsetOpt.offset;
offsetOpt.passivate();
}else {
// keeps realOffset
}
}else {
// if page is active, immediately passivates it not to keep is active
if(pag.isActive()) {
pag.passivate();
}
if(offsetOpt.isActive()){
jdbcCtx.realOffset=offsetOpt.offset;
offsetOpt.passivate();
}else{
jdbcCtx.realOffset = 0;
}
}
} else {
if(offsetOpt.isActive()){
jdbcCtx.realOffset+=offsetOpt.offset;
offsetOpt.passivate();
}else {
// keeps realOffset
}
}
List<Object> parameters = new ArrayList<Object>();
StringBuilder sql = JdbcDBUtils.buildSqlSelect(query);
appendSqlWhere(query, sql, parameters);
JdbcDBUtils.appendSqlOrder(query, sql);
JdbcDBUtils.appendSqlLimitOffset(query, sql, parameters);
//sql.append(suffix);
PreparedStatement statement = null;
ResultSet rs = null;
try {
statement = createStatement(sql.toString(), parameters);
if(pag.isActive()) {
// this is just a hint to the DB so wonder if it should be used
statement.setFetchSize(jdbcCtx.realPageSize);
}
rs = statement.executeQuery();
if(state.isStateless()){
//in iteration, doesn't close the resultset to reuse it
//JdbcDBUtils.closeResultSet(rs);
//JdbcDBUtils.closeStatement(statement);
}else {
Integer offsetParamIdx = parameters.size();
Integer limitParamIdx = offsetParamIdx - 1;
// store indexes of offset and limit for reuse
jdbcCtx.activate();
jdbcCtx.statement = statement;
jdbcCtx.limitParamIdx = limitParamIdx;
jdbcCtx.offsetParamIdx = offsetParamIdx;
}
return new JdbcSienaIterable<T>(this, statement, rs, query);
}
catch(SQLException e) {
JdbcDBUtils.closeResultSet(rs);
JdbcDBUtils.closeStatementAndConnection(this, statement);
throw new SienaException(e);
}
}else {
if(offsetOpt.isActive()){
jdbcCtx.realOffset+=offsetOpt.offset;
offsetOpt.passivate();
}else {
// keeps realOffset
}
// payload has been initialized so goes on
try {
// when paginating, should update limit and offset
//if(pag.isActive()){
// update limit and offset
jdbcCtx.statement.setObject(jdbcCtx.limitParamIdx, jdbcCtx.realPageSize);
//}
//if(offsetOpt.isActive()){
jdbcCtx.statement.setObject(jdbcCtx.offsetParamIdx, jdbcCtx.realOffset);
//}
ResultSet rs = jdbcCtx.statement.executeQuery();
return new JdbcSienaIterable<T>(this, jdbcCtx.statement, rs, query);
}catch(SQLException ex){
JdbcDBUtils.closeStatementAndConnection(this, jdbcCtx.statement);
throw new SienaException(ex);
}
}
}
public <T> Iterable<T> iter(Query<T> query) {
((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER;
return doIter(query, Integer.MAX_VALUE, 0);
}
public <T> Iterable<T> iter(Query<T> query, int limit) {
((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER;
//((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
return doIter(query, limit, 0);
}
public <T> Iterable<T> iter(Query<T> query, int limit, Object offset) {
((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER;
//((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
// if in stateful mode, the offset should be added to the current one
//((QueryOptionOffset)query.option(QueryOptionOffset.ID).activate()).offset=(Integer)offset;
return doIter(query, limit, (Integer)offset);
}
public <T> void release(Query<T> query) {
super.release(query);
QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
if(jdbcCtx != null && jdbcCtx.isActive()){
JdbcDBUtils.closeStatementAndConnection(this, jdbcCtx.statement);
jdbcCtx.statement = null;
jdbcCtx.passivate();
}
}
@Override
public <T> void paginate(Query<T> query) {
QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
if(jdbcCtx==null){
jdbcCtx = new QueryOptionJdbcContext();
query.customize(jdbcCtx);
}
// resets the realoffset to 0 if stateless
if(state.isStateless()){
jdbcCtx.realOffset = 0;
}
}
public <T> void previousPage(Query<T> query) {
QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
if(jdbcCtx==null){
jdbcCtx = new QueryOptionJdbcContext();
query.customize(jdbcCtx);
}
// if no more data before, doesn't try to go before
if(jdbcCtx.noMoreDataBefore){
return;
}
// if no more data after, removes flag to be able to go before
if(jdbcCtx.noMoreDataAfter){
jdbcCtx.noMoreDataAfter = false;
}
QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
if(pag.isPaginating()){
//QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
//if(offset.isActive()){
jdbcCtx.realPageSize = pag.pageSize;
if(jdbcCtx.realOffset>=pag.pageSize) {
jdbcCtx.realOffset-=pag.pageSize;
}
else {
jdbcCtx.realOffset = 0;
jdbcCtx.noMoreDataBefore = true;
}
//}
}else {
// throws exception because it's impossible to reuse nextPage when paginating has been interrupted, the cases are too many
throw new SienaException("Can't use nextPage after pagination has been interrupted...");
}
}
public <T> void nextPage(Query<T> query) {
QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
if(jdbcCtx==null){
jdbcCtx = new QueryOptionJdbcContext();
query.customize(jdbcCtx);
}
// if no more data after, doesn't try to go after
if(jdbcCtx.noMoreDataAfter){
return;
}
// if no more data before, removes flag to be able and stay there
if(jdbcCtx.noMoreDataBefore){
jdbcCtx.noMoreDataBefore = false;
return;
}
QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
if(pag.isPaginating()){
//QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
//if(offset.isActive()){
jdbcCtx.realPageSize = pag.pageSize;
jdbcCtx.realOffset+=pag.pageSize;
//}
}else {
// throws exception because it's impossible to reuse nextPage when paginating has been interrupted, the cases are too many
throw new SienaException("Can't use nextPage after pagination has been interrupted...");
}
}
public int get(Object... objects) {
return get(Arrays.asList(objects));
}
public <T> int get(Iterable<T> objects) {
Map<JdbcClassInfo, List<Object>> objMap = new HashMap<JdbcClassInfo, List<Object>>();
PreparedStatement ps = null;
for(Object obj:objects){
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
if(!objMap.containsKey(classInfo)){
List<Object> l = new ArrayList<Object>();
l.add(obj);
objMap.put(classInfo, l);
}else{
objMap.get(classInfo).add(obj);
}
}
int total = 0;
try {
for(JdbcClassInfo classInfo: objMap.keySet()){
// doesn't manage multiple keys case
if(classInfo.keys.size()>1){
throw new SienaException("Can't batch select multiple keys objects");
}
Field f = classInfo.keys.get(0);
HashMap<Object, Object> keyObj = new HashMap<Object, Object>();
for(Object obj: objMap.get(classInfo)){
Object key = Util.readField(obj, f);
keyObj.put(key, obj);
}
Query<?> q = createQuery(classInfo.info.clazz);
List<?> results = q.filter(f.getName()+ " IN", keyObj.keySet()).fetch();
for(Object res:results){
Object resKey = Util.readField(res, f);
Util.copyObject(res, keyObj.get(resKey));
}
total+=results.size();
}
return total;
} catch (SienaException e) {
throw e;
} catch (Exception e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public <T> T getByKey(Class<T> clazz, Object key) {
PreparedStatement ps = null;
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(clazz);
try {
// doesn't manage multiple keys case
if(classInfo.keys.size()>1){
throw new SienaException("Can't batch select multiple keys objects");
}
Query<T> q = createQuery(clazz);
return q.filter(classInfo.info.getIdField().getName()+ "=", key).get();
} catch (SienaException e) {
throw e;
} catch (Exception e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public <T> List<T> getByKeys(Class<T> clazz, Object... keys) {
return getByKeys(clazz, Arrays.asList(keys));
}
public <T> List<T> getByKeys(Class<T> clazz, Iterable<?> keys) {
PreparedStatement ps = null;
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(clazz);
try {
// doesn't manage multiple keys case
if(classInfo.keys.size()>1){
throw new SienaException("Can't batch select multiple keys objects");
}
Field f = classInfo.keys.get(0);
List<Object> keyList = new ArrayList<Object>();
for(Object key:keys){
keyList.add(key);
}
Query<T> q = createQuery(clazz);
List<T> results = q.filter(f.getName()+ " IN", keyList).fetch();
List<T> realResults = new ArrayList<T>();
HashMap<Object, T> keyObj = new HashMap<Object, T>();
for(Object key: keys){
if(!keyObj.containsKey(key)){
for(int i=0; i<results.size();i++){
T res = results.get(i);
Object resKey = Util.readField(res, f);
if(key.equals(resKey)){
keyObj.put(key, res);
results.remove(i);
break;
}
}
}
realResults.add(keyObj.get(key));
}
return realResults;
} catch (SienaException e) {
throw e;
} catch (Exception e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public <T> PersistenceManagerAsync async() {
throw new SienaException("Not Implemented");
}
public int insert(Object... objects) {
return insert(Arrays.asList(objects));
}
public int insert(Iterable<?> objects) {
Map<JdbcClassInfo, List<Object>> objMap = new HashMap<JdbcClassInfo, List<Object>>();
PreparedStatement ps = null;
for(Object obj:objects){
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
if(!objMap.containsKey(classInfo)){
List<Object> l = new ArrayList<Object>();
l.add(obj);
objMap.put(classInfo, l);
}else{
objMap.get(classInfo).add(obj);
}
}
int total = 0;
try {
for(JdbcClassInfo classInfo: objMap.keySet()){
if(classInfo.generatedKeys.isEmpty()){
ps = getConnection().prepareStatement(classInfo.insertSQL);
for(Object obj: objMap.get(classInfo)){
for (Field field : classInfo.keys) {
Id id = field.getAnnotation(Id.class);
if (id.value() == Generator.UUID) {
field.set(obj, UUID.randomUUID().toString());
}
}
// TODO: implement primary key generation: SEQUENCE
addParameters(obj, classInfo.insertFields, ps, 1);
ps.addBatch();
}
// TODO what to do with results of executeBatch ??????
int[] res = ps.executeBatch();
total+=res.length;
}else {
total+=insertBatchWithAutoIncrementKey(classInfo, objMap);
}
}
return total;
} catch (SienaException e) {
throw e;
} catch (Exception e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public int delete(Object... objects) {
return delete(Arrays.asList(objects));
}
public int delete(Iterable<?> objects) {
Map<JdbcClassInfo, List<Object>> objMap = new HashMap<JdbcClassInfo, List<Object>>();
PreparedStatement ps = null;
for(Object obj:objects){
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
if(!objMap.containsKey(classInfo)){
List<Object> l = new ArrayList<Object>();
l.add(obj);
objMap.put(classInfo, l);
}else{
objMap.get(classInfo).add(obj);
}
}
int total = 0;
try {
for(JdbcClassInfo classInfo: objMap.keySet()){
ps = getConnection().prepareStatement(classInfo.deleteSQL);
for(Object obj: objMap.get(classInfo)){
addParameters(obj, classInfo.keys, ps, 1);
ps.addBatch();
}
// TODO what to do with results of executeBatch ??????
int[] res = ps.executeBatch();
total+=res.length;
}
return total;
} catch (SienaException e) {
throw e;
} catch (Exception e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public <T> int deleteByKeys(Class<T> clazz, Object... keys) {
return deleteByKeys(clazz, Arrays.asList(keys));
}
public <T> int deleteByKeys(Class<T> clazz, Iterable<?> keys) {
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(clazz);
PreparedStatement ps = null;
try {
ps = getConnection().prepareStatement(classInfo.deleteSQL);
for(Object key: keys){
setParameter(ps, 1, key);
ps.addBatch();
}
// TODO what to do with results of executeBatch ??????
int res[] = ps.executeBatch();
return res.length;
} catch (SienaException e) {
throw e;
} catch (Exception e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public <T> int update(Object... objects) {
return update(Arrays.asList(objects));
}
public <T> int update(Iterable<T> objects) {
//throw new NotImplementedException("update not implemented for JDBC yet");
Map<JdbcClassInfo, List<Object>> objMap = new HashMap<JdbcClassInfo, List<Object>>();
PreparedStatement ps = null;
for(Object obj:objects){
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
if(!objMap.containsKey(classInfo)){
List<Object> l = new ArrayList<Object>();
l.add(obj);
objMap.put(classInfo, l);
}else{
objMap.get(classInfo).add(obj);
}
}
int total = 0;
try {
for(JdbcClassInfo classInfo: objMap.keySet()){
ps = getConnection().prepareStatement(classInfo.updateSQL);
for(Object obj: objMap.get(classInfo)){
int i = 1;
i = addParameters(obj, classInfo.updateFields, ps, i);
addParameters(obj, classInfo.keys, ps, i);
ps.addBatch();
}
// TODO what to do with results of executeBatch ??????
int[] res = ps.executeBatch();
total+=res.length;
}
return total;
} catch (SienaException e) {
throw e;
} catch (Exception e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
public <T> int update(Query<T> query, Map<String, ?> fieldValues) {
throw new SienaException("Not Implemented");
}
@Override
public int save(Object... objects) {
return save(Arrays.asList(objects));
}
@Override
public int save(Iterable<?> objects) {
Map<JdbcClassInfo, List<Object>> objMap = new HashMap<JdbcClassInfo, List<Object>>();
PreparedStatement ps = null;
for(Object obj:objects){
JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
if(!objMap.containsKey(classInfo)){
List<Object> l = new ArrayList<Object>();
l.add(obj);
objMap.put(classInfo, l);
}else{
objMap.get(classInfo).add(obj);
}
}
int total = 0;
try {
for(JdbcClassInfo classInfo: objMap.keySet()){
if (!classInfo.generatedKeys.isEmpty()) {
ps = getConnection().prepareStatement(classInfo.insertOrUpdateSQL,
Statement.RETURN_GENERATED_KEYS);
} else {
ps = getConnection().prepareStatement(classInfo.insertOrUpdateSQL);
}
for(Object obj: objMap.get(classInfo)){
Field idField = classInfo.info.getIdField();
Object idVal = Util.readField(obj, idField);
// only generates a UUID if the idVal is null
if(idVal == null){
for (Field field : classInfo.keys) {
Id id = field.getAnnotation(Id.class);
if (id.value() == Generator.UUID) {
field.set(obj, UUID.randomUUID().toString());
}
}
}
// TODO: implement primary key generation: SEQUENCE
int i = 1;
i = addParameters(obj, classInfo.allFields, ps, i);
addParameters(obj, classInfo.updateFields, ps, i);
ps.addBatch();
}
int[] res = ps.executeBatch();
if(!classInfo.generatedKeys.isEmpty()){
ResultSet gk = ps.getGeneratedKeys();
int i;
int idx = 0;
int sz = objMap.get(classInfo).size();
// apparently in the update case, it returns not only the generated keys but also all the updated field values
// so we take only the first SZ values which are the key values.
while(gk.next() && idx < sz) {
i=1;
for (Field field : classInfo.generatedKeys) {
field.setAccessible(true);
JdbcMappingUtils.setFromObject(objMap.get(classInfo).get(idx++), field, gk.getObject(i++));
}
}
}
total+=res.length;
}
return total;
} catch (SienaException e) {
throw e;
} catch (Exception e) {
throw new SienaException(e);
} finally {
JdbcDBUtils.closeStatementAndConnection(this, ps);
}
}
private static final String[] supportedOperators = new String[]{ "<", ">", ">=", "<=", "!=", "=", "IN" };
public String[] supportedOperators() {
return supportedOperators;
}
public static class JdbcClassInfo {
protected static Map<Class<?>, JdbcClassInfo> infoClasses = new ConcurrentHashMap<Class<?>, JdbcClassInfo>();
// encapsulates a classinfo
public ClassInfo info;
public String tableName;
public String insertSQL;
public String updateSQL;
public String insertOrUpdateSQL;
public String deleteSQL;
public String selectSQL;
public String baseSelectSQL;
public String keySelectSQL;
public String baseKeySelectSQL;
public List<Field> keys = null;
public List<Field> insertFields = null;
public List<Field> updateFields = null;
public List<Field> generatedKeys = null;
public List<Field> allFields = null;
public List<Field> joinFields = null;
public Map<String, String> joinFieldAliases = new HashMap<String, String>();
public JdbcClassInfo(ClassInfo info) {
this.info = info;
keys = info.keys;
insertFields = info.insertFields;
updateFields = info.updateFields;
generatedKeys = info.generatedKeys;
allFields = info.allFields;
tableName = info.tableName;
joinFields = info.joinFields;
List<String> keyColumns = new ArrayList<String>();
List<String> keyWhereColumns = new ArrayList<String>();
List<String> insertColumns = new ArrayList<String>();
List<String> updateColumns = new ArrayList<String>();
List<String> allColumns = new ArrayList<String>();
calculateColumns(info.insertFields, insertColumns, null, "");
calculateColumns(info.updateFields, updateColumns, null, "=?");
calculateColumns(info.keys, keyColumns, null, "");
calculateColumns(info.keys, keyWhereColumns, null, "=?");
calculateColumns(info.allFields, allColumns, null, "");
deleteSQL = "DELETE FROM " + tableName + JdbcDBUtils.WHERE + Util.join(keyWhereColumns, JdbcDBUtils.AND);
String[] is = new String[insertColumns.size()];
Arrays.fill(is, "?");
insertSQL =
"INSERT INTO " + tableName
+ " ("+Util.join(insertColumns, ", ") + ")"
+ " VALUES(" + Util.join(Arrays.asList(is), ", ") + ")";
updateSQL =
"UPDATE " + tableName
+ " SET " + Util.join(updateColumns, ", ")
+ JdbcDBUtils.WHERE + Util.join(keyWhereColumns, JdbcDBUtils.AND);
// Update or insert for MYSQL ONLY
String[] as = new String[allColumns.size()];
Arrays.fill(as, "?");
insertOrUpdateSQL =
"INSERT INTO " + tableName + " ("+Util.join(allColumns, ", ") + ")"
+ " VALUES(" + Util.join(Arrays.asList(as), ", ") + ")"
+ " ON DUPLICATE KEY UPDATE " + Util.join(updateColumns, ", ");
baseSelectSQL = "SELECT "+Util.join(allColumns, ", ")+" FROM "+tableName;
baseKeySelectSQL = "SELECT "+Util.join(keyColumns, ", ")+" FROM "+tableName;
selectSQL = baseSelectSQL + JdbcDBUtils.WHERE + Util.join(keyWhereColumns, JdbcDBUtils.AND);
keySelectSQL = baseKeySelectSQL+JdbcDBUtils.WHERE+Util.join(keyWhereColumns, JdbcDBUtils.AND);
}
public static void calculateColumns(List<Field> fields, List<String> columns, String tableName, String suffix) {
for (Field field : fields) {
String[] columnNames = ClassInfo.getColumnNames(field, tableName);
for (String columnName : columnNames) {
columns.add(columnName+suffix);
}
}
}
public static void calculateColumnsAliases(List<Field> fields, List<String> columns, String tableName, String suffix) {
for (Field field : fields) {
String[] columnNames = ClassInfo.getColumnNames(field, tableName);
for (String columnName : columnNames) {
columns.add(columnName+suffix+ " AS "+aliasFromCol(columnName+suffix));
}
}
}
public static String aliasFromCol(String col){
return col.replace('.', '_');
}
public static JdbcClassInfo getClassInfo(Class<?> clazz) {
JdbcClassInfo ci = infoClasses.get(clazz);
if(ci == null) {
ci = new JdbcClassInfo(ClassInfo.getClassInfo(clazz));
infoClasses.put(clazz, ci);
}
return ci;
}
}
}