/*
* Copyright (c) 2013. wyouflf (wyouflf@gmail.com)
*
* 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 com.lidroid.xutils;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import com.lidroid.xutils.db.sqlite.*;
import com.lidroid.xutils.db.table.DbModel;
import com.lidroid.xutils.db.table.Id;
import com.lidroid.xutils.db.table.Table;
import com.lidroid.xutils.db.table.TableUtils;
import com.lidroid.xutils.exception.DbException;
import com.lidroid.xutils.util.IOUtils;
import com.lidroid.xutils.util.LogUtils;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DbUtils {
//*************************************** create instance ****************************************************
/**
* key: dbName
*/
private static HashMap<String, DbUtils> daoMap = new HashMap<String, DbUtils>();
private SQLiteDatabase database;
private DaoConfig daoConfig;
private boolean debug = false;
private boolean allowTransaction = false;
private DbUtils(DaoConfig config) {
if (config == null) {
throw new IllegalArgumentException("daoConfig may not be null");
}
this.database = createDatabase(config);
this.daoConfig = config;
}
private synchronized static DbUtils getInstance(DaoConfig daoConfig) {
DbUtils dao = daoMap.get(daoConfig.getDbName());
if (dao == null) {
dao = new DbUtils(daoConfig);
daoMap.put(daoConfig.getDbName(), dao);
} else {
dao.daoConfig = daoConfig;
}
// update the database if needed
SQLiteDatabase database = dao.database;
int oldVersion = database.getVersion();
int newVersion = daoConfig.getDbVersion();
if (oldVersion != newVersion) { //数据库升级
if (oldVersion != 0) {
DbUpgradeListener upgradeListener = daoConfig.getDbUpgradeListener();
if (upgradeListener != null) {
upgradeListener.onUpgrade(dao, oldVersion, newVersion);
} else {
try {
dao.dropDb();
} catch (DbException e) {
LogUtils.e(e.getMessage(), e);
}
}
}
database.setVersion(newVersion);
}
return dao;
}
public static DbUtils create(Context context) {
DaoConfig config = new DaoConfig(context);
return getInstance(config);
}
public static DbUtils create(Context context, String dbName) {
DaoConfig config = new DaoConfig(context);
config.setDbName(dbName);
return getInstance(config);
}
public static DbUtils create(Context context, String dbDir, String dbName) {
DaoConfig config = new DaoConfig(context);
config.setDbDir(dbDir);
config.setDbName(dbName);
return getInstance(config);
}
public static DbUtils create(Context context, String dbName, int dbVersion, DbUpgradeListener dbUpgradeListener) {
DaoConfig config = new DaoConfig(context);
config.setDbName(dbName);
config.setDbVersion(dbVersion);
config.setDbUpgradeListener(dbUpgradeListener);
return getInstance(config);
}
public static DbUtils create(Context context, String dbDir, String dbName, int dbVersion, DbUpgradeListener dbUpgradeListener) {
DaoConfig config = new DaoConfig(context);
config.setDbDir(dbDir);
config.setDbName(dbName);
config.setDbVersion(dbVersion);
config.setDbUpgradeListener(dbUpgradeListener);
return getInstance(config);
}
public static DbUtils create(DaoConfig daoConfig) {
return getInstance(daoConfig);
}
public DbUtils configDebug(boolean debug) {
this.debug = debug;
return this;
}
public DbUtils configAllowTransaction(boolean allowTransaction) {
this.allowTransaction = allowTransaction;
return this;
}
public SQLiteDatabase getDatabase() {
return database;
}
public DaoConfig getDaoConfig() {
return daoConfig;
}
//*********************************************** operations ********************************************************
public void saveOrUpdate(Object entity) throws DbException {
try {
beginTransaction();
createTableIfNotExist(entity.getClass());
saveOrUpdateWithoutTransaction(entity);
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void saveOrUpdateAll(List<?> entities) throws DbException {
if (entities == null || entities.size() == 0) return;
try {
beginTransaction();
createTableIfNotExist(entities.get(0).getClass());
for (Object entity : entities) {
saveOrUpdateWithoutTransaction(entity);
}
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void replace(Object entity) throws DbException {
try {
beginTransaction();
createTableIfNotExist(entity.getClass());
execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(this, entity));
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void replaceAll(List<?> entities) throws DbException {
if (entities == null || entities.size() == 0) return;
try {
beginTransaction();
createTableIfNotExist(entities.get(0).getClass());
for (Object entity : entities) {
execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(this, entity));
}
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void save(Object entity) throws DbException {
try {
beginTransaction();
createTableIfNotExist(entity.getClass());
execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(this, entity));
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void saveAll(List<?> entities) throws DbException {
if (entities == null || entities.size() == 0) return;
try {
beginTransaction();
createTableIfNotExist(entities.get(0).getClass());
for (Object entity : entities) {
execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(this, entity));
}
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public boolean saveBindingId(Object entity) throws DbException {
boolean result = false;
try {
beginTransaction();
createTableIfNotExist(entity.getClass());
result = saveBindingIdWithoutTransaction(entity);
setTransactionSuccessful();
} finally {
endTransaction();
}
return result;
}
public void saveBindingIdAll(List<?> entities) throws DbException {
if (entities == null || entities.size() == 0) return;
try {
beginTransaction();
createTableIfNotExist(entities.get(0).getClass());
for (Object entity : entities) {
if (!saveBindingIdWithoutTransaction(entity)) {
throw new DbException("saveBindingId error, transaction will not commit!");
}
}
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void deleteById(Class<?> entityType, Object idValue) throws DbException {
if (!tableIsExist(entityType)) return;
try {
beginTransaction();
execNonQuery(SqlInfoBuilder.buildDeleteSqlInfo(this, entityType, idValue));
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void delete(Object entity) throws DbException {
if (!tableIsExist(entity.getClass())) return;
try {
beginTransaction();
execNonQuery(SqlInfoBuilder.buildDeleteSqlInfo(this, entity));
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void delete(Class<?> entityType, WhereBuilder whereBuilder) throws DbException {
if (!tableIsExist(entityType)) return;
try {
beginTransaction();
execNonQuery(SqlInfoBuilder.buildDeleteSqlInfo(this, entityType, whereBuilder));
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void deleteAll(List<?> entities) throws DbException {
if (entities == null || entities.size() == 0 || !tableIsExist(entities.get(0).getClass())) return;
try {
beginTransaction();
for (Object entity : entities) {
execNonQuery(SqlInfoBuilder.buildDeleteSqlInfo(this, entity));
}
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void deleteAll(Class<?> entityType) throws DbException {
delete(entityType, null);
}
public void update(Object entity, String... updateColumnNames) throws DbException {
if (!tableIsExist(entity.getClass())) return;
try {
beginTransaction();
execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(this, entity, updateColumnNames));
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void update(Object entity, WhereBuilder whereBuilder, String... updateColumnNames) throws DbException {
if (!tableIsExist(entity.getClass())) return;
try {
beginTransaction();
execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(this, entity, whereBuilder, updateColumnNames));
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void updateAll(List<?> entities, String... updateColumnNames) throws DbException {
if (entities == null || entities.size() == 0 || !tableIsExist(entities.get(0).getClass())) return;
try {
beginTransaction();
for (Object entity : entities) {
execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(this, entity, updateColumnNames));
}
setTransactionSuccessful();
} finally {
endTransaction();
}
}
public void updateAll(List<?> entities, WhereBuilder whereBuilder, String... updateColumnNames) throws DbException {
if (entities == null || entities.size() == 0 || !tableIsExist(entities.get(0).getClass())) return;
try {
beginTransaction();
for (Object entity : entities) {
execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(this, entity, whereBuilder, updateColumnNames));
}
setTransactionSuccessful();
} finally {
endTransaction();
}
}
@SuppressWarnings("unchecked")
public <T> T findById(Class<T> entityType, Object idValue) throws DbException {
if (!tableIsExist(entityType)) return null;
Table table = Table.get(this, entityType);
Selector selector = Selector.from(entityType).where(table.id.getColumnName(), "=", idValue);
String sql = selector.limit(1).toString();
long seq = CursorUtils.FindCacheSequence.getSeq();
findTempCache.setSeq(seq);
Object obj = findTempCache.get(sql);
if (obj != null) {
return (T) obj;
}
Cursor cursor = execQuery(sql);
if (cursor != null) {
try {
if (cursor.moveToNext()) {
T entity = (T) CursorUtils.getEntity(this, cursor, entityType, seq);
findTempCache.put(sql, entity);
return entity;
}
} catch (Throwable e) {
throw new DbException(e);
} finally {
IOUtils.closeQuietly(cursor);
}
}
return null;
}
@SuppressWarnings("unchecked")
public <T> T findFirst(Selector selector) throws DbException {
if (!tableIsExist(selector.getEntityType())) return null;
String sql = selector.limit(1).toString();
long seq = CursorUtils.FindCacheSequence.getSeq();
findTempCache.setSeq(seq);
Object obj = findTempCache.get(sql);
if (obj != null) {
return (T) obj;
}
Cursor cursor = execQuery(sql);
if (cursor != null) {
try {
if (cursor.moveToNext()) {
T entity = (T) CursorUtils.getEntity(this, cursor, selector.getEntityType(), seq);
findTempCache.put(sql, entity);
return entity;
}
} catch (Throwable e) {
throw new DbException(e);
} finally {
IOUtils.closeQuietly(cursor);
}
}
return null;
}
public <T> T findFirst(Class<T> entityType) throws DbException {
return findFirst(Selector.from(entityType));
}
@SuppressWarnings("unchecked")
public <T> List<T> findAll(Selector selector) throws DbException {
if (!tableIsExist(selector.getEntityType())) return null;
String sql = selector.toString();
long seq = CursorUtils.FindCacheSequence.getSeq();
findTempCache.setSeq(seq);
Object obj = findTempCache.get(sql);
if (obj != null) {
return (List<T>) obj;
}
List<T> result = new ArrayList<T>();
Cursor cursor = execQuery(sql);
if (cursor != null) {
try {
while (cursor.moveToNext()) {
T entity = (T) CursorUtils.getEntity(this, cursor, selector.getEntityType(), seq);
result.add(entity);
}
findTempCache.put(sql, result);
} catch (Throwable e) {
throw new DbException(e);
} finally {
IOUtils.closeQuietly(cursor);
}
}
return result;
}
public <T> List<T> findAll(Class<T> entityType) throws DbException {
return findAll(Selector.from(entityType));
}
public DbModel findDbModelFirst(SqlInfo sqlInfo) throws DbException {
Cursor cursor = execQuery(sqlInfo);
if (cursor != null) {
try {
if (cursor.moveToNext()) {
return CursorUtils.getDbModel(cursor);
}
} catch (Throwable e) {
throw new DbException(e);
} finally {
IOUtils.closeQuietly(cursor);
}
}
return null;
}
public DbModel findDbModelFirst(DbModelSelector selector) throws DbException {
if (!tableIsExist(selector.getEntityType())) return null;
Cursor cursor = execQuery(selector.limit(1).toString());
if (cursor != null) {
try {
if (cursor.moveToNext()) {
return CursorUtils.getDbModel(cursor);
}
} catch (Throwable e) {
throw new DbException(e);
} finally {
IOUtils.closeQuietly(cursor);
}
}
return null;
}
public List<DbModel> findDbModelAll(SqlInfo sqlInfo) throws DbException {
List<DbModel> dbModelList = new ArrayList<DbModel>();
Cursor cursor = execQuery(sqlInfo);
if (cursor != null) {
try {
while (cursor.moveToNext()) {
dbModelList.add(CursorUtils.getDbModel(cursor));
}
} catch (Throwable e) {
throw new DbException(e);
} finally {
IOUtils.closeQuietly(cursor);
}
}
return dbModelList;
}
public List<DbModel> findDbModelAll(DbModelSelector selector) throws DbException {
if (!tableIsExist(selector.getEntityType())) return null;
List<DbModel> dbModelList = new ArrayList<DbModel>();
Cursor cursor = execQuery(selector.toString());
if (cursor != null) {
try {
while (cursor.moveToNext()) {
dbModelList.add(CursorUtils.getDbModel(cursor));
}
} catch (Throwable e) {
throw new DbException(e);
} finally {
IOUtils.closeQuietly(cursor);
}
}
return dbModelList;
}
public long count(Selector selector) throws DbException {
Class<?> entityType = selector.getEntityType();
if (!tableIsExist(entityType)) return 0;
Table table = Table.get(this, entityType);
DbModelSelector dmSelector = selector.select("count(" + table.id.getColumnName() + ") as count");
return findDbModelFirst(dmSelector).getLong("count");
}
public long count(Class<?> entityType) throws DbException {
return count(Selector.from(entityType));
}
//******************************************** config ******************************************************
public static class DaoConfig {
private Context context;
private String dbName = "xUtils.db"; // default db name
private int dbVersion = 1;
private DbUpgradeListener dbUpgradeListener;
private String dbDir;
public DaoConfig(Context context) {
this.context = context.getApplicationContext();
}
public Context getContext() {
return context;
}
public String getDbName() {
return dbName;
}
public void setDbName(String dbName) {
if (!TextUtils.isEmpty(dbName)) {
this.dbName = dbName;
}
}
public int getDbVersion() {
return dbVersion;
}
public void setDbVersion(int dbVersion) {
this.dbVersion = dbVersion;
}
public DbUpgradeListener getDbUpgradeListener() {
return dbUpgradeListener;
}
public void setDbUpgradeListener(DbUpgradeListener dbUpgradeListener) {
this.dbUpgradeListener = dbUpgradeListener;
}
public String getDbDir() {
return dbDir;
}
/**
* set database dir
*
* @param dbDir If dbDir is null or empty, use the app default db dir.
*/
public void setDbDir(String dbDir) {
this.dbDir = dbDir;
}
}
public interface DbUpgradeListener {
public void onUpgrade(DbUtils db, int oldVersion, int newVersion);
}
private SQLiteDatabase createDatabase(DaoConfig config) {
SQLiteDatabase result = null;
String dbDir = config.getDbDir();
if (!TextUtils.isEmpty(dbDir)) {
File dir = new File(dbDir);
if (dir.exists() || dir.mkdirs()) {
File dbFile = new File(dbDir, config.getDbName());
result = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
}
} else {
result = config.getContext().openOrCreateDatabase(config.getDbName(), 0, null);
}
return result;
}
//***************************** private operations with out transaction *****************************
private void saveOrUpdateWithoutTransaction(Object entity) throws DbException {
Table table = Table.get(this, entity.getClass());
Id id = table.id;
if (id.isAutoIncrement()) {
if (id.getColumnValue(entity) != null) {
execNonQuery(SqlInfoBuilder.buildUpdateSqlInfo(this, entity));
} else {
saveBindingIdWithoutTransaction(entity);
}
} else {
execNonQuery(SqlInfoBuilder.buildReplaceSqlInfo(this, entity));
}
}
private boolean saveBindingIdWithoutTransaction(Object entity) throws DbException {
Class<?> entityType = entity.getClass();
Table table = Table.get(this, entityType);
Id idColumn = table.id;
if (idColumn.isAutoIncrement()) {
execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(this, entity));
long id = getLastAutoIncrementId(table.tableName);
if (id == -1) {
return false;
}
idColumn.setAutoIncrementId(entity, id);
return true;
} else {
execNonQuery(SqlInfoBuilder.buildInsertSqlInfo(this, entity));
return true;
}
}
//************************************************ tools ***********************************
private long getLastAutoIncrementId(String tableName) throws DbException {
long id = -1;
Cursor cursor = execQuery("SELECT seq FROM sqlite_sequence WHERE name='" + tableName + "'");
if (cursor != null) {
try {
if (cursor.moveToNext()) {
id = cursor.getLong(0);
}
} catch (Throwable e) {
throw new DbException(e);
} finally {
IOUtils.closeQuietly(cursor);
}
}
return id;
}
public void createTableIfNotExist(Class<?> entityType) throws DbException {
if (!tableIsExist(entityType)) {
SqlInfo sqlInfo = SqlInfoBuilder.buildCreateTableSqlInfo(this, entityType);
execNonQuery(sqlInfo);
String execAfterTableCreated = TableUtils.getExecAfterTableCreated(entityType);
if (!TextUtils.isEmpty(execAfterTableCreated)) {
execNonQuery(execAfterTableCreated);
}
}
}
public boolean tableIsExist(Class<?> entityType) throws DbException {
Table table = Table.get(this, entityType);
if (table.isCheckedDatabase()) {
return true;
}
Cursor cursor = execQuery("SELECT COUNT(*) AS c FROM sqlite_master WHERE type='table' AND name='" + table.tableName + "'");
if (cursor != null) {
try {
if (cursor.moveToNext()) {
int count = cursor.getInt(0);
if (count > 0) {
table.setCheckedDatabase(true);
return true;
}
}
} catch (Throwable e) {
throw new DbException(e);
} finally {
IOUtils.closeQuietly(cursor);
}
}
return false;
}
public void dropDb() throws DbException {
Cursor cursor = execQuery("SELECT name FROM sqlite_master WHERE type='table' AND name<>'sqlite_sequence'");
if (cursor != null) {
try {
while (cursor.moveToNext()) {
try {
String tableName = cursor.getString(0);
execNonQuery("DROP TABLE " + tableName);
Table.remove(this, tableName);
} catch (Throwable e) {
LogUtils.e(e.getMessage(), e);
}
}
} catch (Throwable e) {
throw new DbException(e);
} finally {
IOUtils.closeQuietly(cursor);
}
}
}
public void dropTable(Class<?> entityType) throws DbException {
if (!tableIsExist(entityType)) return;
String tableName = TableUtils.getTableName(entityType);
execNonQuery("DROP TABLE " + tableName);
Table.remove(this, entityType);
}
public void close() {
String dbName = this.daoConfig.getDbName();
if (daoMap.containsKey(dbName)) {
daoMap.remove(dbName);
this.database.close();
}
}
///////////////////////////////////// exec sql /////////////////////////////////////////////////////
private void debugSql(String sql) {
if (debug) {
LogUtils.d(sql);
}
}
private Lock writeLock = new ReentrantLock();
private volatile boolean writeLocked = false;
private void beginTransaction() {
if (allowTransaction) {
database.beginTransaction();
} else {
writeLock.lock();
writeLocked = true;
}
}
private void setTransactionSuccessful() {
if (allowTransaction) {
database.setTransactionSuccessful();
}
}
private void endTransaction() {
if (allowTransaction) {
database.endTransaction();
}
if (writeLocked) {
writeLock.unlock();
writeLocked = false;
}
}
public void execNonQuery(SqlInfo sqlInfo) throws DbException {
debugSql(sqlInfo.getSql());
try {
if (sqlInfo.getBindArgs() != null) {
database.execSQL(sqlInfo.getSql(), sqlInfo.getBindArgsAsArray());
} else {
database.execSQL(sqlInfo.getSql());
}
} catch (Throwable e) {
throw new DbException(e);
}
}
public void execNonQuery(String sql) throws DbException {
debugSql(sql);
try {
database.execSQL(sql);
} catch (Throwable e) {
throw new DbException(e);
}
}
public Cursor execQuery(SqlInfo sqlInfo) throws DbException {
debugSql(sqlInfo.getSql());
try {
return database.rawQuery(sqlInfo.getSql(), sqlInfo.getBindArgsAsStrArray());
} catch (Throwable e) {
throw new DbException(e);
}
}
public Cursor execQuery(String sql) throws DbException {
debugSql(sql);
try {
return database.rawQuery(sql, null);
} catch (Throwable e) {
throw new DbException(e);
}
}
/////////////////////// temp cache ////////////////////////////////////////////////////////////////
private final FindTempCache findTempCache = new FindTempCache();
private class FindTempCache {
private FindTempCache() {
}
/**
* key: sql;
* value: find result
*/
private final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<String, Object>();
private long seq = 0;
public void put(String sql, Object result) {
if (sql != null && result != null) {
cache.put(sql, result);
}
}
public Object get(String sql) {
return cache.get(sql);
}
public void setSeq(long seq) {
if (this.seq != seq) {
cache.clear();
this.seq = seq;
}
}
}
}