sqlite之复习
一.SQlite基本使用
定义一个类继承SQLiteOpenHelper即可完成数据的创建和更新操作。
android 提供数据库操作的帮助类SQLiteOpenHelper,定义一个类继承SQLiteOpenHelper即可完成数据的创建和更新操作。
1、创建SQLiteOpenHelper
public class SqliteHelper extends SQLiteOpenHelper {
public SqliteHelper(Context context) {
// 版本号:1 (初始化的设置成1,当这个版本号升级之后会触发onUpgrade函数)
super(context, "sqltest.db", null, 1);
}
//数据库第一次创建的时候调用,适合创建表结构
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("sql语句");
}
//数据库版本变更之后会执行,适合更改表的结构
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
1.1 创建数据库流程:
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
//构造器, 数据库名字name,操作数据库的Cursor对象,版本号version。
public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
//自定义的构造器
public MySQLiteOpenHelper(Context context, String name) {
this(context, name, null, 1);//传入Context和数据库的名称,调用上面那个构造器
}
public void onCreate(SQLiteDatabase sqLiteDatabase) {
//在创建数据库时,创建一个数据表table
String sql = "create table if not exists user(id integer primary key autoincrement, name varchar(20), passwords varchar(20))";
sqLiteDatabase.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
//用于升级数据库,只需要在创建本类对象时传入一个比之前创建传入的version大的数即可。
}
}
创建数据库:
MySQLiteOpenHelper mySQLiteOpenHelper = new MySQLiteOpenHelper(getApplicationContext(), "create_db");//数据库名称为create_db。
SQLiteDatabase db = mySQLiteOpenHelper.getWritableDatabase();
2、获取SQLiteDatabase对象操作数据库
getReadableDatabase 和 getWritableDatabase 区别?
1.都可以对数据库进行读写
2.当磁盘满了之后, getWritableDatabase会发送异常;getReadableDatabase不会发送异常,直接返回一个可读的SQLiteDatabase对象。
SqliteHelper sqliteHelper=new SqliteHelper(context);
//打开或者创建数据库,如果是第一次就是创建
SQLiteDatabase readableDatabase=sqliteHelper.getReadableDatabase();
//打开或者创建数据库,如果是第一次就是创建
SQLiteDatabase writableDatabase=sqliteHelper.getWritableDatabase();
getReadableDatabase 和 getWritableDatabase 区别?
a.都可以对数据库进行读写
b.当磁盘满了之后getWritableDatabase会发送异常,getReadableDatabase不会发送异常,直接返回一个可读的SQLiteDatabase对象。
3、数据库增删改查()
1.1 sqlite语句的2种方式:
1)执行SQL语句实现增删改查 和使用安卓的api实现增删改查。
insert into person (name, phone, money) values ('张三', '159874611', 2000);//插入
delete from person where name = '李四' and _id = 4;//删除
update person set money = 6000 where name = '李四';//更新
select name, phone from person where name = '张三' order by name,salary ASC;//查询
alter table t_user add c_money float; //升级
create table t_user(uid integer primary key not null,c_name varchar(20),c_age integer,c_phone varchar(20))";//创建表
drop table database_name.table_name; //删除表
db.execSQL("insert into person (name, phone, money) values (?, ?, ?);", new Object[]{"张三", 15987461, 75000});
Cursor cursor = db.rawQuery("select _id, name, money from person where name = ?;", new String[]{"张三"});
Sql增删改查语句:
--表重命名和 添加列:
用来重命名已有的表的 ALTER TABLE 的基本语法如下:
ALTER TABLE table_name rename to new_table_name;
用来在已有的表中添加一个新的列的 ALTER TABLE 的基本语法如下:
ALTER TABLE table_name ADD COLUMN column_def...;
--删除表和清空表:
可以使用 SQLite 的 DELETE 命令从已有的表中删除全部的数据,但建议使用DROP TABLE命令删除整个表,然后再重新创建一遍。
sqlite> DELETE FROM table_name;
sqlite> DROP TABLE table_name;
如果您使用 DELETE TABLE 命令删除所有记录,建议使用VACUUM命令清除未使用的空间。
SQLite> DELETE FROM COMPANY;
SQLite> VACUUM;
2)使用api查询数据:
插入
//以键值对的形式保存存入的数据
ContentValues cv = new ContentValues();
cv.put("name", "刘能");
cv.put("phone", 1651646);
cv.put("money", 3500);
long i = db.insert("person", null, cv); //返回值是改行的主键,如果出错返回-1
nullColumnHack:当ContentValues为空的时候,会将nullColumnHack 作为“NULL”的属性,几乎用不到,一般为null
删除
//返回值是删除的行数
int i = db.delete("person", "_id = ? and name = ?", new String[]{"1", "张三"});
修改
ContentValues cv = new ContentValues();
cv.put("money", 25000);
int i = db.update("person", cv, "name = ?", new String[]{"赵四"});
查询
db.query(String table,String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit):
参数名 作用
table 表名
columns 查询的字段
selection 查询条件
selectionArgs 填充查询条件的占位符,条件中?对应的值
groupBy 分组查询参数
having 分组查询条件
orderBy 排序字段和规则,“字段名 desc/asc”
limit 分页查询,“m,n”,m表示从第几条开始查,n表示一个查询多少条数据
limit分页查询
select * from blacknumber limit pagesize offset startindex;
pageSize每页有多少条数据,startIndex从哪条数据开始
query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit):
String table是操作的数据表的名称;String selection是筛选的字段选项;String[] selectionArgs是字段选项对应的值;
String groupBy是筛选结果的分组依据;String having是在由groupBy子句创建的分组上设置条件;
String orderBy是结果的排序方式,String limit是筛选结果的显示限制,
例如“2, 3”是指从筛选结果的第2个开始显示3个。
----------
增
SQLiteDatabase的insert(String table, String nullColumnHack, ContentValues values)方法插入数据。
String table:数据表的名称。
String nullColumnHack:用于我们在未指定添加数据的情况下,为数据表中可以添加null值的数据填入null值。一般我们传入null。
ContentValues values:用于传递数据,通常我们通过ContentValues 类的对象的putXXX()方法封装数据,然后将数据添加进数据库。
ContentValues 类,类似于java中的Map,以键值对的方式保存数据。
ContentValues value = new ContentValues();
value.put("name", "张三");
value.put("passwords", "123456");
//db数据库对象在前面已经创建,这里直接使用。
db.insert("user",null, value);
删
SQLiteDatabase的delete(String table, String whereClause, String[] whereArgs)删除数据。
String table:操作的数据表的名称。
String whereClause:约束删除行的条件。相当于SQLite语句中“where name=?“内容。
String[] whereArgs:与前一个参数对应约束删除行的条件。相当于”where name=”张三““中的”张三“。
注意:如果参数String whereClause和参数String[] whereArgs都传null的话,就是删除所有行。
//db数据库对象在前面已经创建,这里直接使用。
db.delete("user", "name=?", new String[]{"张三"});
改
SQLiteDatabase的 update (String table, ContentValues values, String whereClause, String[] whereArgs)删除数据。
String table:操作的数据表的名称。
ContentValues values:用于传递数据,通常我们通过ContentValues 类的对象的putXXX()方法封装数据,然后将数据添加进数据库。
String whereClause:约束修改行的条件。相当于SQLite语句中“where name=?“内容。
String[] whereArgs:与前一个参数对应约束删除行的条件。相当于”where name=”张三““中的”张三“。
//db数据库对象在前面已经创建,这里直接使用。
ContentValues values = new ContentValues();
values.put("passwords", "abcd");
db.update("user", values, "name=?", new String[]{"张三"});
查
对于”查“操作,SQLiteDatabase提供了多种方法。
我们先列举一条SQLite中的修改语句:SELECT passwords="123" FROM user。
(1)使用SQL语句进行查询。SQLiteDatabase提供了方法:
rawQuery (String sql, String[] selectionArgs):该方法返回Cursor类的对象,用于操作查询的结果。
String sql = "select * from user";
Cursor cursor = db.rawQuery(sql, null);
cursor.moveToFirst();//转移到结果的第一行
while(!cursor.isAfterLast()){
String name=cursor.getString(cursor.getColumnIndex("name"));
String passwords=cursor.getString(cursor.getColumnIndex("passwords"));
cursor.moveToNext();
}
(2)使用SQLiteDatabase内定方法查询:
query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit):
String table是操作的数据表的名称;String selection是筛选的字段选项;String[] selectionArgs是字段选项对应的值;String groupBy是筛选结果的分组依据;String having是在由groupBy子句创建的分组上设置条件;String orderBy是结果的排序方式,String limit是筛选结果的显示限制,
例如“2, 3”是指从筛选结果的第2个开始显示3个。
Cursor cursor=db.query("user", null, null, null, null,null, " id desc", "2,3");//limit语句 offset, num
cursor.moveToFirst();//转移到结果的第一行
while(!cursor.isAfterLast()){
String name=cursor.getString(cursor.getColumnIndex("name"));
String passwords=cursor.getString(cursor.getColumnIndex("passwords"));
Log.d("data", " name=" + name + " password=" + passwords);
cursor.moveToNext();
}
1)数据库的常用的语句: 待续....
2).android方式的增删改查:
增:long row= writableDatabase.insert("info",null,contentValues);
返回值:代表添加这个新行的Id ,-1代表添加失败; nullColumnHack可以为空,表示添加一个空行
删: int totalRow=writableDatabase.delete("info",whereClause,whereArgs); 返回值:成功删除多少行
改: int totalRow=writableDatabase.update("info",contentValues,whereClause,whereArgs); 返回值:成功修改多少行
查:Cursor cursor = writableDatabase.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
//table:表名, columns:查询的列名, 如果null代表查询所有列;selection:查询条件, selectionArgs:条件占位符的参数值,
//groupBy:按什么字段分组, having:分组的条件, orderBy:按什么字段排序
Cursor cursor = db.query("info", new String[]{"_id","name","phone"}, "name = ?", new String[]{name}, null, null, "_id desc");
增加数据:
public boolean insertData(){
SQLiteDatabase writableDatabase=sqliteHelper.getWritableDatabase();
ContentValues contentValues=new ContentValues();
contentValues.put("name","王五");
contentValues.put("age","13");
//参数1:表名, 参数2:默认填写null, 参数3:插入的数据,列名+value
long row= writableDatabase.insert("info",null,contentValues);
Log.e(TAG,"insertData row="+row);
//row更新的行数
writableDatabase.close();
if(row>0)
return true;
else
return false;
}
删除数据:
public boolean delete(){
SQLiteDatabase writableDatabase=sqliteHelper.getWritableDatabase();
String whereClause="name=?";
String[] whereArgs=new String[]{"王五"};
//参1:表名,参2:where条件, 参3:where条件的占位符参数
int totalRow=writableDatabase.delete("info",whereClause,whereArgs);
Log.e(TAG,"delete totalRow="+totalRow);
//totalRow 更新的行数
writableDatabase.close();
if(totalRow>0)
return true;
else
return false;
}
查询数据:
public boolean select() {
SQLiteDatabase writableDatabase = sqliteHelper.getWritableDatabase();
String table = "info";
String[] columns = new String[]{"name"};
String selection = "name=?";
String[] selectionArgs = new String[]{"王五"};
String groupBy = null;
String having = "";
String orderBy = "";
//参1:表名,参2:要查询的列,查询所有直接填null,参3:查询条件, 参4:查询条件的占位符参数
//参5:分组,参6:条件,参7:排序
Cursor cursor = writableDatabase.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
if (cursor != null && cursor.getCount() > 0) {
while (cursor.moveToNext()) {
String strName = cursor.getString(0);
}
}
writableDatabase.close();
return true;
}
更新数据:
public boolean update(){
SQLiteDatabase writableDatabase=sqliteHelper.getWritableDatabase();
ContentValues contentValues=new ContentValues();
contentValues.put("name","王五");
contentValues.put("age","15");
String whereClause="name=?";
String[] whereArgs=new String[]{"王五"};
//参1:表名, 参2:更新的列名, 参3:where条件, 参4:where条件的占位符参数
int totalRow=writableDatabase.update("info",contentValues,whereClause,whereArgs);
Log.e(TAG,"update totalRow="+totalRow);
//totalRow 更新的行数
writableDatabase.close();
if(totalRow>0)
return true;
else
return false;
}
3.数据库的升级?
public class SqliteHelper extends SQLiteOpenHelper {
// 数据库版本变更之后会执行,适合更改表的结构
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
}
在onUpdate(db, oldVersion,newVersion)中用if语句一层层根据数据库版本的不同做不同操作。
if()..
if()..
然后在onUpgrade进行逻辑判断
@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
if(oldVersion < 2){
DatabaseUtil.upgradeTable(db,connectionSource,A.class,DatabaseUtil.OPERATION_TYPE.ADD);
DatabaseUtil.upgradeTable(db,connectionSource,B.class,DatabaseUtil.OPERATION_TYPE.DELETE);
}
if(oldVersion < 3){
DatabaseUtil.upgradeTable(db,connectionSource,C.class,DatabaseUtil.OPERATION_TYPE.ADD);
}
onCreate(db,connectionSource);
}
这样,如果是从1升级到3,那么两个if语句都会执行。而如果从2升级到3,那么只有if(oldVersion < 3)这个分支会执行。
最后,如果只是新增全新的表D,那么只要在onCreate内多写句TableUtils.createTableIfNotExists(connectionSource, D.class); 就可以啦,不要忘记版本号要+1。
数据库升级增加表和删除表都不涉及数据迁移,但是修改表涉及到对原有数据进行迁移,升级的方法如下:
将现有表命名为临时表,创建新表,将临时表的数据导入新表,删除临时表。
如果是跨版本数据库升级,可以有两种方式,如下所示:
1)逐级升级,确定相邻版本与现在版本的差别,V1升级到V2,V2升级到V3,依次类推。
2)跨级升级,确定每个版本与现在数据库的差别,为每个case编写专门升级大代码。
public class DBservice extends SQLiteOpenHelper{
private String CREATE_BOOK = "create table book(bookId integer primarykey,bookName text);";
private String CREATE_TEMP_BOOK = "alter table book rename to _temp_book";
private String INSERT_DATA = "insert into book select *,'' from _temp_book";
private String DROP_BOOK = "drop table _temp_book";
public DBservice(Context context, String name, CursorFactory factory,int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (newVersion) {
case 2:
db.beginTransaction();
db.execSQL(CREATE_TEMP_BOOK);
db.execSQL(CREATE_BOOK);
db.execSQL(INSERT_DATA);
db.execSQL(DROP_BOOK);
db.setTransactionSuccessful();
db.endTransaction();
break;
}
}
2.多线程操作数据库
SQLite的同步锁精确到数据库级,粒度比较大,不像别的数据库有表锁,行锁。同一个时间只允许一个连接进行写入操作。
如果有大量的数据处理,那么肯定不合适于在UI线程去操作,这时就要考虑多线程的问题了。
我们如果开一个工作线程去操作SQLite数据库,如批量地插入可能需要30秒钟,而这个时间UI线程也要从数据库读取一下数据展示给用户,那么这个时候UI线程能读取到这个数据库吗?大家可以思考一下这个问题。
在多线程中只使用一个SQLiteDatabase引用,在用SQLiteDataBase.close()的时需要注意调是否还有别的线程在使用这个实例。如果一个线程操作完成后就直接close了,别一个正在使用这个数据库的线程就会异常。所以有些人会直接把SQLiteDatabase的实例放在Application中,让它们的生命周期一致。也有的做法是写一个计数器,当计数器为0时才真正关闭数据库。
Android 如何解决数据库多线程锁的问题?多线程操作Sqlite问题?
Sqlite数据库本身是不支持多线程同时操作的。
如果使用多个线程来操作Sqlite,可能会遇到像这样的问题:一个线程中使用完db之后直接调用 了db.close(),但是由于在一个sqlite数据库中,得到的SqliteDatabase对象是同一个。
所以如果一个线程掉用了db.close,那么其他线程中的db对象也就是close的了。此时,如果其他线程再操作数据库就会出现:database not open错误!
方法1:(原创)android Sqlite多线程访问异常解决方案
在多线程访问数据库的时候会出现这样的异常:
java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
或 java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase:
或java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase:
总结:Sqlite自身是不支持多线程同时操作的,给出一个解决方案并列出一些项目中用到的代码。
我们会用到AtomicInteger,一个提供原子操作的Integer的类,也用synchronized关键字。
而AtomicInteger则通过一种线程安全的加减操作接口,由此我们可以做一个DatabaseManager这样的类,具体下面代码块:
public class DatabaseManager {
private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
private AtomicInteger mOpenCounter = new AtomicInteger();
public static synchronized DatabaseManager getInstance(SQLiteOpenHelper helper) {
if (instance == null) {
initializeInstance(helper);
}
return instance;
}
public static synchronized void initializeInstance(SQLiteOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
}
}
public synchronized SQLiteDatabase getWritableDatabase() {
if(mOpenCounter.incrementAndGet() == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getWritableDatabase();
}
return mDatabase;
}
public synchronized SQLiteDatabase getReadableDatabase() {
if(mOpenCounter.incrementAndGet() == 1) {
// Opening new database
mDatabase = mDatabaseHelper.getReadableDatabase();
}
return mDatabase;
}
public synchronized void closeDatabase() {
if(mOpenCounter.decrementAndGet() == 0) {
// Closing database
mDatabase.close();
}
}
在我们进行关闭数据库的时候判断
mOpenCounter.decrementAndGet() == 0(更新器管理的给定对象的字段的当前值为0)的时候才正式关闭数据库,就不会出现上述异常。
操作数据库逻辑代码如下:
首先要取得mDatabaseManager = DatabaseManager.getInstance(mContext);
// 判断表中是否有值
public boolean isExistTabValus() {
boolean flag = false;
SQLiteDatabase db =mDatabaseManager.getReadableDatabase();//获取一个可读的数据库对象
Cursor curcor = null;
try {
curcor = db.rawQuery("select * from tab ", null);
while (curcor.moveToNext()) {
if (curcor.getCount() > 0) {
flag = true;
}
}
} catch (Exception e) {
} finally {
if (curcor != null) {
curcor.close();
}
mDatabaseManager.closeDatabase();//关闭数据库
}
return flag;
}
上面提供一个使用方法,现在项目中使用这种方法关于数据库的操作从未没有出现并发的问题,大家可以尝试一下。
1.1 Android多线程下安全访问数据库? 1. 问题一:每创建一个 SQLiteOpenHelper 对象时,实际上也是在新建一个数据库连接。如果尝试通过多个连接同时对数据库进行写数据操作,其一定会失败。
报android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5) 解决方式: 为确保在[多线程](https://so.csdn.net/so/search?q=多线程&spm=1001.2101.3001.7020)中安全地操作数据库,需保证只有一个数据库连接被占用,
写一个管理单个SQLiteOpenHelper 对象的单例 DatabaseManager 。 2.问题二:报java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase 解决方式:可线程安全的使用数据库连接,用 AtomicInteger来处理并发时候SQLiteDatabase mDatabase的获取的情况。 当需要使用数据库连接,调用类 DatabaseManager的openDatabase() (内置一个标志数据库被打开多少次的计数器。如果计数为1,代表需要打开一个新的数据库连接,否则数据库连接已经存在); 在调用 closeDatabase() 中 计数器都会递减,直到计数为0,就需要关闭数据库连接了。 { public synchronized SQLiteDatabase openDatabase() { if(mOpenCounter.incrementAndGet() == 1) { // Opening new database mDatabase = mDatabaseHelper.getWritableDatabase(); } return mDatabase; } public synchronized void closeDatabase() { if(mOpenCounter.decrementAndGet() == 0) { // Closing database mDatabase.close(); } } } 详细如下: 文章中引用的项目代码请 [点击这里](https://github.com/dmytrodanylyk/android-concurrent-database),假设你已编写了自己的 [SQLiteOpenHelper]
(http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html)。public class DatabaseHelper extends SQLiteOpenHelper { ... } 现在你想在不同的线程中对数据库进行写数据操作: // Thread 1 Context context = getApplicationContext(); DatabaseHelper helper = new DatabaseHelper(context); SQLiteDatabase database = helper.getWritableDatabase(); database.insert(…); database.close(); // Thread 2 Context context = getApplicationContext(); DatabaseHelper helper = new DatabaseHelper(context); SQLiteDatabase database = helper.getWritableDatabase(); database.insert(…); database.close(); 然后报错信息如下,而你的写数据操作将会无效。 android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5) 问题源于你每创建一个SQLiteOpenHelper 对象时,实际上也是在新建一个数据库连接。尝试通过多个连接同时对数据库进行写数据操作,其一定会失败。 为确保在[多线程](https://so.csdn.net/so/search?q=多线程&spm=1001.2101.3001.7020)中安全地操作数据库,需保证只有一个数据库连接被占用。
先编写一个负责管理单个 SQLiteOpenHelper 对象的单例DatabaseManager 。 public class DatabaseManager { private static DatabaseManager instance; private static SQLiteOpenHelper mDatabaseHelper; public static synchronized void initialize(Context context, SQLiteOpenHelper helper) { if (instance == null) { instance = new DatabaseManager(); mDatabaseHelper = helper; } } public static synchronized DatabaseManager getInstance() { if (instance == null) { throw new IllegalStateException(DatabaseManager.class.getSimpleName() + " is not initialized, call initialize(..) method first."); } return instance; } public synchronized SQLiteDatabase getDatabase() { return new mDatabaseHelper.getWritableDatabase(); } } 为了能在多线程中进行写数据操作,我们得修改一下代码,具体如下: // In your application class DatabaseManager.initializeInstance(getApplicationContext()); // Thread 1 DatabaseManager manager = DatabaseManager.getInstance(); SQLiteDatabase database = manager.getDatabase() database.insert(…); database.close(); // Thread 2 DatabaseManager manager = DatabaseManager.getInstance(); SQLiteDatabase database = manager.getDatabase() database.insert(…); database.close(); 然后又导致另个崩毁 java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase 既然只有一个数据库连接,Thread1 和 Thread2 对getDatabase() 的调用就会取得一样的 SQLiteDatabase 对象实例。 当 Thread1 尝试管理数据库连接时,Thread2 却仍然在使用该数据库连接,这也就是导致 IllegalStateException 崩毁的原因。 因此我们只能在确保数据库没有再被占用的情况下,才去关闭它。 在 [stackoveflow](http://stackoverflow.com/) 上有一些讨论推荐“永不关闭”你的 SQLiteDatabase ,会报错如下。 Leak foundCaused by: java.lang.IllegalStateException: SQLiteDatabase created and never closed 示例: public class DatabaseManager { private AtomicInteger mOpenCounter = new AtomicInteger(); private static DatabaseManager instance; private static SQLiteOpenHelper mDatabaseHelper; private SQLiteDatabase mDatabase; public static synchronized void initializeInstance(SQLiteOpenHelper helper) { if (instance == null) { instance = new DatabaseManager(); mDatabaseHelper = helper; } } public static synchronized DatabaseManager getInstance() { if (instance == null) { throw new IllegalStateException(DatabaseManager.class.getSimpleName() + " is not initialized, call initializeInstance(..) method first."); } return instance; } public synchronized SQLiteDatabase openDatabase() { if(mOpenCounter.incrementAndGet() == 1) { // Opening new database mDatabase = mDatabaseHelper.getWritableDatabase(); } return mDatabase; } public synchronized void closeDatabase() { if(mOpenCounter.decrementAndGet() == 0) { // Closing database mDatabase.close(); } } } 然后你可以怎样子去调用它: SQLiteDatabase database = DatabaseManager.getInstance().openDatabase(); database.insert(...); // database.close(); Don't close it directly! DatabaseManager.getInstance().closeDatabase(); // correct way 你应该用 AtomicInteger 来处理并发的情况。 可以线程安全地使用你的数据库连接了。 以后当你需要使用数据库连接,可通过调用类 DatabaseManager的openDatabase()。此方法内置一个标志数据库被打开多少次的计数器。如果计数为1,代表我们需要打开一个新的数据库连接,
否则,数据库连接已经存在。 在方法 closeDatabase() 中,每次调用 closeDatabase() ,计数器都会递减,直到计数为0,就需要关闭数据库连接了。
方法2:防止多个线程又是读取又是写入。网上找到的方法:
解决的办法就是keep single sqlite connection,保持单个SqliteOpenHelper实例,同时对所有数据库操作的方法添加synchronized关键字。
完美解决sqlite的 database locked 或者是 error 5: database locked 问题。
意思就是对保存删除或者此类数据库操作的最上层的方法加锁,这样就能防止数据库被同一时间不同地方调用了。
然后单例模式也可以解决数据库同时读写引起的错误。
缺点是第一次加载时反映稍慢,但是能在绝大多数保证对象的唯一性;
比如使用如下DCL模式
public class XutilsHelper {
private DbManager db;
private static XutilsHelper sInstance = null ;
public static XutilsHelper getInstance(){
if(sInstance == null){
synchronized (XutilsHelper.class){
if(sInstance == null ){
sInstance = new XutilsHelper();
}
}
}
return sInstance;
}
private XutilsHelper(){};
//相关操作代码省略
public ..............
}
1、数据库的四大特征,数据库的隔离级别?
事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。
如银行转账工作:从一个账号扣款并使另一个账号增款,这两个操作要么都执行,要么都不执行。所以,应该把它们看成一个事务。
事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据一致性。事务具有以下4个基本特征:
数据库的四大特征:
(1)原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。
(2)一致性(Consistency)
一个事务执行之前和执行之后都必须处于一致性状态。
(3)隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
(4)持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的。
数据库的事务
什么是事务: 要么同时成功,要么同时失败。事务:银行转账
介绍:使用SQLiteDatabase的beginTransaction()开启一个事务,程序执行到endTransaction() 时会检查事务的标志是否为成功,
如果程序执行到endTransaction()之前调用了setTransactionSuccessful() 设置事务的标志为成功则提交事务;如果没有调用setTransactionSuccessful() 方法则回滚事务。
下面两条SQL语句在同一个事务中执行。
public void update2(){
SQLiteDatabase db=sqliteHelper.getWritableDatabase();
try {
db.beginTransaction();//开启事务
db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"传智播客", 4});
db.execSQL("update person set name=? where personid=?", new Object[]{"传智", 1});
// 设置事务成功的标记, 设置事务成功之后才会提交所有sql执行的结果。
// 如果没有设置事务成功的标记,在endTransaction之后将会自动回滚事务
db.setTransactionSuccessful(); //调用此方法会在执行到endTransaction() 时提交当前事务,如果不调用此方法会回滚事务
}catch (Exception ex){
//出现异常自动回滚事务
}finally {
// 必须关闭事务,告诉数据库系统结束. 防止Sqlite被挂掉。
db.endTransaction();//事务的结束的标志,决定是提交事务还是回滚事务
}
}
12.Sqlite事务介绍:
概念:SQLiteDatabase的beginTransaction()可以开启一个事务,程序执行到endTransaction() 时会检查事务的标志是否为成功,
如果程序执行到endTransaction()之前调用了setTransactionSuccessful() 设置事务的标志为成功,则所有从beginTransaction()开始的操作都会被提交;
如果没有调用setTransactionSuccessful()则回滚事务。
实例1:
应用程序初始化时需要批量的向sqlite中插入大量数据,单独的使用for+Insert方法导致应用响应缓慢,因为 sqlite插入数据的时候默认一条语句就是一个事务,有多少条数据就有多少次磁盘操作。我的应用初始5000条记录也就是要5000次读写磁盘操作。
而且不能保证所有数据都能同时插入。(有可能部分插入成功,另外一部分失败,后续还得删除。太麻烦)
解决方法:添加事务处理,把5000条插入作为一个事务
db.beginTransaction(); //手动设置开始事务
try{
//批量处理操作
for(Collection c:colls){
insert(db, c);
}
db.setTransactionSuccessful(); //设置事务处理成功,不设置会自动回滚不提交。
//在setTransactionSuccessful和endTransaction之间不进行任何数据库操作
}catch(Exception e){
MyLog.printStackTraceString(e);
}finally{
db.endTransaction(); //处理完成
}
实例2:下面两条SQL语句在同一个事务中执行。
//银行账户事务测试
public void payment()
{
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
db.beginTransaction();
try{
db.execSQL("update person set amount=amount-10 where personid=?", new Object[]{1});
db.execSQL("update person set amount=amount+10 where personid=?", new Object[]{2});
//设置事务标志为成功,当结束事务时就会提交事务
db.setTransactionSuccessful();
}
catch(Exception e){}
finally{
db.endTransaction();
}
}
11.android SQLite 批量插入数据慢的解决方案 (针对于不同的android api 版本)
SQLite,是一款轻型的数据库,用到嵌入式的产品中,因为占用的资源非常少,二其中的操作方式几乎和我们接触的数据库不多,甚至只有几百K的他自然会被需求者青睐,下面讲一下在这样的轻型数据库中怎么对他进行一些读写操作。
之前做选择联系人的时候出现如果一个手机里联系人超过2000的话,往数据库里面插入会非常耗时,不同的手机存储的条数不同,这个存储的数量和手机的内存有很大的关系,往往取决于手机内存,下面对于数据量大的情况来写一下sqlite的批量查询。
SqLite 插入数据有几种
总结: 以上的几种方式都用到了数据库中的事务这个东西,sqlite语句在其中只会走一次,其他的就是数据循环到数据库中的对象里,这样比以前用对象插入,再用for在外围循环快的不知道多少倍,之前插入2000多条数据300多毫秒,以后对于上万条数据也是非常之快的。
第1种 :同样在 SQLiteDatabase 中
public void inertOrUpdateDateBatch(List<String> sqls) { SQLiteDatabase db = getWritableDatabase(); db.beginTransaction(); try { for (String sql : sqls) { db.execSQL(sql); } // 设置事务标志为成功,当结束事务时就会提交事务 db.setTransactionSuccessful(); } catch (Exception e) { e.printStackTrace(); } finally { // 结束事务 db.endTransaction(); db.close(); } }
第2种:SQLiteDatabase db.insert("table_name", null, contentValues) 中也可以批量插入
public void insertData(插入数据){ db.beginTransaction(); // 手动设置开始事务 for (ContentValues v : list) { db.insert("表名", null, v); } db.setTransactionSuccessful(); //设置事务处理成功,不设置会自动回滚不提交 db.endTransaction(); db.close() }
第3种 : SQLiteStatement 个人比较喜欢用这种方式,对数据的处理看很清楚明
String sql = "insert into表名(对应的列) values(?)"; SQLiteStatement stat = db.compileStatement(sql); db.beginTransaction(); for (数据集) { //循环所要插入的数据 } db.setTransactionSuccessful(); db.endTransaction(); db.close();