
redora.service.ServiceBase Maven / Gradle / Ivy
/*
* 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 {
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;
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;
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;
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(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 String sql, @NotNull 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 String sql) throws QueryException {
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 String sql) throws SQLException {
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 String tableName) throws QueryException {
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 String tableName, @NotNull String keyName) throws QueryException {
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 String sql) throws PagingException {
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;
}
public static void close(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