
jodd.db.oom.DbOomQuery Maven / Gradle / Ivy
Go to download
Jodd DB is efficient and thin database facade; DbOom is convenient database object mapper.
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package jodd.db.oom;
import jodd.db.DbQuery;
import jodd.db.DbSession;
import jodd.db.DbUtil;
import jodd.db.oom.mapper.DefaultResultSetMapper;
import jodd.db.oom.mapper.ResultSetMapper;
import jodd.db.oom.sqlgen.ParameterValue;
import jodd.db.type.SqlType;
import jodd.util.StringUtil;
import jodd.log.Logger;
import jodd.log.LoggerFactory;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Map;
import static jodd.db.oom.DbOomUtil.initialCollectionSize;
/**
* A simple ORM extension for {@link DbQuery}.
*
* OOM extension may map results to objects in two ways:
*
* - auto mode - when result set is mapped to provided types, and
* - mapped mode - requires explicit mapping definitions.
*
*/
public class DbOomQuery extends DbQuery {
private static final Logger log = LoggerFactory.getLogger(DbOomQuery.class);
// ---------------------------------------------------------------- default ctors
public DbOomQuery(Connection conn, String sqlString) {
super(conn, sqlString);
}
public static DbOomQuery query(Connection conn, String sqlString) {
return new DbOomQuery(conn, sqlString);
}
public DbOomQuery(DbSession session, String sqlString) {
super(session, sqlString);
}
public static DbOomQuery query(DbSession session, String sqlString) {
return new DbOomQuery(session, sqlString);
}
public DbOomQuery(String sqlString) {
super(sqlString);
}
public static DbOomQuery query(String sqlString) {
return new DbOomQuery(sqlString);
}
// ---------------------------------------------------------------- sqlgen ctors
protected DbSqlGenerator sqlgen;
public DbOomQuery(Connection conn, DbSqlGenerator sqlgen) {
super(conn, sqlgen.generateQuery());
this.sqlgen = sqlgen;
}
public static DbOomQuery query(Connection conn, DbSqlGenerator sqlgen) {
return new DbOomQuery(conn, sqlgen);
}
public DbOomQuery(DbSession session, DbSqlGenerator sqlgen) {
super(session, sqlgen.generateQuery());
this.sqlgen = sqlgen;
}
public static DbOomQuery query(DbSession session, DbSqlGenerator sqlgen) {
return new DbOomQuery(session, sqlgen);
}
public DbOomQuery(DbSqlGenerator sqlgen) {
super(sqlgen.generateQuery());
this.sqlgen = sqlgen;
}
public static DbOomQuery query(DbSqlGenerator sqlgen) {
return new DbOomQuery(sqlgen);
}
// ---------------------------------------------------------------- initialization
protected DbOomManager dbOomManager = DbOomManager.getInstance();
/**
* Returns used ORM manager.
*/
public DbOomManager getManager() {
return dbOomManager;
}
/**
* Prepares the query after initialization. Besides default work, it checks if sql generator
* is used, and if so, generator hints and query parameters will be used for this query.
* Note regarding hints: since hints can be added manually, generators hints will be ignored
* if there exists some manually set hints.
*/
@Override
protected void prepareQuery() {
super.prepareQuery();
if (sqlgen == null) {
return;
}
if (hints == null) {
String[] joinHints = sqlgen.getJoinHints();
if (joinHints != null) {
withHints(joinHints);
}
}
// insert parameters
Map parameters = sqlgen.getQueryParameters();
if (parameters == null) {
return;
}
for (Map.Entry entry : parameters.entrySet()) {
String paramName = entry.getKey();
ParameterValue param = entry.getValue();
DbEntityColumnDescriptor dec = param.getColumnDescriptor();
if (dec == null) {
setObject(paramName, param.getValue());
} else {
resolveColumnDbSqlType(connection, dec);
setObject(paramName, param.getValue(), dec.getSqlTypeClass(), dec.getDbSqlType());
}
}
}
/**
* Resolves column db sql type and populates it in column descriptor if missing.
*/
protected void resolveColumnDbSqlType(Connection connection, DbEntityColumnDescriptor dec) {
if (dec.dbSqlType != SqlType.DB_SQLTYPE_UNKNOWN) {
return;
}
ResultSet rs = null;
DbEntityDescriptor ded = dec.getDbEntityDescriptor();
try {
DatabaseMetaData dmd = connection.getMetaData();
rs = dmd.getColumns(null, ded.getSchemaName(), ded.getTableName(), dec.getColumnName());
if (rs.next()) {
dec.dbSqlType = rs.getInt("DATA_TYPE");
} else {
dec.dbSqlType = SqlType.DB_SQLTYPE_NOT_AVAILABLE;
if (log.isWarnEnabled()) {
log.warn("Column SQL type not available: " + ded.toString() + '.' + dec.getColumnName());
}
}
} catch (SQLException sex) {
dec.dbSqlType = SqlType.DB_SQLTYPE_NOT_AVAILABLE;
if (log.isWarnEnabled()) {
log.warn("Column SQL type not resolved: " + ded.toString() + '.' + dec.getColumnName(), sex);
}
} finally {
DbUtil.close(rs);
}
}
// ---------------------------------------------------------------- join hints
protected String[] hints;
protected JoinHintResolver hintResolver = dbOomManager.getHintResolver();
/**
* Specifies hints for the query. Provided string is
* split on ',' separator.
*/
public DbOomQuery withHints(String hint) {
this.hints = StringUtil.splitc(hint, ',');
return this;
}
/**
* Specifies multiple hints for the query.
*/
public DbOomQuery withHints(String... hints) {
this.hints = hints;
return this;
}
/**
* Prepares a row (array of rows mapped object) using hints.
* Returns either single object or objects array.
*/
protected Object resolveRowResults(Object[] row) {
row = hintResolver.join(row, hints);
return row.length == 1 ? row[0] : row;
}
// ---------------------------------------------------------------- result set
protected boolean cacheEntities = dbOomManager.isCacheEntitiesInResultSet();
/**
* Defines if entities should be cached in {@link ResultSetMapper}.
* Overrides default value in {@link DbOomManager}.
*/
public DbOomQuery cacheEntities(boolean cacheEntities) {
this.cacheEntities = cacheEntities;
return this;
}
/**
* Executes the query and returns {@link #createResultSetMapper(java.sql.ResultSet) builded ResultSet mapper}.
*/
protected ResultSetMapper executeAndBuildResultSetMapper() {
ResultSet resultSet = execute();
return createResultSetMapper(resultSet);
}
/**
* Factory for result sets mapper.
*/
protected ResultSetMapper createResultSetMapper(ResultSet resultSet) {
Map columnAliases = sqlgen != null ? sqlgen.getColumnData() : null;
return new DefaultResultSetMapper(resultSet, columnAliases, cacheEntities, this);
}
// ---------------------------------------------------------------- db list
protected boolean entityAwareMode = dbOomManager.isEntityAwareMode();
/**
* Defines entity-aware mode for entities tracking in result collection.
* @see DbOomManager#setEntityAwareMode(boolean)
*/
public DbOomQuery entityAwareMode(boolean entityAware) {
if (entityAware) {
this.cacheEntities = true;
}
this.entityAwareMode = entityAware;
return this;
}
// ---------------------------------------------------------------- iterator
public Iterator iterate(Class... types) {
return iterate(types, autoClose);
}
public Iterator iterate() {
return iterate(null, autoClose);
}
protected Iterator iterate(Class[] types, boolean close) {
return new DbListIterator<>(this, types, close);
}
// ---------------------------------------------------------------- list
public List list(Class... types) {
return list(types, -1, autoClose);
}
public List list() {
return list(null, -1, autoClose);
}
public List list(int max, Class... types) {
return list(types, max, autoClose);
}
public List list(int max) {
return list(null, max, autoClose);
}
/**
* Iterates result set, maps rows to classes and populates resulting array list.
* @param types mapping types
* @param max max number of rows to collect, -1
for all
* @param close true
if query is closed at the end, otherwise false
.
* @return list of mapped entities or array of entities
*/
@SuppressWarnings({"unchecked"})
protected List list(Class[] types, int max, boolean close) {
List result = new ArrayList<>(initialCollectionSize(max));
ResultSetMapper rsm = executeAndBuildResultSetMapper();
if (types == null) {
types = rsm.resolveTables();
}
Object previousElement = null;
while (rsm.next()) {
Object[] objects = rsm.parseObjects(types);
Object row = resolveRowResults(objects);
int size = result.size();
T newElement = (T) row;
if (entityAwareMode && size > 0) {
if (previousElement != null && newElement != null) {
boolean equals;
if (newElement.getClass().isArray()) {
equals = Arrays.equals((Object[]) previousElement, (Object[]) newElement);
} else {
equals = previousElement.equals(newElement);
}
if (equals) {
continue;
}
}
}
if (size == max) {
break;
}
result.add(newElement);
previousElement = newElement;
}
close(rsm, close);
return result;
}
// ---------------------------------------------------------------- set
public Set listSet(Class... types) {
return listSet(types, -1, autoClose);
}
public Set listSet() {
return listSet(null, -1, autoClose);
}
public Set listSet(int max, Class... types) {
return listSet(types, max, autoClose);
}
public Set listSet(int max) {
return listSet(null, max, autoClose);
}
@SuppressWarnings({"unchecked"})
protected Set listSet(Class[] types, int max, boolean close) {
Set result = new LinkedHashSet<>(initialCollectionSize(max));
ResultSetMapper rsm = executeAndBuildResultSetMapper();
if (types == null) {
types = rsm.resolveTables();
}
Object previousElement = null;
while (rsm.next()) {
Object[] objects = rsm.parseObjects(types);
Object row = resolveRowResults(objects);
int size = result.size();
T newElement = (T) row;
if (entityAwareMode && size > 0) {
if (previousElement != null && newElement != null) {
boolean equals;
if (newElement.getClass().isArray()) {
equals = Arrays.equals((Object[]) previousElement, (Object[]) newElement);
} else {
equals = previousElement.equals(newElement);
}
if (equals) {
continue;
}
}
}
if (size == max) {
break;
}
result.add(newElement);
previousElement = newElement;
}
close(rsm, close);
return result;
}
// ---------------------------------------------------------------- find
public T find(Class... types) {
return find(types, autoClose, null);
}
public T find() {
return find(null, autoClose, null);
}
protected T find(Class[] types, boolean close, ResultSet resultSet) {
if (resultSet == null) {
resultSet = execute();
}
ResultSetMapper rsm = createResultSetMapper(resultSet);
Iterator iterator = new DbListIterator<>(this, types, rsm, false);
T result = null;
if (iterator.hasNext()) {
result = iterator.next();
}
close(rsm, close);
return result;
}
// ---------------------------------------------------------------- generated columns
/**
* Finds generated key column of given type.
*/
public T findGeneratedKey(Class type) {
return find(new Class[] {type}, false, getGeneratedColumns());
}
/**
* Finds generated columns.
*/
public Object findGeneratedColumns(Class... types) {
return find(types, false, getGeneratedColumns());
}
// ---------------------------------------------------------------- util
/**
* {@inheritDoc}
*/
@Override
public DbOomQuery autoClose() {
super.autoClose();
return this;
}
/**
* Closes results set or whole query.
*/
protected void close(ResultSetMapper rsm, boolean closeQuery) {
if (closeQuery) {
close();
} else {
closeResultSet(rsm.getResultSet());
}
}
}