blob: b7b5cd7694b041c77ebc7072df81587e95f1d84e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.empire.spring;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.sql.DataSource;
import org.apache.empire.db.DBColumnExpr;
import org.apache.empire.db.DBCommand;
import org.apache.empire.db.DBReader;
import org.apache.empire.db.DBRecord;
import org.apache.empire.db.DBRecordData;
import org.apache.empire.db.DBRowSet;
import org.apache.empire.db.DBTable;
import org.apache.empire.db.exceptions.RecordNotFoundException;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.Assert;
/**
* This class simplifies database access methods with Empire. Database queries
* are executed with the help of callback interfaces, while hides connection
* handling from the developer. Only the query logic has to be implemented and
* leave getting and releasing the connection to the template.
*
* Some methods (delete, update) delegates to DBRecord, DBDatabase and DBCommand
* methods while hiding connection handling from developers.
*
* This class is working with a wrapped JdbcTemplate, so either jdbcTemplate or
* dataSource must be set.
*
* Allows customization of creating DBReader and DBRecord instances through
* ObjectFactories.
*
* This class is based on {@link org.springframework.jdbc.core.JdbcTemplate}.
*
*
*/
public class EmpireTemplate implements InitializingBean {
/** Wrapped JdbcTemplate instance */
private JdbcTemplate jdbcTemplate;
/** Factory to create a new DBReader instance, by default returns DBReader */
private ObjectFactory<DBReader> readerFactory = new ObjectFactory<DBReader>() {
@Override
public DBReader getObject() throws BeansException {
return new DBReader();
}
};
/** Factory to create a new DBRecord instance, by default returns DBRecord */
private ObjectFactory<DBRecord> recordFactory = new ObjectFactory<DBRecord>() {
@Override
public DBRecord getObject() throws BeansException {
return new DBRecord();
}
};
/** Default constructor */
public EmpireTemplate() {
super();
}
/**
* Setting a custom ObjectFactory to allow custom DBRecord create with
* newRecord().
*
* @param recordFactory
*/
public void setDBRecordFactory(ObjectFactory<DBRecord> recordFactory) {
this.recordFactory = recordFactory;
}
/**
* Setting a custom DBRecord class to use in newRecord().
*
* @param recordClass
* the class which extends DBRecord.class
*/
public void setDBRecordClass(final Class<? extends DBRecord> recordClass) {
this.recordFactory = new ObjectFactory<DBRecord>() {
@Override
public DBRecord getObject() throws BeansException {
try {
return recordClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
}
/**
* Setting a custom ObjectFactory to allow custom DBReaders to use in
* queries.
*
* @param readerFactory
*/
public void setDBReaderFactory(ObjectFactory<DBReader> readerFactory) {
this.readerFactory = readerFactory;
}
/**
* Setting a custom DBReader class to use in queries.
*
* @param readerClass
* the class which extends DBReader.class
*/
public void setDBReaderClass(final Class<? extends DBReader> readerClass) {
this.readerFactory = new ObjectFactory<DBReader>() {
@Override
public DBReader getObject() throws BeansException {
try {
return readerClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
};
}
/** Setting the wrapped JdbcTemplate */
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return this.jdbcTemplate;
}
/** Setting the datasource */
public final void setDataSource(DataSource dataSource) {
if (this.jdbcTemplate == null
|| dataSource != this.jdbcTemplate.getDataSource()) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.jdbcTemplate.afterPropertiesSet();
}
}
public void afterPropertiesSet() {
if (getJdbcTemplate() == null) {
throw new IllegalArgumentException(
"Property 'jdbcTemplate' is required, either jdbcTemplate or dataSource must be set.");
}
}
/**
* Executes a given DBCommand, mapping each row to a Java object via a
* DBRecordMapper.
*
* @param cmd
* the DBCommand to execute
* @param recordMapper
* the mapper which maps each DBRecordData to a Java object
* @return the extracted list
*/
public <K> List<K> query(final DBCommand cmd,
final DBRecordMapper<K> recordMapper) {
return query(cmd, new DbRecordMapperExtractor<K>(recordMapper));
}
/**
* Executes a given DBCommand, mapping a single row to a Java object using
* the provided DBRecordMapper.
*
* @param cmd
* the DBCommand to execute
* @param recordMapper
* the DBRecordMapper to map
* @return the single Object
* @throws IncorrectResultSizeDataAccessException
* if more than one result object has been found
*/
public <K> K queryForObject(final DBCommand cmd,
final DBRecordMapper<K> recordMapper) {
return DataAccessUtils.uniqueResult(query(cmd, recordMapper));
}
/**
* Executes a given DBCommand, mapping a single column to a Java object
* using on DBRecordData.getValue() method.
*
* @param cmd
* the DBCommand to execute
* @param col
* the column to map
* @return the extracted list
*/
public List<Object> queryForList(final DBCommand cmd, final DBColumnExpr col) {
class SingleValueMapper implements DBRecordMapper<Object> {
@Override
public Object mapRecord(DBRecordData record, int rowNum) {
return record.getValue(col);
}
}
return query(cmd, new SingleValueMapper());
}
/**
* Executes a given DBCommand, mapping a single column to a single Long. If
* the value in the database is null, defaultValue is returned.
*
* @param cmd
* the DBCommand to execute
* @param col
* the column to map
* @param defaultValue
* the value to return in case of the database value is null
* @return the single Long value
* @throws IncorrectResultSizeDataAccessException
* if more than one result object has been found
*/
public Long queryForLong(final DBCommand cmd, final DBColumnExpr col,
Long defaultValue) {
return DataAccessUtils.uniqueResult(queryForLongList(cmd, col,
defaultValue));
}
/**
* Executes a given DBCommand, extracting a single column to a List of Long.
* If the value in the database is null, defaultValue is added to the list.
*
* @param cmd
* the DBCommand to execute
* @param col
* the column to map
* @param defaultValue
* the value to return in case of the database value is null
* @return the extracted list
*/
public List<Long> queryForLongList(final DBCommand cmd,
final DBColumnExpr col, final Long defaultValue) {
class SingleLongMapper implements DBRecordMapper<Long> {
@Override
public Long mapRecord(DBRecordData record, int rowNum) {
return record.isNull(col) ? defaultValue : record.getLong(col);
}
}
return query(cmd, new SingleLongMapper());
}
/**
* Executes a given DBCommand, mapping a single column to a single Integer.
* If the value in the database is null, defaultValue is returned.
*
* @param cmd
* the DBCommand to execute
* @param col
* the column to map
* @param defaultValue
* the value to return in case of the database value is null
* @return the single Integer value
* @throws IncorrectResultSizeDataAccessException
* if more than one result object has been found
*/
public Integer queryForInteger(final DBCommand cmd, final DBColumnExpr col,
Integer defaultValue) {
return DataAccessUtils.uniqueResult(queryForIntegerList(cmd, col,
defaultValue));
}
/**
* Executes a given DBCommand, extracting a single column to a List of
* Integers. If the value in the database is null, defaultValue is added to
* the list.
*
* @param cmd
* the DBCommand to execute
* @param col
* the column to map
* @param defaultValue
* the value to return in case of the database value is null
* @return the extracted list
*/
public List<Integer> queryForIntegerList(final DBCommand cmd,
final DBColumnExpr col, final Integer defaultValue) {
class SingleIntegerMapper implements DBRecordMapper<Integer> {
@Override
public Integer mapRecord(DBRecordData record, int rowNum) {
return record.isNull(col) ? defaultValue : record.getInt(col);
}
}
return query(cmd, new SingleIntegerMapper());
}
/**
* Executes a given DBCommand, mapping a single column to a single String.
*
* @param cmd
* the DBCommand to execute
* @param col
* the column to map
* @return the single String value
* @throws IncorrectResultSizeDataAccessException
* if more than one result object has been found
*/
public String queryForString(final DBCommand cmd, final DBColumnExpr col) {
return DataAccessUtils.uniqueResult(queryForStringList(cmd, col));
}
/**
* Executes a given DBCommand, extracting a single column to a List of
* Strings.
*
* @param cmd
* the DBCommand to execute
* @param col
* the column to map
* @return the extracted list
*/
public List<String> queryForStringList(final DBCommand cmd,
final DBColumnExpr col) {
class SingleStringMapper implements DBRecordMapper<String> {
@Override
public String mapRecord(DBRecordData record, int rowNum) {
return record.getString(col);
}
}
return query(cmd, new SingleStringMapper());
}
/**
* Executes a given DBCommand and handles the DBReader with the provided
* DBReaderExtractor.
*
* @param cmd
* the DBCommand to execute
* @param readerExtractor
* @return the result returned by the readerExtractor
*/
public <K> K query(final DBCommand cmd,
final DBReaderExtractor<K> readerExtractor) {
class QueryCallback implements ConnectionCallback<K> {
public K doInConnection(Connection connection) throws SQLException,
DataAccessException {
return query(connection, cmd, readerExtractor);
}
}
return getJdbcTemplate().execute(new QueryCallback());
}
/**
* Executes a given DBCommand and handles each row of the DBReader with the
* provided DBRecordCallbackHandler.
*
* @param cmd
* the DBCommand to execute
* @param recordCallbackHandler
*/
public void query(final DBCommand cmd,
final DBRecordCallbackHandler recordCallbackHandler) {
query(cmd, new DbRecordCallbackHandlerExtractor(recordCallbackHandler));
}
/**
* Deletes a given record from the database. Calls
* DBRecord.delete(connection).
*
* @see org.apache.empire.db.DBRecord#delete(Connection)
* @param record
* to delete
*/
public void deleteRecord(final DBRecord record) {
class DeleteRecordCallback implements ConnectionCallback<Object> {
public Object doInConnection(Connection connection)
throws SQLException, DataAccessException {
record.delete(connection);
return null;
}
}
getJdbcTemplate().execute(new DeleteRecordCallback());
}
/**
* Deletes a record from a table with a given single primary key.
*
* @param table
* the table to delete from
* @param key
* the primary key
*/
public void deleteRecord(final DBTable table, final Object key) {
deleteRecord(table, new Object[] { key });
}
/**
* Deletes a record from a table with a given multiple primary key. Calls
* DBTable.deleteRecord(Object[], Connection).
*
* @see org.apache.empire.db.DBTable.deleteRecord(Object[], Connection)
* @param table
* the table to delete from
* @param keys
* the primary keys array
*/
public void deleteRecord(final DBTable table, final Object[] keys) {
class DeleteRecordCallback implements ConnectionCallback<Object> {
public Object doInConnection(Connection connection)
throws SQLException, DataAccessException {
table.deleteRecord(keys, connection);
return null;
}
}
getJdbcTemplate().execute(new DeleteRecordCallback());
}
/**
* Updates the record and saves all changes in the database. Calls
* DBRecord.update(connection).
*
* @see org.apache.empire.db.DBRecord#update(Connection)
* @param record
* to update
* @return the updated record
*/
public DBRecord updateRecord(final DBRecord record) {
class UpdateRecordCallback implements ConnectionCallback<DBRecord> {
public DBRecord doInConnection(Connection connection)
throws SQLException, DataAccessException {
record.update(connection);
return record;
}
}
return getJdbcTemplate().execute(new UpdateRecordCallback());
}
/**
* Executes an Update statement from a command object. This method delegates
* to DBDatabase.executeUpdate(cmd, connection), getting the DBDatabase
* instance from the DBCOmmand object.
*
* @see org.apache.empire.db.DBDatabase#executeUpdate(DBCommand, Connection)
*
* @param cmd
* the command object containing the update command
* @return the number of records that have been updated with the supplied
* statement
*/
public int executeUpdate(final DBCommand cmd) {
class UpdateRecordCallback implements ConnectionCallback<Integer> {
public Integer doInConnection(Connection connection)
throws SQLException, DataAccessException {
return cmd.getDatabase().executeUpdate(cmd, connection);
}
}
return getJdbcTemplate().execute(new UpdateRecordCallback());
}
/**
* Executes a Delete statement from a command object. This method delegates
* to DBDatabase.executeDelete(cmd, connection), getting the DBDatabase
* instance from the DBCOmmand object.
*
* @see org.apache.empire.db.DBDatabase#executeDelete(DBCommand, Connection)
*
* @param cmd
* the command object containing the delete command
* @return the number of records that have been deleted with the supplied
* statement
*/
public int executeDelete(final DBTable table, final DBCommand cmd) {
class DeleteRecordCallback implements ConnectionCallback<Integer> {
public Integer doInConnection(Connection connection)
throws SQLException, DataAccessException {
return cmd.getDatabase().executeDelete(table, cmd, connection);
}
}
return getJdbcTemplate().execute(new DeleteRecordCallback());
}
/**
* Executes an Insert statement from a command object. This method delegates
* to DBDatabase.executeInsert(cmd, connection), getting the DBDatabase
* instance from the DBCOmmand object.
*
* @see org.apache.empire.db.DBDatabase#executeInsert(DBCommand, Connection)
*
* @param cmd
* the command object containing the insert command
* @return the number of records that have been inserted with the supplied
* statement
*/
public int executeInsert(final DBCommand cmd) {
class InsertRecordCallback implements ConnectionCallback<Integer> {
public Integer doInConnection(Connection connection)
throws SQLException, DataAccessException {
return cmd.getDatabase().executeInsert(cmd, connection);
}
}
return getJdbcTemplate().execute(new InsertRecordCallback());
}
/**
* Helper method to an create a DBRecord instance. Can be customized through
* setRecordFactory or setRecordClass methods.
*
* @param table
* the table
* @return the new DBRecord instance
*/
public DBRecord newRecord(final DBRowSet table) {
DBRecord record = this.recordFactory.getObject();
record.create(table);
return record;
}
/**
* Opens a DBRecord instance with the given multiple primary keys. In
* contrast of getRecord(DBRowSet, Object) this method throws
* RecordNotFoundExpcetion if matching record exists.
*
* @param table
* the table to read the record from
* @param key
* the primary key
* @return the DBRecord instance, never null
* @throws org.apache.empire.db.exceptions.RecordNotFoundException
* in case of the record not found
*/
public DBRecord openRecord(final DBRowSet table, final Object key) {
return openRecord(table, new Object[] { key });
}
/**
* Opens a DBRecord instance with the given multiple primary keys. In
* contrast of getRecord(DBRowSet, Object[]) this method throws
* RecordNotFoundExpcetion if matching record exists.
*
* @param table
* the table to read the record from
* @param keys
* the primary key array
* @return the DBRecord instance, never null
* @throws org.apache.empire.db.exceptions.RecordNotFoundException
* in case of the record not found
*/
public DBRecord openRecord(final DBRowSet table, final Object[] keys) {
class ReadRecordCallback implements ConnectionCallback<DBRecord> {
public DBRecord doInConnection(Connection connection)
throws SQLException, DataAccessException {
DBRecord record = EmpireTemplate.this.recordFactory.getObject();
record.read(table, keys, connection);
return record;
}
}
return getJdbcTemplate().execute(new ReadRecordCallback());
}
/**
* Opens a DBRecord instance with the given single primary key. In contrast
* of openRecord(DBRowSet, Object) this method returns null if no matching
* record exists.
*
* @param table
* the table to read the record from
* @param key
* the primary key
* @return the DBRecord instance, can be null
*/
public DBRecord getRecord(final DBRowSet table, final Object key) {
return openRecord(table, new Object[] { key });
}
/**
* Opens a DBRecord instance with the given multiple primary keys. In
* contrast of openRecord(DBRowSet, Object[]) this method returns null if no
* matching record exists.
*
* @param table
* the table to read the record from
* @param keys
* the primary keys array
* @return the DBRecord instance, can be null
*/
public DBRecord getRecord(final DBRowSet table, final Object[] keys) {
class ReadRecordCallback implements ConnectionCallback<DBRecord> {
public DBRecord doInConnection(Connection connection)
throws SQLException, DataAccessException {
DBRecord record = EmpireTemplate.this.recordFactory.getObject();
try {
record.read(table, keys, connection);
} catch (RecordNotFoundException e) {
return null;
}
return record;
}
}
return getJdbcTemplate().execute(new ReadRecordCallback());
}
/**
* Executes a given DBCommand query and maps each row to Class<T> object
* using DBReader.getBeanList method.
*
* @see org.apache.empire.db.DBReader.getBeanList(C, Class<T>, int)
*
* @param cmd
* the query command
* @param c
* the collection to add the objects to
* @param t
* the class type of the objects in the list
* @param maxCount
* the maximum number of objects
*
* @return the list of T
*/
public <C extends Collection<T>, T> C queryForBeanList(final DBCommand cmd,
final C c, final Class<T> t, final int maxCount) {
class GetBeanListCallback implements DBReaderExtractor<C> {
@Override
public C process(DBReader reader) {
return reader.getBeanList(c, t, maxCount);
}
}
return query(cmd, new GetBeanListCallback());
}
/**
* Executes a given DBCommand query and maps each row to Class<T> object
* using DBReader.getBeanList method.
*
* @see org.apache.empire.db.DBReader.getBeanList(Class<T>, int)
*
* @param cmd
* the query command
* @param t
* the class type of the objects in the list
* @param maxCount
* the maximum number of objects
*
* @return the list of T
*/
public <T> List<T> queryForBeanList(DBCommand cmd, Class<T> t, int maxItems) {
return queryForBeanList(cmd, new ArrayList<T>(), t, maxItems);
}
/**
* Executes a given DBCommand query and maps each row to Class<T> object
* using DBReader.getBeanList method.
*
* @see org.apache.empire.db.DBReader.getBeanList(Class<T>)
*
* @param cmd
* the query command
* @param t
* the class type of the objects in the list
*
* @return the list of T
*/
public <T> List<T> queryForBeanList(DBCommand cmd, Class<T> t) {
return queryForBeanList(cmd, t, -1);
}
/**
* Executes a given DBCommand query and maps a single row to Class<T> object
* using DBReader.getBeanList method.
*
* @param cmd
* the query command
* @param t
* the class type of the object to return
*
* @return the list of T
* @throws IncorrectResultSizeDataAccessException
* if more than one result object has been found
*/
public <T> T queryForBean(DBCommand cmd, Class<T> t) {
return DataAccessUtils.uniqueResult(queryForBeanList(cmd, t, -1));
}
/**
* Executes a ConnectionCallback. Delegates to the underlying JdbcTemplate.
*
* @see
* org.springframework.jdbc.core.JdbcTemplate.execute(ConnectionCallback
* <K>)
*
* @param connectionCallback
* @return arbitrary object
*/
public <K> K execute(ConnectionCallback<K> connectionCallback) {
return getJdbcTemplate().execute(connectionCallback);
}
private <K> K query(Connection connection, DBCommand command,
DBReaderExtractor<K> callback) {
DBReader reader = newDBReader();
try {
reader.open(command, connection);
return callback.process(reader);
} finally {
reader.close();
}
}
/**
* Creates a new DBReader instance from the readerFactory;
*
* @return DBReader instance
*/
private DBReader newDBReader() {
return this.readerFactory.getObject();
}
private static class DbRecordCallbackHandlerExtractor implements
DBReaderExtractor<Object> {
private final DBRecordCallbackHandler rowCallbackHandler;
public DbRecordCallbackHandlerExtractor(
DBRecordCallbackHandler rowCallbackHandler) {
Assert.notNull(rowCallbackHandler, "RowCallbackHandler is required");
this.rowCallbackHandler = rowCallbackHandler;
}
// @Override
public Object process(DBReader reader) {
try {
while (reader.moveNext()) {
this.rowCallbackHandler.processRecord(reader);
}
return null;
} finally {
reader.close();
}
}
}
private static class DbRecordMapperExtractor<K> implements
DBReaderExtractor<List<K>> {
private final DBRecordMapper<K> dataReader;
public DbRecordMapperExtractor(DBRecordMapper<K> rowMapper) {
Assert.notNull(rowMapper, "DataReader is required");
this.dataReader = rowMapper;
}
// @Override
public List<K> process(DBReader reader) {
try {
List<K> results = new ArrayList<K>();
int rowNum = 0;
while (reader.moveNext()) {
results.add(this.dataReader.mapRecord(reader, rowNum));
rowNum++;
}
return results;
} finally {
reader.close();
}
}
}
}