
redora.service.ServiceBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of core Show documentation
Show all versions of core Show documentation
Implementation of the API and core database access.
The newest version!
/*
* Copyright 2009-2010 Nanjing RedOrange ltd (http://www.red-orange.cn)
*
* 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 redora.service;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import redora.api.fetch.Mode;
import redora.api.fetch.Page;
import redora.db.DatabaseFactory;
import redora.db.Statement;
import redora.exceptions.ConnectException;
import redora.exceptions.PagingException;
import redora.exceptions.PersistException;
import redora.exceptions.QueryException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.logging.Logger;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Level.INFO;
import static redora.db.DatabaseFactory.getDBProperty;
/**
* Base class for the java service entry classes providing the database
* connection management. This class will cache all prepared statements and it
* will maintain a database connection. With the makePreparedStatement function
* a hash in the SQL query determines if a prepared statement is already in the
* cache.
*
* @author Nanjing RedOrange (http://www.red-orange.cn)
*/
public abstract class ServiceBase {
private static final transient Logger l = Logger.getLogger("redora.ServiceBase");
protected Statement st;
boolean closeStatement = true;
boolean closed = false;
final String schema;
/** If true, the system will return to auto commit when an transaction is ended.*/
public boolean defaultAutoCommit;
/** true if you are in a Transaction @see #beginTransaction */
public boolean inTransaction = false;
/** Last time in System.currentTime when this service was invoked. */
public long last;
protected ServiceBase(@NotNull String schema) throws ConnectException {
this.schema = schema;
st = DatabaseFactory.statement(schema);
last = System.currentTimeMillis();
defaultAutoCommit = "true".equals(getDBProperty(schema, "autoCommit"));
}
public ServiceBase(@NotNull final ServiceBase chain, @NotNull String schema) {
this.schema = schema;
st = chain.st;
closeStatement = false; //chain master handles this
defaultAutoCommit = false; //By setting this to false, this service will not mangle with the transaction settings
}
public void beginTransaction() throws PersistException {
inTransaction = true;
last = System.currentTimeMillis();
try {
if (defaultAutoCommit) {
st.con.con.setAutoCommit(false);
}
} catch (SQLException e) {
reset(l);
throw new PersistException("Failed to start transaction", e);
}
}
public void commit() throws PersistException {
inTransaction = false;
last = System.currentTimeMillis();
try {
st.con.con.commit();
if (defaultAutoCommit) {
st.con.con.setAutoCommit(true);
}
} catch (SQLException e) {
reset(l);
throw new PersistException("Failed to commit transaction", e);
}
}
public void rollback() throws PersistException {
inTransaction = false;
last = System.currentTimeMillis();
try {
st.con.con.rollback();
if (defaultAutoCommit) {
st.con.con.setAutoCommit(true);
}
} catch (SQLException e) {
reset(l);
throw new PersistException("Failed to rollback transaction", e);
}
}
/**
* A 'final' resort method that closes the statement and connection
* and re-initializes it. Use reset in the catch blocks where you handle
* exceptions that should not occur. Using reset(0 in these cases makes
* the application more robust.
*/
public void reset(@NotNull final Logger parent) {
l.log(INFO, "Attempting to reset the connection, from {0}", parent.getName());
inTransaction = false;
last = System.currentTimeMillis();
if (closeStatement) {
st.close();
try {
st = DatabaseFactory.statement(schema);
} catch (ConnectException e) {
l.log(SEVERE, "Failed to reset", e);
}
}
}
/**
* Normally you do not need this use ServiceFactory.close(Service) instead.
* This method will release any open statement and connection related to this
* service.
*/
public void close() {
inTransaction = false;
if (closeStatement) {
st.close();
}
closed = true;
}
@Override
public void finalize() throws Throwable {
if (!closed && closeStatement) {
l.log(INFO, "You might want to close services yourself, i will do it now for you");
close();
}
super.finalize();
}
/**
* Adds paging info to the given SQL. This means for example the statement
* 'select * from myTable' will be enhanced to something like:
* 'select * from myTable limit 20, 30'
*
* It will only add it when Page has Mode.Page. If a result size was not established
* it will use given SQL to count the number of records. So use this after all
* the query parameters are set.
*
* @param sql (Mandatory) The SQL to which you want to add the LIMIT statement
* @param page (Mandatory) The Page object managing the paging.
* @throws PagingException This method can run a count query on the DB, when that fails this is the exception.
* @return An SQL statement suffixed with limit x,y
*/
@NotNull
public String preparePage(@NotNull final String sql, @NotNull final Page page) throws PagingException {
if (page.getMode() == Mode.Page) {
if (page.resultCount() == -1) {
page.initPageMode(getResultSize(sql));
}
return new StringBuilder(sql).append(" limit ").append(page.position() * page.pageSize())
.append(',').append(page.pageSize()).toString();
}
return sql;
}
public void execute(@NotNull final String sql) throws QueryException {
last = System.currentTimeMillis();
try {
st.st.execute(sql);
} catch (SQLException e) {
l.log(SEVERE, "Failed to perform query: " + sql, e);
reset(l);
throw new QueryException("Failed to perform query: " + sql, e);
}
}
public ResultSet sqlQuery(@NotNull final String sql) throws SQLException {
last = System.currentTimeMillis();
return st.st.executeQuery(sql);
}
/**
* Looks up the database meta data to determine if the table exists.
*
* @param tableName (Mandatory) Any table name you wish to see if it exists.
* @return True if the table exists.
* @throws QueryException When something fails in the query for the existence of tableName.
*/
public boolean tableExists(@NotNull final String tableName) throws QueryException {
last = System.currentTimeMillis();
ResultSet rs = null;
try {
rs = st.con.con.getMetaData().getTables(null, null, tableName, null);
return rs.next();
} catch (SQLException e) {
l.log(SEVERE, "Failed to open meta data to find table " + tableName, e);
reset(l);
throw new QueryException("Failed to open meta data to find table " + tableName, e);
} finally {
close(rs);
}
}
/**
* Retrieves the last attribute name on which the unique key is defined.
*
* @param tableName (Mandatory) table name
* @param keyName (Mandatory) unique index name
* @return null, or the attribute name of the index
* @throws QueryException When the meta data could not be retrieved.
*/
@Nullable
public String uniqueKeyAttribute(@NotNull final String tableName, @NotNull final String keyName)
throws QueryException {
last = System.currentTimeMillis();
ResultSet rs = null;
try {
rs = st.con.con.getMetaData().getIndexInfo(null, null, tableName, true, true);
while (rs.next()) {
if (keyName.equalsIgnoreCase(rs.getString("INDEX_NAME"))) {
return rs.getString("COLUMN_NAME");
//TODO now it gets the last value: maybe it should get the first.
}
}
} catch (SQLException e) {
reset(l);
throw new QueryException("Cannot retrieve Index: " + keyName, e);
} finally {
close(rs);
}
return null;
}
/**
* Get the total size of the query size for the given SQL.
*
* @param sql (Mandatory) The SQL sentence from which you want to determine the # of records it will produce.
* @return The count(1) result of given query.
* @throws PagingException When i cannot connect to the DB or the count(1) query fails
*/
public int getResultSize(@NotNull final String sql) throws PagingException {
last = System.currentTimeMillis();
String SQL = sql.replace(sql.substring(sql.indexOf("select") + 7, sql.indexOf("from") - 1), "count(1)");
ResultSet rs = null;
try {
rs = st.st.executeQuery(SQL);
if (rs.next()) {
return rs.getInt(1);
}
} catch (SQLException e) {
l.log(SEVERE, "Failed to retrieve query size: " + SQL, e);
reset(l);
throw new PagingException("Failed to retrieve query size: " + SQL, e);
} finally {
close(rs);
}
return 0;
}
/**
* Safely closes ResultSet, exceptions will be logged as warning.
* @param rs (Optional) the to be closed resultset
*/
public static void close(@Nullable final ResultSet rs) {
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
l.log(WARNING, "Failed to close result set", e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy