前言
Android的数据库是很方便的,但是很多的时候,Android程序员很少使用数据库,包括我在内,目前在我们的项目中还没用过数据库,但是不常用不代表不需要掌握,我们来大体总结一下
SQLite
给个链接:Android SQLite详解
再给个链接:SQLite常用语句
我自己基于用户、帖子、访客三个表写了一个Demo:数据库Demo
首先创建一个DBHelper类继承SQLiteOpenHelper类,重写onCreate()和onUpgrade()方法,这个类主要用来创建数据库和建表
public class DBHelper extends SQLiteOpenHelper {
private static final int DB_VERSION = 1;
public static final String DB_NAME = "pengboboer.db";
public static final String TABLE_NAME_USER = "t_user";
public static final String TABLE_NAME_POST = "t_post";
public static final String TABLE_NAME_VISITOR = "t_visitor";
public static final String[] USER_COLUMNS = new String[] {"id","name","age","country"};
public static final String[] POST_COLUMNS = new String[] {"postId","title","visitCount","publishTime","publisherUid"};
public static final String[] VISITOR_COLUMNS = new String[] {"id","postId","visitorUid", "visitTime"};
public DBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
String sqlUser = "create table if not exists " + TABLE_NAME_USER
+ " (id integer primary key autoincrement, name text, age integer, country text)";
String sqlPost = "create table if not exists " + TABLE_NAME_POST
+ " (postId integer primary key autoincrement, title text, visitCount integer, publishTime text, publisherUid integer, foreign key(publisherUid) references " + TABLE_NAME_USER + "(id))";
String sqlVisitor = "create table if not exists " + TABLE_NAME_VISITOR
+ " (id integer primary key, postId integer, visitorUid integer, visitTime text)";
sqLiteDatabase.execSQL(sqlUser);
sqLiteDatabase.execSQL(sqlPost);
sqLiteDatabase.execSQL(sqlVisitor);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
String sqlUser = "DROP TABLE IF EXISTS" + TABLE_NAME_USER;
String sqlPost = "DROP TABLE IF EXISTS" + TABLE_NAME_POST;
String sqlVisitor = "DROP TABLE IF EXISTS" + TABLE_NAME_VISITOR;
sqLiteDatabase.execSQL(sqlUser);
sqLiteDatabase.execSQL(sqlPost);
sqLiteDatabase.execSQL(sqlVisitor);
onCreate(sqLiteDatabase);
}
@Override
public void onOpen(SQLiteDatabase db) {
super.onOpen(db);
if(!db.isReadOnly()) {
db.execSQL("PRAGMA foreign_keys=ON;");
}
}
}
然后我们再创建一个DaoManager类用来处理一些操作方法,先看一下它的抽象类:
public interface Dao {
boolean insert(String tableName, ContentValues contentValues);
boolean delete(String tableName, int id);
boolean update(String tableName, int id, ContentValues contentValues);
/** 默认初始化往数据库插入一些数据 */
boolean initSomeData();
/** 数据库中是否存在数据 */
boolean isExistData();
/** 获取用户表所有的信息 */
List<User> getAllUserData();
/** 获取帖子表所有的信息 */
List<Post> getAllPostData();
/** 获取访客表所有的信息 */
List<Visitor> getVisitorData(int postId);
/** 获取首页帖子列表所有的信息 */
List<MainPostItem> getAllPostItem();
/** 获取帖子访客列表所有的信息 */
List<VisitorItem> getAllVisitorItem(int postId);
/** 获取某一帖子的阅读量 */
int getPostVisitCount(int postId);
}
增删改,需先调用getWritableDatabase():
1、然后调用对应的方法,传入参数
2、调用通用的execSQL(String sql),通过语句直接操作
// 核心就是try中的几行代码,记得在finally中关闭事务和db
public boolean insert(String tableName, ContentValues contentValues) {
SQLiteDatabase db = null;
try {
db = dbHelper.getWritableDatabase();
db.beginTransaction();
db.insertOrThrow(tableName, null, contentValues);
db.setTransactionSuccessful();
return true;
} catch (SQLiteConstraintException e) {
e.printStackTrace();
Log.e(TAG, "主键重复", e);
} catch (Exception e) {
Log.e(TAG, "出现异常", e);
} finally {
if (db != null) {
db.endTransaction();
db.close();
}
}
return false;
}
// 删和改类似,上面beginTransaction()下面那行改为
db.delete(tableName, "id = ?", new String[]{String.valueOf(id)});
db.update(tableName, contentValues, "id = ?", new String[]{String.valueOf(id)});
// 至于说直接用sql语句,那么举个增的例子,其它一样
db.execSQL("insert into " + DBHelper.TABLE_NAME_USER + " (id, name, age, country) values (11, '马云', 55, '中国')");
查,需先调用getReadableDatabase():
1、使用query:传入一些参数来查
2、使用rawQuery:直接用语句
// 直接查
cursor = db.query(DBHelper.TABLE_NAME_USER, DBHelper.USER_COLUMNS, null, null, null, null, null);
// 用语句查
cursor = db.rawQuery(sql,null);
我的Demo中有两个表连接查询语句比较抽象:
// 查询所有的帖子
// 查询帖子表并连用户表查询发帖人的信息
String sql = "select * from " + DBHelper.TABLE_NAME_POST + " post left join " + DBHelper.TABLE_NAME_USER + " user on post.publisherUid = user.id";
// 查询某一帖子的访客
// 从访客表中查询某一帖子的访客,并连用户表查询访客的信息
String sql = "select * from " + DBHelper.TABLE_NAME_VISITOR + " visitor left join " + DBHelper.TABLE_NAME_USER + " user on visitor.visitorUid = user.id where visitor.postId = " + postId;
线程问题
线程的问题我简单说一下,sqlite是线程安全的,线程安全意味着速度变慢,如果不需要线程安全,可以禁用互斥锁。
三种线程模型:
- 单线程模型:互斥锁被禁用,同一时间只允许一个线程访问
- 多线程模型:一个连接在同一个时间只允许一个线程访问
- 串行模型:开启所有锁,随意访问
为了保证最高的并发性,推荐:
- 使用多线程模式
- 使用WAL模式
- 单线程写,多线程读
- 避免长时间事务
- 缓存sqlite3_prepare编译结果
- 多语句通过BEGIN和COMMIT做显示事务,减少多次的自动事务消耗
对于sqlite_busy
- 有写操作时,其它写和读操作会被驳回
- 开启事务后提交事务之前,其它写操作会被驳回
- 有读操作时,写操作会被驳回,读操作之间可以并发执行
再说一下这篇文章:Concurrent database access - Dmytro Danylyk
- 如果在两个线程新建了两个数据库连接,并且这两个线程同时去写入,那么其中一项不会被写入,并且报错:数据库被锁定。
- 要在多线程中使用数据库,要确保使用同一个数据库连接,所有我们使用一个单例模式
- 即使是使用了单例,也同样会报一个新的错误:尝试重新打开一个已经关闭的对象。因为我们使用同一个数据库连接,可以线程1关闭了数据库,而线程2还在用它,所以会报错。
- 一些人建议不要关闭数据库,但是这样的话会引发内存泄漏。
- 正确的方法是:写一个open和close的方法,方法内部有一个计数器,这样来解决上面提到的问题。
- 如此一来就可以使用数据库了,并且它是线程安全的
总结
数据库简单的使用就是这些,我个人用SQLite也很少,一些常用语句在上面的链接我有给到,那个文章总结的还不错。
把那些语句掌握了,基本用起来没啥问题。
我写了个Demo,模拟帖子、用户、访客,有兴趣的,尤其Android新手可以看下:数据库Demo