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 DaoCore Show documentation
Show all versions of DaoCore Show documentation
greenDAO is a light and fast ORM for Android
The newest version!
/*
* Copyright (C) 2011-2013 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import android.database.CrossProcessCursor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
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;
/**
* Base class for all DAOs: Implements entity operations like insert, load, delete, and query.
*
* This class is thread-safe.
*
* @author Markus
*
* @param
* Entity type
* @param
* Primary key (PK) type; use Void if entity does not have exactly one PK
*/
/*
* 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 SQLiteStatement
*
* 3.) identityScope
*/
public abstract class AbstractDao {
protected final SQLiteDatabase 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;
}
}
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) {
SQLiteStatement 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) {
SQLiteStatement 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(SQLiteStatement 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 identy scope.
*
* @return row ID of newly inserted entity
*/
public long insertWithoutSettingPk(T entity) {
SQLiteStatement 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, SQLiteStatement 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();
List list = new ArrayList(count);
if (cursor instanceof CrossProcessCursor) {
CursorWindow window = ((CrossProcessCursor) cursor).getWindow();
if (window != null) { // E.g. Roboelectric has no Window at this point
if (window.getNumRows() == count) {
cursor = new FastCursor(window);
} else {
DaoLog.d("Window vs. result size: " + window.getNumRows() + "/" + count);
}
}
}
if (cursor.moveToFirst()) {
if (identityScope != null) {
identityScope.lock();
identityScope.reserveRoom(count);
}
try {
do {
list.add(loadCurrent(cursor, 0, false));
} while (cursor.moveToNext());
} finally {
if (identityScope != null) {
identityScope.unlock();
}
}
}
return list;
}
/** 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);
if (lock) {
identityScopeLong.put2(key, entity);
} else {
identityScopeLong.put2NoLock(key, entity);
}
attachEntity(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 © 2015 - 2025 Weber Informatics LLC | Privacy Policy