com.j256.ormlite.dao.BaseDaoImpl Maven / Gradle / Ivy
package com.j256.ormlite.dao;
import java.lang.reflect.Constructor;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import com.j256.ormlite.db.DatabaseType;
import com.j256.ormlite.field.DataType;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.misc.BaseDaoEnabled;
import com.j256.ormlite.stmt.DeleteBuilder;
import com.j256.ormlite.stmt.GenericRowMapper;
import com.j256.ormlite.stmt.PreparedDelete;
import com.j256.ormlite.stmt.PreparedQuery;
import com.j256.ormlite.stmt.PreparedUpdate;
import com.j256.ormlite.stmt.QueryBuilder;
import com.j256.ormlite.stmt.SelectArg;
import com.j256.ormlite.stmt.SelectIterator;
import com.j256.ormlite.stmt.StatementBuilder.StatementType;
import com.j256.ormlite.stmt.StatementExecutor;
import com.j256.ormlite.stmt.UpdateBuilder;
import com.j256.ormlite.stmt.Where;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.support.DatabaseResults;
import com.j256.ormlite.table.DatabaseTableConfig;
import com.j256.ormlite.table.ObjectFactory;
import com.j256.ormlite.table.TableInfo;
/**
* Base class for the Database Access Objects that handle the reading and writing a class from the database.
*
*
* This class is also {@link Iterable} which means you can do a {@code for (T obj : dao)} type of loop code to iterate
* through the table of persisted objects. See {@link #iterator()}.
*
*
*
* NOTE: If you are using the Spring type wiring, {@link #initialize} should be called after all of the set
* methods. In Spring XML, init-method="initialize" should be used.
*
*
* @param
* The class that the code will be operating on.
* @param
* The class of the ID column associated with the class. The T class does not require an ID field. The class
* needs an ID parameter however so you can use Void or Object to satisfy the compiler.
* @author graywatson
*/
public abstract class BaseDaoImpl implements Dao {
private static final ThreadLocal>> daoConfigLevelLocal =
new ThreadLocal>>() {
@Override
protected List> initialValue() {
return new ArrayList>(10);
}
};
private static ReferenceObjectCache defaultObjectCache;
private static final Object constantObject = new Object();
protected StatementExecutor statementExecutor;
protected DatabaseType databaseType;
protected final Class dataClass;
protected Constructor constructor;
protected DatabaseTableConfig tableConfig;
protected TableInfo tableInfo;
protected ConnectionSource connectionSource;
protected CloseableIterator lastIterator;
protected ObjectFactory objectFactory;
private boolean initialized;
// NOTE: package perms to removed synthetic accessor
ObjectCache objectCache;
private ConcurrentMap daoObserverMap;
/**
* Construct our base DAO using Spring type wiring. The {@link ConnectionSource} must be set with the
* {@link #setConnectionSource} method afterwards and then the {@link #initialize()} method must be called. The
* dataClass provided must have its fields marked with {@link DatabaseField} annotations or the
* {@link #setTableConfig} method must be called before the {@link #initialize()} method is called.
*
*
* If you are using Spring then your should use: init-method="initialize"
*
*
* @param dataClass
* Class associated with this Dao. This must match the T class parameter.
*/
protected BaseDaoImpl(Class dataClass) throws SQLException {
this(null, dataClass, null);
}
/**
* Construct our base DAO class. The dataClass provided must have its fields marked with {@link DatabaseField} or
* javax.persistance annotations.
*
* @param connectionSource
* Source of our database connections.
* @param dataClass
* Class associated with this Dao. This must match the T class parameter.
*/
protected BaseDaoImpl(ConnectionSource connectionSource, Class dataClass) throws SQLException {
this(connectionSource, dataClass, null);
}
/**
* Construct our base DAO class.
*
* @param connectionSource
* Source of our database connections.
* @param tableConfig
* Hand or Spring wired table configuration information.
*/
protected BaseDaoImpl(ConnectionSource connectionSource, DatabaseTableConfig tableConfig) throws SQLException {
this(connectionSource, tableConfig.getDataClass(), tableConfig);
}
private BaseDaoImpl(ConnectionSource connectionSource, Class dataClass, DatabaseTableConfig tableConfig)
throws SQLException {
this.dataClass = dataClass;
this.tableConfig = tableConfig;
this.constructor = findNoArgConstructor(dataClass);
if (connectionSource != null) {
this.connectionSource = connectionSource;
initialize();
}
}
/**
* Initialize the various DAO configurations after the various setters have been called.
*/
public void initialize() throws SQLException {
if (initialized) {
// just skip it if already initialized
return;
}
if (connectionSource == null) {
throw new IllegalStateException("connectionSource was never set on " + getClass().getSimpleName());
}
databaseType = connectionSource.getDatabaseType();
if (databaseType == null) {
throw new IllegalStateException(
"connectionSource is getting a null DatabaseType in " + getClass().getSimpleName());
}
if (tableConfig == null) {
tableInfo = new TableInfo(databaseType, dataClass);
} else {
tableConfig.extractFieldTypes(databaseType);
tableInfo = new TableInfo(databaseType, tableConfig);
}
statementExecutor = new StatementExecutor(databaseType, tableInfo, this);
/*
* This is a bit complex. Initially, when we were configuring the field types, external DAO information would be
* configured for auto-refresh, foreign BaseDaoEnabled classes, and foreign-collections. This would cause the
* system to go recursive and for class loops, a stack overflow.
*
* Then we fixed this by putting a level counter in the FieldType constructor that would stop the configurations
* when we reach some recursion level. But this created some bad problems because we were using the DaoManager
* to cache the created DAOs that had been constructed already limited by the level.
*
* What we do now is have a 2 phase initialization. The constructor initializes most of the fields but then we
* go back and call FieldType.configDaoInformation() after we are done. So for every DAO that is initialized
* here, we have to see if it is the top DAO. If not we save it for dao configuration later.
*/
List> daoConfigList = daoConfigLevelLocal.get();
daoConfigList.add(this);
if (daoConfigList.size() > 1) {
// if we have recursed then just save the dao for later configuration
return;
}
try {
/*
* WARNING: We do _not_ use an iterator here because we may be adding to the list as we process it and we'll
* get exceptions otherwise. This is an ArrayList so the get(i) should be efficient.
*
* Also, do _not_ get a copy of daoConfigLevel.doArray because that array may be replaced by another, larger
* one during the recursion.
*/
for (int i = 0; i < daoConfigList.size(); i++) {
BaseDaoImpl dao = daoConfigList.get(i);
/*
* Here's another complex bit. The first DAO that is being constructed as part of a DAO chain is not yet
* in the DaoManager cache. If we continue onward we might come back around and try to configure this
* DAO again. Forcing it into the cache here makes sure it won't be configured twice. This will cause it
* to be stuck in twice when it returns out to the DaoManager but that's a small price to pay. This also
* applies to self-referencing classes.
*/
DaoManager.registerDao(connectionSource, dao);
try {
// config our fields which may go recursive
for (FieldType fieldType : dao.getTableInfo().getFieldTypes()) {
fieldType.configDaoInformation(connectionSource, dao.getDataClass());
}
} catch (SQLException e) {
// unregister the DAO we just pre-registered
DaoManager.unregisterDao(connectionSource, dao);
throw e;
}
// it's now been fully initialized
dao.initialized = true;
}
} finally {
// if we throw we want to clear our class hierarchy here
daoConfigList.clear();
daoConfigLevelLocal.remove();
}
}
@Override
public T queryForId(ID id) throws SQLException {
checkForInitialized();
DatabaseConnection connection = connectionSource.getReadOnlyConnection(tableInfo.getTableName());
try {
return statementExecutor.queryForId(connection, id, objectCache);
} finally {
connectionSource.releaseConnection(connection);
}
}
@Override
public T queryForFirst(PreparedQuery preparedQuery) throws SQLException {
checkForInitialized();
DatabaseConnection connection = connectionSource.getReadOnlyConnection(tableInfo.getTableName());
try {
return statementExecutor.queryForFirst(connection, preparedQuery, objectCache);
} finally {
connectionSource.releaseConnection(connection);
}
}
@Override
public List queryForAll() throws SQLException {
checkForInitialized();
return statementExecutor.queryForAll(connectionSource, objectCache);
}
@Override
public T queryForFirst() throws SQLException {
checkForInitialized();
return queryBuilder().queryForFirst();
}
@Override
public List queryForEq(String fieldName, Object value) throws SQLException {
return queryBuilder().where().eq(fieldName, value).query();
}
@Override
public QueryBuilder queryBuilder() {
checkForInitialized();
return new QueryBuilder(databaseType, tableInfo, this);
}
@Override
public UpdateBuilder updateBuilder() {
checkForInitialized();
return new UpdateBuilder(databaseType, tableInfo, this);
}
@Override
public DeleteBuilder deleteBuilder() {
checkForInitialized();
return new DeleteBuilder(databaseType, tableInfo, this);
}
@Override
public List query(PreparedQuery preparedQuery) throws SQLException {
checkForInitialized();
return statementExecutor.query(connectionSource, preparedQuery, objectCache);
}
@Override
public List queryForMatching(T matchObj) throws SQLException {
return queryForMatching(matchObj, false);
}
@Override
public List queryForMatchingArgs(T matchObj) throws SQLException {
return queryForMatching(matchObj, true);
}
@Override
public List queryForFieldValues(Map fieldValues) throws SQLException {
return queryForFieldValues(fieldValues, false);
}
@Override
public List queryForFieldValuesArgs(Map fieldValues) throws SQLException {
return queryForFieldValues(fieldValues, true);
}
@Override
public T queryForSameId(T data) throws SQLException {
checkForInitialized();
if (data == null) {
return null;
}
ID id = extractId(data);
if (id == null) {
return null;
} else {
return queryForId(id);
}
}
@Override
public int create(T data) throws SQLException {
checkForInitialized();
// ignore creating a null object
if (data == null) {
return 0;
}
if (data instanceof BaseDaoEnabled) {
@SuppressWarnings("unchecked")
BaseDaoEnabled daoEnabled = (BaseDaoEnabled) data;
daoEnabled.setDao(this);
}
DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
try {
return statementExecutor.create(connection, data, objectCache);
} finally {
connectionSource.releaseConnection(connection);
}
}
@Override
public int create(final Collection datas) throws SQLException {
checkForInitialized();
for (T data : datas) {
if (data instanceof BaseDaoEnabled) {
@SuppressWarnings("unchecked")
BaseDaoEnabled daoEnabled = (BaseDaoEnabled) data;
daoEnabled.setDao(this);
}
}
/*
* This is a little strange in that we get the connection but then the call-batch-task saves another one. I
* thought that it was an ok thing to do otherwise it made the call-batch-tasks more complicated.
*/
final DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
try {
return callBatchTasks(new Callable() {
@Override
public Integer call() throws SQLException {
int modCount = 0;
for (T data : datas) {
modCount += statementExecutor.create(connection, data, objectCache);
}
return modCount;
}
});
} finally {
connectionSource.releaseConnection(connection);
}
}
@Override
public synchronized T createIfNotExists(T data) throws SQLException {
if (data == null) {
return null;
}
T existing = queryForSameId(data);
if (existing == null) {
create(data);
return data;
} else {
return existing;
}
}
@Override
public synchronized CreateOrUpdateStatus createOrUpdate(T data) throws SQLException {
if (data == null) {
return new CreateOrUpdateStatus(false, false, 0);
}
ID id = extractId(data);
// assume we need to create it if there is no id
if (id == null || !idExists(id)) {
int numRows = create(data);
return new CreateOrUpdateStatus(true, false, numRows);
} else {
int numRows = update(data);
return new CreateOrUpdateStatus(false, true, numRows);
}
}
@Override
public int update(T data) throws SQLException {
checkForInitialized();
// ignore updating a null object
if (data == null) {
return 0;
}
if (data instanceof BaseDaoEnabled) {
@SuppressWarnings("unchecked")
BaseDaoEnabled daoEnabled = (BaseDaoEnabled) data;
daoEnabled.setDao(this);
}
DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
try {
return statementExecutor.update(connection, data, objectCache);
} finally {
connectionSource.releaseConnection(connection);
}
}
@Override
public int updateId(T data, ID newId) throws SQLException {
checkForInitialized();
// ignore updating a null object
if (data == null) {
return 0;
} else {
DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
try {
return statementExecutor.updateId(connection, data, newId, objectCache);
} finally {
connectionSource.releaseConnection(connection);
}
}
}
@Override
public int update(PreparedUpdate preparedUpdate) throws SQLException {
checkForInitialized();
DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
try {
return statementExecutor.update(connection, preparedUpdate);
} finally {
connectionSource.releaseConnection(connection);
}
}
@Override
public int refresh(T data) throws SQLException {
checkForInitialized();
// ignore refreshing a null object
if (data == null) {
return 0;
}
if (data instanceof BaseDaoEnabled) {
@SuppressWarnings("unchecked")
BaseDaoEnabled daoEnabled = (BaseDaoEnabled) data;
daoEnabled.setDao(this);
}
DatabaseConnection connection = connectionSource.getReadOnlyConnection(tableInfo.getTableName());
try {
return statementExecutor.refresh(connection, data, objectCache);
} finally {
connectionSource.releaseConnection(connection);
}
}
@Override
public int delete(T data) throws SQLException {
checkForInitialized();
// ignore deleting a null object
if (data == null) {
return 0;
} else {
DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
try {
return statementExecutor.delete(connection, data, objectCache);
} finally {
connectionSource.releaseConnection(connection);
}
}
}
@Override
public int deleteById(ID id) throws SQLException {
checkForInitialized();
// ignore deleting a null id
if (id == null) {
return 0;
} else {
DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
try {
return statementExecutor.deleteById(connection, id, objectCache);
} finally {
connectionSource.releaseConnection(connection);
}
}
}
@Override
public int delete(Collection datas) throws SQLException {
checkForInitialized();
// ignore deleting a null object
if (datas == null || datas.isEmpty()) {
return 0;
} else {
DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
try {
return statementExecutor.deleteObjects(connection, datas, objectCache);
} finally {
connectionSource.releaseConnection(connection);
}
}
}
@Override
public int deleteIds(Collection ids) throws SQLException {
checkForInitialized();
// ignore deleting a null object
if (ids == null || ids.isEmpty()) {
return 0;
} else {
DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
try {
return statementExecutor.deleteIds(connection, ids, objectCache);
} finally {
connectionSource.releaseConnection(connection);
}
}
}
@Override
public int delete(PreparedDelete preparedDelete) throws SQLException {
checkForInitialized();
DatabaseConnection connection = connectionSource.getReadWriteConnection(tableInfo.getTableName());
try {
return statementExecutor.delete(connection, preparedDelete);
} finally {
connectionSource.releaseConnection(connection);
}
}
@Override
public CloseableIterator iterator() {
return iterator(DatabaseConnection.DEFAULT_RESULT_FLAGS);
}
@Override
public CloseableIterator closeableIterator() {
return iterator(DatabaseConnection.DEFAULT_RESULT_FLAGS);
}
@Override
public CloseableIterator iterator(int resultFlags) {
checkForInitialized();
CloseableIterator iterator = createIterator(resultFlags);
lastIterator = iterator;
return iterator; // make sure we return the newly created iterator and not the field!
}
@Override
public CloseableWrappedIterable getWrappedIterable() {
checkForInitialized();
return new CloseableWrappedIterableImpl(new CloseableIterable() {
@Override
public Iterator iterator() {
return closeableIterator();
}
@Override
public CloseableIterator closeableIterator() {
try {
return BaseDaoImpl.this.createIterator(DatabaseConnection.DEFAULT_RESULT_FLAGS);
} catch (Exception e) {
throw new IllegalStateException("Could not build iterator for " + dataClass, e);
}
}
});
}
@Override
public CloseableWrappedIterable getWrappedIterable(final PreparedQuery preparedQuery) {
checkForInitialized();
return new CloseableWrappedIterableImpl(new CloseableIterable() {
@Override
public Iterator iterator() {
return closeableIterator();
}
@Override
public CloseableIterator closeableIterator() {
try {
return BaseDaoImpl.this.createIterator(preparedQuery, DatabaseConnection.DEFAULT_RESULT_FLAGS);
} catch (Exception e) {
throw new IllegalStateException("Could not build prepared-query iterator for " + dataClass, e);
}
}
});
}
@Override
public void closeLastIterator() throws Exception {
if (lastIterator != null) {
lastIterator.close();
lastIterator = null;
}
}
@Override
public CloseableIterator iterator(PreparedQuery preparedQuery) throws SQLException {
return iterator(preparedQuery, DatabaseConnection.DEFAULT_RESULT_FLAGS);
}
@Override
public CloseableIterator iterator(PreparedQuery preparedQuery, int resultFlags) throws SQLException {
checkForInitialized();
CloseableIterator li = createIterator(preparedQuery, resultFlags);
lastIterator = li;
return li; // make sure we return the newly created iterator and not the field!
}
@Override
public GenericRawResults queryRaw(String query, String... arguments) throws SQLException {
checkForInitialized();
try {
return statementExecutor.queryRaw(connectionSource, query, arguments, objectCache);
} catch (SQLException e) {
throw new SQLException("Could not perform raw query for " + query, e);
}
}
@Override
public GenericRawResults queryRaw(String query, RawRowMapper mapper, String... arguments)
throws SQLException {
checkForInitialized();
try {
return statementExecutor.queryRaw(connectionSource, query, mapper, arguments, objectCache);
} catch (SQLException e) {
throw new SQLException("Could not perform raw query for " + query, e);
}
}
@Override
public GenericRawResults queryRaw(String query, DataType[] columnTypes, RawRowObjectMapper mapper,
String... arguments) throws SQLException {
checkForInitialized();
try {
return statementExecutor.queryRaw(connectionSource, query, columnTypes, mapper, arguments, objectCache);
} catch (SQLException e) {
throw new SQLException("Could not perform raw query for " + query, e);
}
}
@Override
public GenericRawResults