
jodd.db.DbQueryBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jodd-db Show documentation
Show all versions of jodd-db Show documentation
Jodd DB is efficient and thin database facade; DbOom is convenient database object mapper.
// Copyright (c) 2003-2012, Jodd Team (jodd.org). All Rights Reserved.
package jodd.db;
import jodd.db.debug.LoggablePreparedStatementFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import java.util.HashSet;
/**
* Support for {@link DbQuery} holds all configuration, initialization and the execution code.
*/
abstract class DbQueryBase {
private static final Logger log = LoggerFactory.getLogger(DbQueryBase.class);
// ---------------------------------------------------------------- ctor
protected final DbManager dbManager = DbManager.getInstance();
protected DbQueryBase() {
this.forcePreparedStatement = dbManager.forcePreparedStatement;
this.type = dbManager.type;
this.concurrencyType = dbManager.concurrencyType;
this.holdability = dbManager.holdability;
this.debug = dbManager.debug;
this.fetchSize = dbManager.fetchSize;
this.maxRows = dbManager.maxRows;
}
// ---------------------------------------------------------------- query states
public static final int QUERY_CREATED = 1;
public static final int QUERY_INITIALIZED = 2;
public static final int QUERY_CLOSED = 3;
protected int queryState = QUERY_CREATED;
/**
* Returns query state.
*/
public int getQueryState() {
return queryState;
}
/**
* Checks if query is not closed and throws an exception if it is.
*/
protected void checkNotClosed() {
if (queryState == QUERY_CLOSED) {
throw new DbSqlException("Query is closed. Operation may be performed only on active queries.");
}
}
/**
* Checks if query is created (and not yet initialized or closed) and throws an exception if it is not.
*/
protected void checkCreated() {
if (queryState != QUERY_CREATED) {
String message = (queryState == QUERY_INITIALIZED ?
"Query is already initialized." : "Query is closed.");
throw new DbSqlException(message + " Operation may be performed only on created queries.");
}
}
/**
* Checks if query is initialized and throws an exception if it is not.
*/
protected void checkInitialized() {
if (queryState != QUERY_INITIALIZED) {
String message = (queryState == QUERY_CREATED ?
"Query is just created and not yet initialized." : "Query is closed.");
throw new DbSqlException(message + " Operation may be performed only on initialized queries.");
}
}
/**
* Returns true
if query is closed.
*/
public boolean isClosed() {
return queryState == QUERY_CLOSED;
}
/**
* Returns true
if query is active: created and possibly initialized.
* Opened query may be not initialized.
*/
public boolean isActive() {
return queryState < QUERY_CLOSED;
}
/**
* Returns true
if query is initialized. Initialized query is the one that has
* created JDBC statements.
*/
public boolean isInitialized() {
return queryState == QUERY_INITIALIZED;
}
// ---------------------------------------------------------------- input
protected Connection connection;
protected DbSession session;
protected String sqlString;
/**
* Returns used {@link DbSession}.
*/
public DbSession getSession() {
return session;
}
// ---------------------------------------------------------------- attributes
protected Statement statement;
protected PreparedStatement preparedStatement;
protected Set resultSets;
protected DbQueryParser query;
/**
* Stores result set.
*/
protected void saveResultSet(ResultSet rs) {
if (resultSets == null) {
resultSets = new HashSet();
}
resultSets.add(rs);
}
// ---------------------------------------------------------------- configuration
/**
* If set to true
all created statements will be prepared.
*/
protected boolean forcePreparedStatement;
/**
* Forces creation of prepared statements.
*/
public void forcePreparedStatement() {
checkCreated();
forcePreparedStatement = true;
}
// ---------------------------------------------------------------- initialization
/**
* Initializes the query if not already initialized.
* Usually, user doesn't have to invoke it at all, since it will
* be called before all methods that deals directly with JDBC statement.
* Throws an exception if called on closed query.
*/
public final void init() {
checkNotClosed();
if (queryState == QUERY_INITIALIZED) {
return;
}
initializeJdbc();
queryState = QUERY_INITIALIZED;
prepareQuery();
}
/**
* Initializes session. When not specified (i.e. is null
),
* session is fetched from session provider.
*/
protected void initSession(DbSession session) {
if (session != null) {
this.session = session;
return;
}
DbSessionProvider dbSessionProvider = dbManager.sessionProvider;
if (dbSessionProvider == null) {
throw new DbSqlException("Session provider not available.");
}
this.session = dbSessionProvider.getDbSession();
}
/**
* Performs JDBC initialization of the query. Obtains connection, parses the SQL query string
* and creates statements. Initialization is performed only once, when switching to initialized state.
*/
@SuppressWarnings("MagicConstant")
protected void initializeJdbc() {
// connection
if (connection == null) {
initSession(session);
connection = session.getConnection();
}
this.query = new DbQueryParser(sqlString);
// statement
if ((forcePreparedStatement == false) && (query.prepared == false)) {
try {
if (holdability != DEFAULT_HOLDABILITY) {
statement = connection.createStatement(type, concurrencyType, holdability);
} else {
statement = connection.createStatement(type, concurrencyType);
}
} catch (SQLException sex) {
throw new DbSqlException("Unable to create statement.", sex);
}
return;
}
// prepared statement
try {
if (debug == true) {
if (generatedColumns != null) {
if (generatedColumns.length == 0) {
statement = LoggablePreparedStatementFactory.create(connection, query.sql, Statement.RETURN_GENERATED_KEYS);
} else {
statement = LoggablePreparedStatementFactory.create(connection, query.sql, generatedColumns);
}
} else {
if (holdability != DEFAULT_HOLDABILITY) {
statement = LoggablePreparedStatementFactory.create(connection, query.sql, type, concurrencyType, holdability);
} else {
statement = LoggablePreparedStatementFactory.create(connection, query.sql, type, concurrencyType);
}
}
} else {
if (generatedColumns != null) {
if (generatedColumns.length == 0) {
statement = connection.prepareStatement(query.sql, Statement.RETURN_GENERATED_KEYS);
} else {
statement = connection.prepareStatement(query.sql, generatedColumns);
}
} else {
if (holdability != DEFAULT_HOLDABILITY) {
statement = connection.prepareStatement(query.sql, type, concurrencyType, holdability);
} else {
statement = connection.prepareStatement(query.sql, type, concurrencyType);
}
}
}
} catch (SQLException sex) {
throw new DbSqlException("Unable to create prepared statement.", sex);
}
preparedStatement = (PreparedStatement) statement;
}
/**
* Prepares the query just after the initialization.
* Query is fully set and ready.
*/
protected void prepareQuery() {
if (fetchSize != 0) {
setFetchSize(fetchSize);
}
if (maxRows != 0) {
setMaxRows(maxRows);
}
}
// ---------------------------------------------------------------- close
/**
* Closes all result sets opened by this query. Query remains active.
*/
private List closeQueryResultSets() {
List sexs = null;
if (resultSets != null) {
for (ResultSet rs : resultSets) {
try {
rs.close();
} catch (SQLException sex) {
if (sexs == null) {
sexs = new ArrayList();
}
sexs.add(sex);
} finally {
totalOpenResultSetCount--;
}
}
resultSets.clear();
resultSets = null;
}
return sexs;
}
/**
* Closes all result sets created by this query. Query remains active.
*/
public void closeAllResultSets() {
List sexs = closeQueryResultSets();
if (sexs != null) {
throw new DbSqlException("Unable to close associated ResultSets.", sexs);
}
}
/**
* Closes all assigned result sets and then closes the query. Query becomes closed.
*/
protected List closeQuery() {
List sexs = closeQueryResultSets();
if (statement != null) {
try {
statement.close();
} catch (SQLException sex) {
if (sexs == null) {
sexs = new ArrayList();
}
sexs.add(sex);
}
statement = null;
}
query = null;
queryState = QUERY_CLOSED;
return sexs;
}
/**
* Closes the query and all created results sets and detaches itself from the session.
*/
@SuppressWarnings({"ClassReferencesSubclass"})
public void close() {
List sexs = closeQuery();
connection = null;
if (this.session != null) {
this.session.detachQuery(this);
}
if (sexs != null) {
throw new DbSqlException("Unable to close query.", sexs);
}
}
/**
* Closes single result set that was created by this query, It is not necessary to close result sets
* explicitly, since {@link DbQueryBase#close()} method closes all created result sets.
* Query remains active.
*/
public void closeResultSet(ResultSet rs) {
if (rs == null) {
return;
}
if (resultSets.remove(rs) == false) {
throw new DbSqlException("Provided ResultSet is not created by this query and should be not closed in this way.");
}
try {
rs.close();
} catch (SQLException sex) {
throw new DbSqlException("Unable to close the result set.", sex);
} finally {
totalOpenResultSetCount--;
}
}
// ---------------------------------------------------------------- result set type
/**
* @see ResultSet#TYPE_FORWARD_ONLY
*/
public static final int TYPE_FORWARD_ONLY = ResultSet.TYPE_FORWARD_ONLY;
/**
* @see ResultSet#TYPE_SCROLL_SENSITIVE
*/
public static final int TYPE_SCROLL_SENSITIVE = ResultSet.TYPE_SCROLL_SENSITIVE;
/**
* @see ResultSet#TYPE_SCROLL_INSENSITIVE
*/
public static final int TYPE_SCROLL_INSENSITIVE = ResultSet.TYPE_SCROLL_INSENSITIVE;
protected int type;
public int getType() {
return type;
}
public void setType(int type) {
checkCreated();
this.type = type;
}
public void typeForwardOnly() {
setType(TYPE_FORWARD_ONLY);
}
public void typeScrollSensitive() {
setType(TYPE_SCROLL_SENSITIVE);
}
public void typeScrollInsensitive() {
setType(TYPE_SCROLL_SENSITIVE);
}
// ---------------------------------------------------------------- concurrency
/**
* @see ResultSet#CONCUR_READ_ONLY
*/
public static final int CONCUR_READ_ONLY = ResultSet.CONCUR_READ_ONLY;
/**
* @see ResultSet#CONCUR_UPDATABLE
*/
public static final int CONCUR_UPDATABLE = ResultSet.CONCUR_UPDATABLE;
protected int concurrencyType;
public int getConcurrencyType() {
return concurrencyType;
}
public void setConcurrencyType(int concurrencyType) {
checkCreated();
this.concurrencyType = concurrencyType;
}
public void concurReadOnly() {
setConcurrencyType(CONCUR_READ_ONLY);
}
public void concurUpdatable() {
setConcurrencyType(CONCUR_UPDATABLE);
}
// ---------------------------------------------------------------- holdability
/**
* Default holdability. JDBC specification does not specifies default value for holdability.
*/
public static final int DEFAULT_HOLDABILITY = -1;
/**
* @see ResultSet#CLOSE_CURSORS_AT_COMMIT
*/
public static final int CLOSE_CURSORS_AT_COMMIT = ResultSet.CLOSE_CURSORS_AT_COMMIT;
/**
* @see ResultSet#HOLD_CURSORS_OVER_COMMIT
*/
public static final int HOLD_CURSORS_OVER_COMMIT = ResultSet.HOLD_CURSORS_OVER_COMMIT;
protected int holdability;
public int getHoldability() {
return holdability;
}
public void setHoldability(int holdability) {
checkCreated();
this.holdability = holdability;
}
public void holdCursorsOverCommit() {
setHoldability(HOLD_CURSORS_OVER_COMMIT);
}
public void closeCursorsAtCommit() {
setHoldability(CLOSE_CURSORS_AT_COMMIT);
}
// ---------------------------------------------------------------- debug mode
protected boolean debug;
public boolean isInDebugMode() {
return debug;
}
public void setDebug(boolean debug) {
checkCreated();
this.debug = debug;
}
public void setDebugMode() {
setDebug(true);
}
// ---------------------------------------------------------------- generated keys
protected String[] generatedColumns;
/**
* Returns generated column names.
*/
public String[] getGeneratedColumnNames() {
return generatedColumns;
}
/**
* Specifies columns which values will be generated by database.
*/
public void setGeneratedColumns(String... columns) {
checkCreated();
generatedColumns = columns;
}
/**
* Specifies that database will generate some columns values,
* usually the single id.
*/
public void setGeneratedKey() {
setGeneratedColumns();
}
/**
* Resets creating generated columns.
*/
public void resetGeneratedColumns() {
checkCreated();
generatedColumns = null;
}
// ---------------------------------------------------------------- performance hints
protected int fetchSize;
/**
* Returns fetch size.
* @see #setFetchSize(int)
*/
public int getFetchSize() {
return fetchSize;
}
/**
* Gives the JDBC driver a hint as to the number of rows that should be fetched from the database when
* more rows are needed. The number of rows specified affects only result sets created using this statement.
* If the value specified is zero, then the hint is ignored. The default value is zero.
* @see Statement#setFetchSize(int)
*/
public void setFetchSize(int rows) {
checkNotClosed();
this.fetchSize = rows;
if (statement != null) {
try {
statement.setFetchSize(fetchSize);
} catch (SQLException sex) {
throw new DbSqlException("Unable to set fetch size of: " + fetchSize, sex);
}
}
}
protected int maxRows;
/**
* Returns max rows.
* @see #setMaxRows(int)
*/
public int getMaxRows() {
return maxRows;
}
/**
* Sets the limit for the maximum number of rows that any ResultSet object can contain to the given number.
* If the limit is exceeded, the excess rows are silently dropped. Zero means there is no limit.
* @see Statement#setMaxRows(int)
*/
public void setMaxRows(int maxRows) {
checkNotClosed();
this.maxRows = maxRows;
if (statement != null) {
try {
statement.setMaxRows(maxRows);
} catch (SQLException sex) {
throw new DbSqlException("Unable to set max rows of: " + maxRows, sex);
}
}
}
// ---------------------------------------------------------------- execute
protected long start;
protected long elapsed = -1;
/**
* Returns query execution elapsed time in milliseconds.
* Returns -1
if query is still not executed.
*/
public long getExecutionTime() {
return elapsed;
}
/**
* Executes the query. If this method is invoked at least once, the query or
* all created ResultSets must be explicitly closed at the end of query usage.
* This can be done explicitly by calling {@link DbQueryBase#closeResultSet(ResultSet)}
* оr implicitly, during {@link DbQueryBase#close()}.
* @see Statement#execute(String)
*/
public ResultSet execute() {
start = System.currentTimeMillis();
init();
ResultSet rs = null;
if (log.isDebugEnabled()) {
log.debug("Executing statement: " + getQueryString());
}
try {
if (preparedStatement == null) {
rs = statement.executeQuery(query.sql);
} else {
rs = preparedStatement.executeQuery();
}
rs.setFetchSize(fetchSize);
} catch (SQLException sex) {
DbUtil.close(rs);
throw new DbSqlException("Unable to execute query.", sex);
}
saveResultSet(rs);
totalOpenResultSetCount++;
elapsed = System.currentTimeMillis() - start;
if (log.isDebugEnabled()) {
log.debug("execution time: " + elapsed + "ms");
}
return rs;
}
/**
* Executes UPDATE, INSERT or DELETE queries. Query is not closed afterwards.
* @see Statement#executeUpdate(String)
*/
public int executeUpdate() {
return executeUpdate(false);
}
/**
* Executes UPDATE, INSERT or DELETE queries and closes query afterwards.
* @see Statement#executeUpdate(String)
*/
public int executeUpdateAndClose() {
return executeUpdate(true);
}
/**
* Executes UPDATE, INSERT or DELETE queries and optionally closes the query.
* @see Statement#executeUpdate(String)
*/
protected int executeUpdate(boolean closeQuery) {
start = System.currentTimeMillis();
init();
int result;
if (log.isDebugEnabled()) {
log.debug("Executing update: " + getQueryString());
}
try {
if (preparedStatement == null) {
if (generatedColumns != null) {
if (generatedColumns.length == 0) {
result = statement.executeUpdate(query.sql, Statement.RETURN_GENERATED_KEYS);
} else {
result = statement.executeUpdate(query.sql, generatedColumns);
}
} else {
result = statement.executeUpdate(query.sql);
}
} else {
result = preparedStatement.executeUpdate();
}
} catch (SQLException sex) {
throw new DbSqlException("Unable to execute the query.", sex);
}
if (closeQuery) {
close();
}
elapsed = System.currentTimeMillis() - start;
if (log.isDebugEnabled()) {
log.debug("execution time: " + elapsed + "ms");
}
return result;
}
/**
* Special execute() for 'select count(*)' queries. Query is not closed after the execution.
* It doesn't check if query is really a count query. If result set returns zero rows, (what is very unlikely),
* it returns -1
.
*/
public long executeCount() {
return executeCount(false);
}
/**
* Executes count queries and closes afterwards.
*/
public long executeCountAndClose() {
return executeCount(true);
}
/**
* Executes count queries and optionally closes query afterwards.
*/
protected long executeCount(boolean close) {
start = System.currentTimeMillis();
init();
ResultSet rs = null;
if (log.isDebugEnabled()) {
log.debug("Executing prepared count: " + getQueryString());
}
try {
if (preparedStatement == null) {
rs = statement.executeQuery(query.sql);
} else {
rs = preparedStatement.executeQuery();
}
long firstLong = DbUtil.getFirstLong(rs);
elapsed = System.currentTimeMillis() - start;
if (log.isDebugEnabled()) {
log.debug("execution time: " + elapsed + "ms");
}
return firstLong;
} catch (SQLException sex) {
throw new DbSqlException("Unable to execute count query.", sex);
} finally {
DbUtil.close(rs);
if (close) {
close();
}
}
}
// ---------------------------------------------------------------- generated keys
/**
* Returns generated columns.
*/
public ResultSet getGeneratedColumns() {
checkInitialized();
if (generatedColumns == null) {
throw new DbSqlException("No column is specified as auto-generated.");
}
ResultSet rs;
try {
rs = statement.getGeneratedKeys();
} catch (SQLException sex) {
throw new DbSqlException("Unable to return generated keys.", sex);
}
saveResultSet(rs);
totalOpenResultSetCount++;
return rs;
}
/**
* Returns generated key i.e. first generated column as long
.
*/
public long getGeneratedKey() {
checkInitialized();
ResultSet rs = getGeneratedColumns();
try {
return DbUtil.getFirstLong(rs);
} catch (SQLException sex) {
throw new DbSqlException("Unable to get generated key as long value.", sex);
} finally {
DbUtil.close(rs);
resultSets.remove(rs);
totalOpenResultSetCount--;
}
}
// ---------------------------------------------------------------- query string
/**
* Returns query SQL string. For prepared statements, returned sql string with quick-and-dirty replaced values.
*/
public String getQueryString() {
if ((preparedStatement != null) && debug) {
return LoggablePreparedStatementFactory.getQueryString(preparedStatement);
}
if (query != null) {
return query.sql;
}
return sqlString;
}
/**
* @see #getQueryString()
*/
@Override
public String toString() {
return getQueryString();
}
// ---------------------------------------------------------------- statistics
protected static int totalOpenResultSetCount;
/**
* Returns total number of open result sets.
* @see #getOpenResultSetCount()
*/
public static int getTotalOpenResultSetCount() {
return totalOpenResultSetCount;
}
/**
* Returns number of created result sets that are still not explicitly closed.
* @see #getTotalOpenResultSetCount()
*/
public int getOpenResultSetCount() {
return resultSets == null ? 0 : resultSets.size();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy