Android Room Database 学习
Posted microhex
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Room Database 学习相关的知识,希望对你有一定的参考价值。
Room数据库学习
初衷
由于接手的项目中,看到别人使用的是android Jetpack下的Android Room数据库,由于以前也只是接触过GreenDao和自己写的sqlite数据库,所以就学习了一下,做一个笔记记录一下。这是我以前学习的greendao笔记,可以横向比较一下:https://blog.csdn.net/u013762572/article/details/78133463
概览
配置
现在有两个版本的 一个是andoirdx版的,一个是android版的。
我们可以上这个网站搜索需要的版本序号:
https://mvnrepository.com/
我贴一下当前我自己所有使用的版本:
implementation "android.arch.persistence.room:runtime:1.1.0"
annotationProcessor "android.arch.persistence.room:compiler:1.1.0"
另外也贴一下androidx版本的吧:
implementation "androidx.room:room-runtime:2.1.0-alpha07"
annotationProcessor "androidx.room:room-compiler:2.1.0-alpha07"
另外多说一句,就个人而言,androidx包一定会成为主流,来替代各种版本的support包,所以如果你还没有使用上androidx,那么请早些使用。
如果你的代码中包含了kotlin
,那么需要下面的配置:
build.gradle下面需要添加plguin
apply plugin: 'kotlin-kapt'
dependencies
implementation 'androidx.room:room-runtime:2.0.0'
//使用了kotlin之后,annotationProcessor需要kapt的实现,以配合kotlin-kapt插件使用
kapt 'androidx.room:room-compiler:2.0.0'
implementation "androidx.room:room-ktx:2.1.0-alpha05"
当然以上版本,你可以在上面的网站上找到最新的版本,因为版本更新比较快,一段时间之后将会存在更新的,更稳定的版本
定义data类
我们需要实例化Class类,需要加上@Entity
注解,其他用到的注解,将会一一说明,大致代码如下:
@Entity(tableName = "t_user")
public class User
public User()
@PrimaryKey(autoGenerate = true)
public long id ;
@ColumnInfo(name = "user_name")
public String userName;
public String address ;
@ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
public int age ;
public boolean sex ;
@Ignore
public User boyFriend;
@Entity 实体类的标注,将会在数据库映射成对应的表结构,tableName不写,将会是实体类默认
@PrimaryKey 主键
@ColumnInfo 字段属性对应列的关系 没有该注解时,属性名将一一对应字段名
@Ignore 属性值将不会序列化为字段,与@java.beans.Transient意义一致
定义UserDao
定义查询接口,需要的查询的方法都需要提前定义,如下:
@Dao
public interface UserDao
@Query("SELECT * FROM t_user")
List<User> queryAllUserInfo();
@Query("SELECT * FROM t_user WHERE id = :id")
User getUserById(long id);
@Insert(onConflict = OnConflictStrategy.REPLACE )
long[] insertUser(User... users);
@Update
void updateUser(User user);
@Delete
void deleteUser(User user);
@dao 标记接口为数据访问对象,将会自动生成其实现类来查询数据
@Query 查询接口
@Insert 插入接口
@Update更新接口
@Delete删除接口
定义抽象数据库
定义需要访问的抽象数据库,定义数据库中需要存储的data类,以及数据库的版本信息,也是一成不变,基本模式如下,该数据库对象必须是抽象的,而且需要继承RoomDatabase
:
@Database(entities = User.class, version = 2)
public abstract class AbstractUserDataBase extends RoomDatabase
public abstract UserDao getUserDao();
@Database标注为一个RoomDatabase 里面有多少张表,entities里面也必须都填写
项目build 自动生成文件
如果一切都正确,将生成UserDao和AbstractUserDataBase的实现类,如下图所示:
使用(CRUD)
获取AbstractUserDataBase
的实现类:
AbstractUserDataBase userDataBase=
Room.databaseBuilder(getContext(),AbstractUserDataBase.class,"userDataBase").build()
插入数据:
User u = new User();
u.userName= "Tom";
u.age = 19;
u.address = "shanghai";
userDataBase.getUserDao().insertUser(u)
删除数据:在这里插入代码片
User u = new User();
u.id = 4L;
useDaoBase.getUserDao().deleteUser(u);
查询数据:
List<User> listList = userDaoBase.getUserDao().queryAllUserInfo();
以上是比较基本的操作,就不多说了。主要说一下需要注意的点,在我们使用更新的时候,如使用下面的方法:
@Update
fun updatePerson(p:Person)
我们更新的时候,如果这么写:
val p = Person(id = 1,name = "tom",age = 0)
userDaoBase.getUserDao().updatePerson(p)
Person这条记录将会被全局更新,意思就是它所有的字段都会被更新掉,这个是比较尴尬的,也是和我们平时后端使用数据库的想法是不一样的,一般的想法都是我更新时设置了什么就更新什么,但是Room比较蛋疼,会一下子更新全部。那么如果我们需要非要更新部分数据呢?那只能用@Query
了。如下,只更新Person
对象的userName
字段:
@Query("update person set user_name = (:userName) where id = (:Id)")
fun updatePersonNameById(userName:String, id:Int) : Int
然后就可以写代码更新了。
表映射关系
因为Room数据库是Android Jetpack.组件之一,拥有良好的性能是它考虑的重点,速度和内存一直是它考虑的重点。为了达到这些要求,它设计的时候禁止了对象之间的引用。对于对象之间的引用,我们可以理解为(一对一,一对多,多对多)。Room是对sqlite的抽象,而sqlite本身就是一个关系型数据库,因此Room为了在使用过程中,避免查询一个数据,就需要查询多个其他多个属性的问题,就定义了独有的解决方案,个人感觉这个方案并没有想象中好,相反也许会增加使用过程中不必要的麻烦。对于没有想象中那么好,稍后我会说一下自己的想法。
使用外键【foreignKeys】
先来个例子,上面已经有了一个User
对象的例子,还需要重新创建一个Book
,以此来营造one User to mapper Many Books
的任务,对于Book,实现如下:
@Entity(foreignKeys = [ForeignKey(
entity = User::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("user_id"),
onDelete = ForeignKey.NO_ACTION,
onUpdate = ForeignKey.NO_ACTION
)])
data class Book (
@PrimaryKey(autoGenerate = true)
val bookId : Int,
val title: String?,
@ColumnInfo(name = "user_id")
val userId:Int
)
先不看代码,解释一下:
Book对象有三个属性,资格自增Id、一个书名(title)、一个外键(userId)
@entity中foreignKeys
属性的含义,entity
是需要连接到哪个实体类,对应的parentColumns
就是需要链接的实体类的标识符(一般都为主键),数据库中也称之为主表,childColumns
就是本实体类中的字段属性,数据库中也称之为从表。
对于Ondelete、OnUpdate中对应的ACTION,这可能是ROOM做得比较优秀的点了,具体的ACTION有以下几个:
`NO_ACTION`: 主表更新,从表什么都不做。
`RESTRICT`:字面意思是限制或者约束,意思就是如果你想先修改或者删除主表的数据,但是这个主表是从表的外键,那么对不起,这种行为或者操作是不被允许的,是禁止的。如果真的要删除数据,那么请从从表开始修改或者删除,先不要拿主表开刀.
`SET_NULL`: 对应上面修改或者删除主表时的操作,此时操作被允许,只是从表中对应的外键都被设置为NULL
`SET_DEFAULT`:对应上面修改或者删除主表时的操作,此时操作被允许,只是从表中对应的外键都被重置,该是什么默认值,就是什么默认值
CASCADE: 这个需要分开说,当主表数据`修改(update)`时,从表中数据也修改,来重新满足外键关系;当主表数据删除(delete)时,从表数据也删除。
总体来说,这种外键我们一般也是用得比较多,也懒得讲了,平时该怎么用怎么用,而Room就是多了几个选项,可以在处理外键问题上来的更加容易一下。
@Embedded注解
在应用中,存在这样一个场景,一个Person对象,其中包含了一个地址Address
对象,Address
对象中包含了地址信息,基本类型结构为:
data class Address(
val street:String? ,
val state:String?,
val city:String?
)
override fun toString(): String
return "Address(street=$street, state=$state, city=$city)"
现在Person对象需要包含一个Addres对象:
data class Person(
@PrimaryKey(autoGenerate = true) val id : Int,
val name : String?,
val age: Int,
val address: Address?
)
但是上面已经明确说明,Room中不能搞对象引用,不然真的没法存。为了解决这个问题,Room使用了@Embedded
,而Embedded的中文意思就是嵌入式,就意味嵌入到Person对象中:
@Entity(tableName = "t_person")
data class Person(
@PrimaryKey(autoGenerate = true) val id : Int,
val name : String?,
val age: Int,
@Embedded val address: Address?
)
那么具体的表结构最终生成什么样子的呢?看一下截图就知道了:
可以看到,Room直接把Address
的字段完全搬到了Person
的后面,这个就很尴尬了,我在做的过程中,出现了这样一下问题:
Address
和Person
中不能有同名的字段Address
对象不再是数据库中对象,它只是一个简单的class bean
对象了,这个需要理解一下,不然很容易搞错。
Many2Many问题
总有地方存在多对多的数据结构,那么这种问题,Room该怎么处理呢?其实这一点,我感觉Room禁止对象引用还是没有做到彻底,因为在我看来,它最终还是搞了对象引用。在Many2Many的问题上,Room同greendao一样,使用了中间表的概念。
引用官方的例子,一首歌Song
和一个播放列表Playlist
是多对多的关系:一首歌可以在多个播放列表中;一个播放列表可以存在多首歌。大致的class结构如下:
歌曲Song
结构
@Entity
data class Song(
@PrimaryKey(autoGenerate = true)val id : Int,
val name :String,
val artistName:String
)
播放列表Playlist
结构
@Entity
data class Playlist(
@PrimaryKey(autoGenerate = true) val id : Int,
val name:String?,
val description:String?
)
那么中间表为:
@Entity(tableName = "play_song_join",
primaryKeys = ["play_list_id","song_id"],
foreignKeys = [
ForeignKey(entity = Playlist::class,
parentColumns = ["id"],
childColumns = ["play_list_id"]),
ForeignKey(entity = Song::class,
parentColumns = ["id"],
childColumns = ["song_id"])
]
)
data class PlaySongJoin(
val play_list_id: Int,
val song_id : Int
)
结构很清晰,中间表存储两个对象的主键,最后一一对应。在查询时,此时就需要使用SQL代码了:
@Query("""
SELECT * from playlist
INNER JOIN play_song_join
ON playlist.id = play_song_join.play_list_id
WHERE play_song_join.song_id = (:songId)
""")
fun getPlayListsForSongs(songId:Int) : List<Playlist>
@Query("""
SELECT * FROM song
INNER JOIN play_song_join
ON song.id = play_song_join.song_id
WHERE play_song_join.play_list_id = (:playListId)
""")
fun getSonsForPlayList(playListId:Int) : List<Song>
使用内连接来查询数据,还是对数据库SQL语句有些要求。
数据视图
这个平时用的比较少,但是还是聊一下这东西。而且也是2.1.0版本之后的新东西。我们上面定义过了User
和Book
两个实体类,如果有个需要同时需要User
信息和Book
信息,按照一般的做法,我们需要查询两遍,一遍查User
,一遍查Book
,效率有些低下,数据视图就是解决这个问题。首先我们定义下UserBookDetail
(此时我们假设User和Book是一一对应关系):
public class UserBookDetail
public long id ;
public String userName;
public String address ;
public int age ;
public boolean sex ;
//书ID
public long bookId;
//书名
public String title ;
则使用数据视图,又需要重新写SQL语句了:
@DatabaseView("
SELECT t.id,
t.user_name,
t.address,
t.age,
t.sex,
b.book_id,
b.title
from user t inner join book b on t.id = b.user_id ")
public class UserBookDetail
public long id ;
public String userName;
public String address ;
public int age ;
public boolean sex ;
//书ID
public long bookId;
//书名
public String title ;
这个用的不多,我也只是记录一下,具体的可以参考官方文档:)
使用总结
使用Room一段时间之后,有几点需要注意一下:
- 所有的查找、修改、删除、增加等操作都需要放在子线程,这个是必须的,不然就一定报错。
- 通过上面的数据我们看出,数据之间的操作,都是需要id的,而我们查找的这个id不是很方便获取,即使我们insert了这个对象,返回的也只是数据库影响的列数,对象中Id也没有及时修改过来,这个和greendao相比,还是有些差距的。
- 速度还是蛮快的,其他的,暂时还没有发现什么毛病,这是每次写个查询,都需要build一次还是蛮麻烦的。
学习代码为:
https://github.com/Microhx/studyCodes/tree/master/room
以上是关于Android Room Database 学习的主要内容,如果未能解决你的问题,请参考以下文章
Android Room Database:如何处理实体中的 Arraylist?
获取 android 房间数据库 java.lang.IllegalArgumentException 的以下异常:@androidx.room.Database 未定义元素视图()
Android Room Database,检索输入的最新记录的特定值