目录
创建数据库
建立数据库类,并创建一个Book表。首先把创建表的SQL语句写好,然后在onCreate中执行这条语句。
class MyDatabaseHelper(val context: Context, name: String, version: Int) : SQLiteOpenHelper(context, name, null, version) {
private val createBook = "create table Book (" +
" id integer primary key autoincrement," +
"author text," +
"price real," +
"pages integer," +
"name text," +
"category_id integer)"
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBook)
Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
}
}
修改activity_main.xml中的代码,只添加一个按钮用于创建数据库。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/createDatabase"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Create Database"
/>
</LinearLayout>
最后修改MainActivity中的代码,创建数据库。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1)
createDatabase.setOnClickListener {
dbHelper.writableDatabase
}
}
}
其中MyDatabaseHelper()中的参数分别是 上下文、数据库名字、数据库版本。数据库只能创建一次,再次创建时发现已经存在该数据库了,那么就不会再次创建了。
升级数据库
假设升级后的数据库增加了一张Category表,那么就可以这么写:首先把创建Category表的SQL语句写好,然后在 onUpgrade中执行这条语句,在MainActivity中修改数据库的版本号为2,升级的逻辑就会被执行。注意,每个版本都要进行判断,不然的话,如果直接从1升到3版本,那么2版本的升级逻辑就会被略过!!
class MyDatabaseHelper(val context: Context, name: String, version: Int) : SQLiteOpenHelper(context, name, null, version) {
private val createBook = "create table Book (" +
" id integer primary key autoincrement," +
"author text," +
"price real," +
"pages integer," +
"name text," +
"category_id integer)"
private val createCategory = "create table Category (" +
"id integer primary key autoincrement," +
"category_name text," +
"category_code integer)"
override fun onCreate(db: SQLiteDatabase) {
db.execSQL(createBook)
db.execSQL(createCategory)
Toast.makeText(context, "Create succeeded", Toast.LENGTH_SHORT).show()
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
if (oldVersion <= 1) {
db.execSQL(createCategory)
}
if (oldVersion <= 2) {
db.execSQL("alter table Book add column category_id integer")
}
}
}
数据库的CRUD操作
添加数据
添加数据用的是insert方法 参数为 (表名、未添加数据的情况下为可为空的列自动赋值null、要添加的数据),首先在activity_main.xml 中添加一个id为addData的按钮,然后为这个按钮设置点击事件。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1)
createDatabase.setOnClickListener {
dbHelper.writableDatabase
}
addData.setOnClickListener {
val db = dbHelper.writableDatabase
val values1 = ContentValues().apply {
// 开始组装第一条数据
put("name", "The Da Vinci Code")
put("author", "Dan Brown")
put("pages", 454)
put("price", 16.96)
}
db.insert("Book", null, values1) // 插入第一条数据
val values2 = ContentValues().apply {
// 开始组装第二条数据
put("name", "The Lost Symbol")
put("author", "Dan Brown")
put("pages", 510)
put("price", 19.95)
}
db.insert("Book", null, values2) // 插入第二条数据
}
}
}
因为在表中的id列设置了自增,所以不需要给id列赋值,这里的values编写形式还可以继续简化,如values1可以这么写:
val values1= contentValuesOf("name" to "The Da Vinci Code","author" to "Dan Brown","pages" to 454,"price" to 16.96)
更新数据
更新数据使用的是update方法 参数为 (表名、要更新的数据、第三和第四个参数具体指定更新哪些行), 首先在activity_main.xml 中添加一个id为updateData的按钮,然后为这个按钮设置点击事件。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1)
createDatabase.setOnClickListener {
dbHelper.writableDatabase
}
addData.setOnClickListener {
val db = dbHelper.writableDatabase
val values1 = ContentValues().apply {
// 开始组装第一条数据
put("name", "The Da Vinci Code")
put("author", "Dan Brown")
put("pages", 454)
put("price", 16.96)
}
db.insert("Book", null, values1) // 插入第一条数据
val values2 = ContentValues().apply {
// 开始组装第二条数据
put("name", "The Lost Symbol")
put("author", "Dan Brown")
put("pages", 510)
put("price", 19.95)
}
db.insert("Book", null, values2) // 插入第二条数据
}
updateData.setOnClickListener {
val db = dbHelper.writableDatabase
val values = ContentValues()
values.put("price", 10.99)
val rows = db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
Toast.makeText(this, "rows is $rows", Toast.LENGTH_SHORT).show()
}
}
}
这里的values编写形式也是可以简化的。
删除数据
更新数据使用的是delete方法 参数为(表名、第二和第三个参数具体指定删除那些行), 首先在activity_main.xml 中添加一个id为deleteData的按钮,然后为这个按钮设置点击事件。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1)
createDatabase.setOnClickListener {
dbHelper.writableDatabase
}
addData.setOnClickListener {
val db = dbHelper.writableDatabase
val values1 = ContentValues().apply {
// 开始组装第一条数据
put("name", "The Da Vinci Code")
put("author", "Dan Brown")
put("pages", 454)
put("price", 16.96)
}
db.insert("Book", null, values1) // 插入第一条数据
val values2 = ContentValues().apply {
// 开始组装第二条数据
put("name", "The Lost Symbol")
put("author", "Dan Brown")
put("pages", 510)
put("price", 19.95)
}
db.insert("Book", null, values2) // 插入第二条数据
}
updateData.setOnClickListener {
val db = dbHelper.writableDatabase
val values = ContentValues()
values.put("price", 10.99)
val rows = db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
Toast.makeText(this, "rows is $rows", Toast.LENGTH_SHORT).show()
}
deleteData.setOnClickListener {
val db = dbHelper.writableDatabase
db.delete("Book", "pages > ?", arrayOf("500"))
}
}
}
查询数据
这是最复杂的一种操作,查询使用的是query方法,为了方便演示,使用最短的query重载方法,它有七个参数, 分别是(表名,查询哪几列 /null为查询所有列,第三和第四个参数约束查询哪几行/null为查询所有行,指定需要去group by的列/null为不进行group by操作,对group by的结果进行过滤/null为不进行过滤,指定查询结果的排序方式/null为使用默认排序)该方法返回一个cursor对象。 首先在activity_main.xml 中添加一个id为queryData的按钮,然后为这个按钮设置点击事件。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1)
createDatabase.setOnClickListener {
dbHelper.writableDatabase
}
addData.setOnClickListener {
val db = dbHelper.writableDatabase
val values1 = ContentValues().apply {
// 开始组装第一条数据
put("name", "The Da Vinci Code")
put("author", "Dan Brown")
put("pages", 454)
put("price", 16.96)
}
db.insert("Book", null, values1) // 插入第一条数据
val values2 = ContentValues().apply {
// 开始组装第二条数据
put("name", "The Lost Symbol")
put("author", "Dan Brown")
put("pages", 510)
put("price", 19.95)
}
db.insert("Book", null, values2) // 插入第二条数据
}
updateData.setOnClickListener {
val db = dbHelper.writableDatabase
val values = ContentValues()
values.put("price", 10.99)
val rows = db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
Toast.makeText(this, "rows is $rows", Toast.LENGTH_SHORT).show()
}
deleteData.setOnClickListener {
val db = dbHelper.writableDatabase
db.delete("Book", "pages > ?", arrayOf("500"))
}
queryData.setOnClickListener {
val db = dbHelper.writableDatabase
// 查询Book表中所有的数据
val cursor = db.query("Book", null, null, null, null, null, null)
if (cursor.moveToFirst()) {
do {
// 遍历Cursor对象,取出数据并打印
val name = cursor.getString(cursor.getColumnIndex("name"))
val author = cursor.getString(cursor.getColumnIndex("author"))
val pages = cursor.getInt(cursor.getColumnIndex("pages"))
val price = cursor.getDouble(cursor.getColumnIndex("price"))
Log.d("MainActivity", "book name is $name")
Log.d("MainActivity", "book author is $author")
Log.d("MainActivity", "book pages is $pages")
Log.d("MainActivity", "book price is $price")
} while (cursor.moveToNext())
}
cursor.close()
}
}
}
使用事务
在数据库中使用事务可以保证一系列操作要么会全部完成,要么一个都不会完成。首先在activity_main.xml 中添加一个id为replaceData的按钮,然后为这个按钮设置点击事件。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbHelper = MyDatabaseHelper(this, "BookStore.db", 1)
createDatabase.setOnClickListener {
dbHelper.writableDatabase
}
addData.setOnClickListener {
val db = dbHelper.writableDatabase
val values1 = ContentValues().apply {
// 开始组装第一条数据
put("name", "The Da Vinci Code")
put("author", "Dan Brown")
put("pages", 454)
put("price", 16.96)
}
db.insert("Book", null, values1) // 插入第一条数据
val values2 = ContentValues().apply {
// 开始组装第二条数据
put("name", "The Lost Symbol")
put("author", "Dan Brown")
put("pages", 510)
put("price", 19.95)
}
db.insert("Book", null, values2) // 插入第二条数据
}
updateData.setOnClickListener {
val db = dbHelper.writableDatabase
val values = ContentValues()
values.put("price", 10.99)
val rows = db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
Toast.makeText(this, "rows is $rows", Toast.LENGTH_SHORT).show()
}
deleteData.setOnClickListener {
val db = dbHelper.writableDatabase
db.delete("Book", "pages > ?", arrayOf("500"))
}
queryData.setOnClickListener {
val db = dbHelper.writableDatabase
// 查询Book表中所有的数据
val cursor = db.query("Book", null, null, null, null, null, null)
if (cursor.moveToFirst()) {
do {
// 遍历Cursor对象,取出数据并打印
val name = cursor.getString(cursor.getColumnIndex("name"))
val author = cursor.getString(cursor.getColumnIndex("author"))
val pages = cursor.getInt(cursor.getColumnIndex("pages"))
val price = cursor.getDouble(cursor.getColumnIndex("price"))
Log.d("MainActivity", "book name is $name")
Log.d("MainActivity", "book author is $author")
Log.d("MainActivity", "book pages is $pages")
Log.d("MainActivity", "book price is $price")
} while (cursor.moveToNext())
}
cursor.close()
}
replaceData.setOnClickListener {
val db = dbHelper.writableDatabase
db.beginTransaction() // 开启事务
try {
db.delete("Book", null, null)
// if (true) {
// // 在这里手动抛出一个异常,让事务失败
// throw NullPointerException()
// }
val values = cvOf("name" to "Game of Thrones", "author" to "George Martin", "pages" to 720, "price" to 20.85)
db.insert("Book", null, values)
db.setTransactionSuccessful() // 事务已经执行成功
} catch (e: Exception) {
e.printStackTrace()
} finally {
db.endTransaction() // 结束事务
}
}
}
}
Room的使用
Room是对数据库的原生的API进行的封装,它由3部分组成 Entity、Dao、Database。Entity可以理解为数据库中的表,Dao是对数据库操作进行的封装,Database定义了数据库中的关键信息,并提供Dao的访问实体类。
首先在软件级的gradle中添加如下依赖:
apply plugin: 'kotlin-kapt'
implementation "androidx.room:room-runtime:2.1.0"
kapt "androidx.room:room-compiler:2.1.0"
定义Entity
@Entity
data class User(var firstName: String, var lastName: String, var age: Int) {
@PrimaryKey(autoGenerate = true)
var id: Long = 0
}
在数据类前加上注解@Entity,将它生命成一个实体类。然后在id列前面加上注解@PrimaryKey(autoGenerate = true),将id声明为主键并设置自增。
定义Dao
Dao层是封装业务逻辑的地方,尽量将可能用到的CRUD操作写到这里。
@Dao
interface UserDao {
@Insert
fun insertUser(user: User): Long
@Update
fun updateUser(newUser: User)
@Query("select * from User")
fun loadAllUsers(): List<User>
@Query("select * from User where age > :age")
fun loadUsersOlderThan(age: Int): List<User>
@Delete
fun deleteUser(user: User)
@Query("delete from User where lastName = :lastName")
fun deleteUserByLastName(lastName: String): Int
}
这里的插入、更新、删除操作为了简便演示并没有写SQL语句进行操作,但是它们一样能使用SQL语句进行操作。我们可以看到 @Query注解下并不全是查询操作,最后一个就是删除操作,这里使用@Query注解而不使用@Delete的原因是下面那个使用的是非实体类参数,使用非实体类参数来进行CRUD操作时统一使用@Query注解。在查询操作中可使用使用传入的变量对数据进行动态的查询,就像第二个查询操作一样。
定义DataBase
@Database(version = 1, entities = [User::class, Book::class])
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun bookDao(): BookDao
companion object {
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("create table Book (id integer primary key autoincrement not null, name text not null, pages integer not null)")
}
}
private val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("alter table Book add column author text not null default 'unknown'")
}
}
private var instance: AppDatabase? = null
@Synchronized
fun getDatabase(context: Context): AppDatabase {
instance?.let {
return it
}
return Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, "app_database")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build().apply {
instance = this
}
}
}
}
这里要使用@Database注解来声明这是一个数据库型实体类。然后写入数据库的版本、包含的实体类。然后在companion object { }中定义了数据库的升级逻辑,这里定义了1到2版本、2到3版本的升级逻辑,而在测试开发的时候一般使用.fallbackToDestructiveMigration()来替换这里的.addMigrations(MIGRATION_1_2, MIGRATION_2_3),这个方法升级的时候会销毁当前的数据库再创建,这样就暂时不用写复杂的升级逻辑了(开发完了记得及时替换回来)。还有,数据库的操作属于耗时操作,所以一般不允许在主线程中操作,只能放到子线程中,但为了开发时方便,我们可以在数据库执行.build()前添加.allowMainThreadQueries()方法使操作能够在主线程中进行(开发完了记得及时删除)。 随后,定义了instance来缓存该数据库的实例,并且使用 getDatabase方法进行判空处理,若不为空则返回它,否则就调用Room.databaseBuilder方法直接再建一个赋值给instance然后返回。注意Room.databaseBuilder的第一个参数一定要用applicationContext ,不然容易出现内存泄露,第二个参数时该数据库的class类型,第三个参数是数据库的名字。
最后,我们看一下实际操作数据库是怎样的。
先定义4个按钮用于进行CRUD操作:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/addDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Add Data"/>
<Button
android:id="@+id/updateDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Update Data"/>
<Button
android:id="@+id/deleteDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Delete Data"/>
<Button
android:id="@+id/queryDataBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Query Data"/>
</LinearLayout>
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val userDao = AppDatabase.getDatabase(this).userDao()
val user1 = User("Tom", "Brady", 40)
val user2 = User("Tom", "Hanks", 63)
addDataBtn.setOnClickListener {
thread {
user1.id = userDao.insertUser(user1)
user2.id = userDao.insertUser(user2)
}
}
updateDataBtn.setOnClickListener {
thread {
user1.age = 42
userDao.updateUser(user1)
}
}
deleteDataBtn.setOnClickListener {
thread {
userDao.deleteUserByLastName("Hanks")
}
}
queryDataBtn.setOnClickListener {
thread {
for (user in userDao.loadAllUsers()) {
Log.d("MainActivity", user.toString())
}
}
}
}
好了,数据库的基本操作就到这了,作为基础学习应该够了。