de.greenrobot.dao.AbstractDao Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of greendao-encryption Show documentation
Show all versions of greendao-encryption Show documentation
greenDAO is a light and fast ORM for Android
/*
* Copyright (C) 2011-2016 Markus Junginger, greenrobot (http://greenrobot.de)
*
* 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 de.greenrobot.dao;
import android.database.CrossProcessCursor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.DatabaseUtils;
import de.greenrobot.dao.database.Database;
import de.greenrobot.dao.database.DatabaseStatement;
import de.greenrobot.dao.identityscope.IdentityScope;
import de.greenrobot.dao.identityscope.IdentityScopeLong;
import de.greenrobot.dao.internal.DaoConfig;
import de.greenrobot.dao.internal.FastCursor;
import de.greenrobot.dao.internal.TableStatements;
import de.greenrobot.dao.query.Query;
import de.greenrobot.dao.query.QueryBuilder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
/**
* Base class for all DAOs: Implements entity operations like insert, load, delete, and query.
*
* This class is thread-safe.
*
* @param Entity type
* @param Primary key (PK) type; use Void if entity does not have exactly one PK
* @author Markus
*/
/*
* When operating on TX, statements, or identity scope the following locking order must be met to avoid deadlocks:
*
* 1.) If not inside a TX already, begin a TX to acquire a DB connection (connection is to be handled like a lock)
*
* 2.) The DatabaseStatement
*
* 3.) identityScope
*/
public abstract class AbstractDao {
protected final Database db;
protected final DaoConfig config;
protected IdentityScope identityScope;
protected IdentityScopeLong identityScopeLong;
protected TableStatements statements;
protected final AbstractDaoSession session;
protected final int pkOrdinal;
public AbstractDao(DaoConfig config) {
this(config, null);
}
@SuppressWarnings("unchecked")
public AbstractDao(DaoConfig config, AbstractDaoSession daoSession) {
this.config = config;
this.session = daoSession;
db = config.db;
identityScope = (IdentityScope) config.getIdentityScope();
if (identityScope instanceof IdentityScopeLong) {
identityScopeLong = (IdentityScopeLong) identityScope;
}
statements = config.statements;
pkOrdinal = config.pkProperty != null ? config.pkProperty.ordinal : -1;
}
public AbstractDaoSession getSession() {
return session;
}
TableStatements getStatements() {
return config.statements;
}
public String getTablename() {
return config.tablename;
}
public Property[] getProperties() {
return config.properties;
}
public Property getPkProperty() {
return config.pkProperty;
}
public String[] getAllColumns() {
return config.allColumns;
}
public String[] getPkColumns() {
return config.pkColumns;
}
public String[] getNonPkColumns() {
return config.nonPkColumns;
}
/**
* Loads and entity for the given PK.
*
* @param key a PK value or null
* @return The entity or null, if no entity matched the PK value
*/
public T load(K key) {
assertSinglePk();
if (key == null) {
return null;
}
if (identityScope != null) {
T entity = identityScope.get(key);
if (entity != null) {
return entity;
}
}
String sql = statements.getSelectByKey();
String[] keyArray = new String[]{key.toString()};
Cursor cursor = db.rawQuery(sql, keyArray);
return loadUniqueAndCloseCursor(cursor);
}
public T loadByRowId(long rowId) {
String[] idArray = new String[]{Long.toString(rowId)};
Cursor cursor = db.rawQuery(statements.getSelectByRowId(), idArray);
return loadUniqueAndCloseCursor(cursor);
}
protected T loadUniqueAndCloseCursor(Cursor cursor) {
try {
return loadUnique(cursor);
} finally {
cursor.close();
}
}
protected T loadUnique(Cursor cursor) {
boolean available = cursor.moveToFirst();
if (!available) {
return null;
} else if (!cursor.isLast()) {
throw new DaoException("Expected unique result, but count was " + cursor.getCount());
}
return loadCurrent(cursor, 0, true);
}
/** Loads all available entities from the database. */
public List loadAll() {
Cursor cursor = db.rawQuery(statements.getSelectAll(), null);
return loadAllAndCloseCursor(cursor);
}
/** Detaches an entity from the identity scope (session). Subsequent query results won't return this object. */
public boolean detach(T entity) {
if (identityScope != null) {
K key = getKeyVerified(entity);
return identityScope.detach(key, entity);
} else {
return false;
}
}
/**
* Detaches all entities (of type T) from the identity scope (session). Subsequent query results won't return any
* previously loaded objects.
*/
public void detachAll() {
if (identityScope != null) {
identityScope.clear();
}
}
protected List loadAllAndCloseCursor(Cursor cursor) {
try {
return loadAllFromCursor(cursor);
} finally {
cursor.close();
}
}
/**
* Inserts the given entities in the database using a transaction.
*
* @param entities The entities to insert.
*/
public void insertInTx(Iterable entities) {
insertInTx(entities, isEntityUpdateable());
}
/**
* Inserts the given entities in the database using a transaction.
*
* @param entities The entities to insert.
*/
public void insertInTx(T... entities) {
insertInTx(Arrays.asList(entities), isEntityUpdateable());
}
/**
* Inserts the given entities in the database using a transaction. The given entities will become tracked if the PK
* is set.
*
* @param entities The entities to insert.
* @param setPrimaryKey if true, the PKs of the given will be set after the insert; pass false to improve
* performance.
*/
public void insertInTx(Iterable entities, boolean setPrimaryKey) {
DatabaseStatement stmt = statements.getInsertStatement();
executeInsertInTx(stmt, entities, setPrimaryKey);
}
/**
* Inserts or replaces the given entities in the database using a transaction. The given entities will become
* tracked if the PK is set.
*
* @param entities The entities to insert.
* @param setPrimaryKey if true, the PKs of the given will be set after the insert; pass false to improve
* performance.
*/
public void insertOrReplaceInTx(Iterable entities, boolean setPrimaryKey) {
DatabaseStatement stmt = statements.getInsertOrReplaceStatement();
executeInsertInTx(stmt, entities, setPrimaryKey);
}
/**
* Inserts or replaces the given entities in the database using a transaction.
*
* @param entities The entities to insert.
*/
public void insertOrReplaceInTx(Iterable entities) {
insertOrReplaceInTx(entities, isEntityUpdateable());
}
/**
* Inserts or replaces the given entities in the database using a transaction.
*
* @param entities The entities to insert.
*/
public void insertOrReplaceInTx(T... entities) {
insertOrReplaceInTx(Arrays.asList(entities), isEntityUpdateable());
}
private void executeInsertInTx(DatabaseStatement stmt, Iterable entities, boolean setPrimaryKey) {
db.beginTransaction();
try {
synchronized (stmt) {
if (identityScope != null) {
identityScope.lock();
}
try {
for (T entity : entities) {
bindValues(stmt, entity);
if (setPrimaryKey) {
long rowId = stmt.executeInsert();
updateKeyAfterInsertAndAttach(entity, rowId, false);
} else {
stmt.execute();
}
}
} finally {
if (identityScope != null) {
identityScope.unlock();
}
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
/**
* Insert an entity into the table associated with a concrete DAO.
*
* @return row ID of newly inserted entity
*/
public long insert(T entity) {
return executeInsert(entity, statements.getInsertStatement());
}
/**
* Insert an entity into the table associated with a concrete DAO without setting key property.
*
* Warning: This may be faster, but the entity should not be used anymore. The entity also won't be attached to
* identity scope.
*
* @return row ID of newly inserted entity
*/
public long insertWithoutSettingPk(T entity) {
DatabaseStatement stmt = statements.getInsertStatement();
long rowId;
if (db.isDbLockedByCurrentThread()) {
synchronized (stmt) {
bindValues(stmt, entity);
rowId = stmt.executeInsert();
}
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks
db.beginTransaction();
try {
synchronized (stmt) {
bindValues(stmt, entity);
rowId = stmt.executeInsert();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
return rowId;
}
/**
* Insert an entity into the table associated with a concrete DAO.
*
* @return row ID of newly inserted entity
*/
public long insertOrReplace(T entity) {
return executeInsert(entity, statements.getInsertOrReplaceStatement());
}
private long executeInsert(T entity, DatabaseStatement stmt) {
long rowId;
if (db.isDbLockedByCurrentThread()) {
synchronized (stmt) {
bindValues(stmt, entity);
rowId = stmt.executeInsert();
}
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks
db.beginTransaction();
try {
synchronized (stmt) {
bindValues(stmt, entity);
rowId = stmt.executeInsert();
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
updateKeyAfterInsertAndAttach(entity, rowId, true);
return rowId;
}
protected void updateKeyAfterInsertAndAttach(T entity, long rowId, boolean lock) {
if (rowId != -1) {
K key = updateKeyAfterInsert(entity, rowId);
attachEntity(key, entity, lock);
} else {
// TODO When does this actually happen? Should we throw instead?
DaoLog.w("Could not insert row (executeInsert returned -1)");
}
}
/** Reads all available rows from the given cursor and returns a list of entities. */
protected List loadAllFromCursor(Cursor cursor) {
int count = cursor.getCount();
if (count == 0) {
return new ArrayList();
}
List list = new ArrayList(count);
CursorWindow window = null;
boolean useFastCursor = false;
if (cursor instanceof CrossProcessCursor) {
window = ((CrossProcessCursor) cursor).getWindow();
if (window != null) { // E.g. Robolectric has no Window at this point
if (window.getNumRows() == count) {
cursor = new FastCursor(window);
useFastCursor = true;
} else {
DaoLog.d("Window vs. result size: " + window.getNumRows() + "/" + count);
}
}
}
if (cursor.moveToFirst()) {
if (identityScope != null) {
identityScope.lock();
identityScope.reserveRoom(count);
}
try {
if (!useFastCursor && window != null && identityScope != null) {
loadAllUnlockOnWindowBounds(cursor, window, list);
} else {
do {
list.add(loadCurrent(cursor, 0, false));
} while (cursor.moveToNext());
}
} finally {
if (identityScope != null) {
identityScope.unlock();
}
}
}
return list;
}
private void loadAllUnlockOnWindowBounds(Cursor cursor, CursorWindow window, List list) {
int windowEnd = window.getStartPosition() + window.getNumRows();
for (int row = 0; ; row++) {
list.add(loadCurrent(cursor, 0, false));
row++;
if (row >= windowEnd) {
window = moveToNextUnlocked(cursor);
if (window == null) {
break;
}
windowEnd = window.getStartPosition() + window.getNumRows();
} else {
if (!cursor.moveToNext()) {
break;
}
}
}
}
/**
* Unlock identityScope during cursor.moveToNext() when it is about to fill the window (needs a db connection):
* We should not hold the lock while trying to acquire a db connection to avoid deadlocks.
*/
private CursorWindow moveToNextUnlocked(Cursor cursor) {
identityScope.unlock();
try {
if (cursor.moveToNext()) {
return ((CrossProcessCursor) cursor).getWindow();
} else {
return null;
}
} finally {
identityScope.lock();
}
}
/** Internal use only. Considers identity scope. */
final protected T loadCurrent(Cursor cursor, int offset, boolean lock) {
if (identityScopeLong != null) {
if (offset != 0) {
// Occurs with deep loads (left outer joins)
if (cursor.isNull(pkOrdinal + offset)) {
return null;
}
}
long key = cursor.getLong(pkOrdinal + offset);
T entity = lock ? identityScopeLong.get2(key) : identityScopeLong.get2NoLock(key);
if (entity != null) {
return entity;
} else {
entity = readEntity(cursor, offset);
attachEntity(entity);
if (lock) {
identityScopeLong.put2(key, entity);
} else {
identityScopeLong.put2NoLock(key, entity);
}
return entity;
}
} else if (identityScope != null) {
K key = readKey(cursor, offset);
if (offset != 0 && key == null) {
// Occurs with deep loads (left outer joins)
return null;
}
T entity = lock ? identityScope.get(key) : identityScope.getNoLock(key);
if (entity != null) {
return entity;
} else {
entity = readEntity(cursor, offset);
attachEntity(key, entity, lock);
return entity;
}
} else {
// Check offset, assume a value !=0 indicating a potential outer join, so check PK
if (offset != 0) {
K key = readKey(cursor, offset);
if (key == null) {
// Occurs with deep loads (left outer joins)
return null;
}
}
T entity = readEntity(cursor, offset);
attachEntity(entity);
return entity;
}
}
/** Internal use only. Considers identity scope. */
final protected O loadCurrentOther(AbstractDao dao, Cursor cursor, int offset) {
return dao.loadCurrent(cursor, offset, /* TODO check this */true);
}
/** A raw-style query where you can pass any WHERE clause and arguments. */
public List queryRaw(String where, String... selectionArg) {
Cursor cursor = db.rawQuery(statements.getSelectAll() + where, selectionArg);
return loadAllAndCloseCursor(cursor);
}
/**
* Creates a repeatable {@link Query} object based on the given raw SQL where you can pass any WHERE clause and
* arguments.
*/
public Query queryRawCreate(String where, Object... selectionArg) {
List