版权声明
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/9708574.html
简介
Android 2017 IO大会推出了官方数据库框架:Room。Room其实就只是对原生的SQLite API进行了一层封装。不得不说google其实早应该出SQLite的ORM了,因为Android的SQLite谁用谁知道,没有任何封装的字符输入。如果不对着Demo基本上会有记不起来的一两个关键字的时候,而且完全手敲容易输入错误。当然还有很多其他的Android ORM框架例如OrmLite、GreenDao 和 Sugar。但是本着Google爸爸的东西肯定有牛逼的地方,我们还是需要学习一下怎么使用。
这里解释一下什么是ORM,对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。其实更好的说明就是,对数据库指令的二次封装。使其使用更加简单、轻松、缓解记不住指令的尴尬。
参考文档:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0525/7971.html
数据库升级迁移:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0728/8278.html
导入工程
这里只说明Android studio的导入
1.首先在build.gradle里添加
allprojects {
repositories {
jcenter()
google()
}
}
2.然后在添加依赖
implementation 'android.arch.persistence.room:runtime:1.0.0'
annotationProcessor "android.arch.persistence.room:compiler:1.0.0"
使用流程概况
因为创建步骤较多所以这里简单先给一个流程,给大家有一个简单的概念:
- 创建数据class,使用的注释是:@Entity
- 创建Dao 数据操作抽象class,使用的注释是:@Dao(负责提供操作数据的方法,比如基本的增加、更新、查找、删除。当然除了这些基本的,更复杂的我们将在后续深入学习)
- 创建应用程序数据库的抽象class,使用的注释是:@Database(负责创建应用数据库,组合“数据class”与“数据操作class”,还有数据库版本)
- 实例化使用数据库
一个简单的小Demo
在深入学习前,我们先来一个简单的小demo演示一下创建流程与使用。
1.首先是创建数据class,它使用关键注释是@Entity,这个class代表着一列的数据表(里面包含着主键id(这是是必需的)、内容1标题、内容2标题等等)
import androidx.room.ColumnInfo; import androidx.room.Entity; import androidx.room.PrimaryKey; @Entity //重点!这个是关键.数据class必需使用@Entity注释 public class MyData { @PrimaryKey //@PrimaryKey = 主键 @ColumnInfo(name = "id") public int id; @ColumnInfo(name = "name")//这个代表了这个数据标题名称 public String name; public String content; //当然不写 @ColumnInfo(name = "content") 也是可以的,因为数据名称可以默认为变量名称。 }
2.创建Dao 数据操作抽象class,它使用关键注释是@Dao,这个class负责提供操作数据的方法(比如基本的增加、更新、查找、删除)
import java.util.List; import androidx.room.Dao; import androidx.room.Delete; import androidx.room.Insert; import androidx.room.OnConflictStrategy; import androidx.room.Query; import androidx.room.Update; @Dao //重点! 这个是关键,数据操作的class必需使用@Dao来注释 public abstract class MyDao { //另外注意它是一个抽象类 @Insert(onConflict = OnConflictStrategy.REPLACE) //@Insert = 插入, onConflict = 如果冲突 OnConflictStrategy.REPLACE = 如果冲突就替换 public abstract void insert(MyData... data); //添加了插入注释后,这个方法就可以当做插入数据的方法使用 @Update public abstract void update(MyData... data);// @Update = 更新 @Delete public abstract void delete(MyData... data);// @Delete = 删除 @Query("select * from MyData") abstract List<MyData> getAll(); //@Query = 查询 ,这里的注释括号里的内容代表这查询的关键词,可以用于筛查想要的数据。 }
3.创建应用程序数据库的抽象class,使用的注释是:@Database(负责创建应用数据库,组合数据class与数据操作class,还有数据库版本)
/** * 重点!应用数据库class必需使用Database注释 * entities 实体 = 我们的数据class MyData,注意它使用了{}包裹,如果你需要创建多个表,这里可以用,逗号隔开增加。 * version = 1 数据库版本号 * exportSchema = false 导出模式 */ @Database(entities = {MyData.class},version = 1,exportSchema = false) public abstract class AppDatabase extends RoomDatabase { public abstract MyDao Dao(); private static AppDatabase mAppDataBase; public static AppDatabase getI(Context context){ //实现单例模式 if (mAppDataBase == null){ mAppDataBase = Room.databaseBuilder(context,AppDatabase.class,"data.db")//data.db 是你的数据库名称 .build(); } return mAppDataBase; } }
注意!这里使用的是单例模式。 还有另外,你要记住你的数据库名称。另外数据库是允许在主线程里创建的,但是不建议在主线程里创建。如果你非要创建,可以添加属性后在UI线程中创建。
mAppDataBase = Room.databaseBuilder(context,AppDatabase.class,"data.db") .allowMainThreadQueries() .build();
4.在activity的子线程里实例化并且使用数据库。
public class RoomActivity extends AppCompatActivity { private static final String TAG = "RoomActivity"; private Button mBtnGetData; private AppDatabase appDatabase; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_room); /** * 实例化应用数据库 */ new Thread(new Runnable() { @Override public void run() { appDatabase = AppDatabase.getI(RoomActivity.this); MyDao dao = appDatabase.Dao();//得到实例化的数据操作class MyData data = new MyData();//实例一个数据class data.id = 101; data.name = "橘子"; data.content = "酸酸的"; MyData data2 = new MyData(); data2.id = 102; data2.name = "苹果"; data2.content = "脆脆的"; //因为我在MyDao的insert插入方法里写的是数组参数,所以也可以多个添加 dao.insert(data,data2); Log.e(TAG, "数据导入完成"); } }).start(); mTextView = (TextView)findViewById(R.id.textView); mBtnGetData = (Button)findViewById(R.id.btn_getdata); mBtnGetData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { new Thread(new Runnable() { @Override public void run() { MyDao dao = appDatabase.Dao(); final StringBuffer sb = new StringBuffer(); sb.append("数据库内容是:"+"\n"+"-------------------------------\n"); List<MyData> datas = dao.getAll();//得到所有数据的List for (MyData data : datas){ sb.append("id : "+String.valueOf(data.id)+"\n"); sb.append("name : "+data.name+"\n"); sb.append("content : "+data.content+"\n"); sb.append("-------------------------------\n"); } runOnUiThread(new Runnable() { @Override public void run() { mTextView.setText(sb.toString()); } }); } }).start(); } }); } }
以上是创建数据库、插入数据、查询数据在线程中的操作。创建数据库、插入数据和查询数据最好都在子线程里操作,尽量不要在主线程里操作数据库。另外注意!如果你在子线程里创建了数据库,那么你在主线程中就无法获取数据,一定要在子线程里获取数据。
效果图:
深入学习 @Entity使用
@Entity 是用来创建表格与表格中一列的内容的。Room 会为实体类中定义的每个字段在数据库中创建“列”。
下面这个代码块展示了如何定义一个实体
@Entity public class MyData { @PrimaryKey //@PrimaryKey = 主键 @ColumnInfo(name = "id") public int id; @ColumnInfo(name = "name") public String name; public String content; @Ignore //使用@Ignore的数据将不会在数据库创建此数据的一列 public Bitmap bitmap; }
另外如果你有不需要数据库创建字段列,你可以使用@Ignore 进行注释。为了能够保存某个字段,Room必须能够对它进行操作。你可以使用public修饰符,或者你可以提供getter和setter方法。如果你选择后者,记住Room是基于JavaBeans约定的。
自定义表名 tableName 自定义字段名@ColumnInfo
在通常不添加自定义表名的属性情况下,表名是默认class名的,但是如果有需求自定义可以这样实现
@Entity (tableName = "Data")//创建自定义表名称 public class MyData { @PrimaryKey public int id; @ColumnInfo(name = "name") public String name; @ColumnInfo(name = "content") public String content; }
另外你需要注意一点,表名对大小写是有区分的。
主键@PrimaryKey
一份数据实体,必定包含一个主键变量(或者叫主键字段) 。
自增主键 @PrimaryKey (autoGenerate = true)
如果你不想自己添加主键值,可以设定这个属性,让主键值自增
@Entity public class MyData { @PrimaryKey (autoGenerate = true) public int id; @ColumnInfo(name = "name") public String name; @ColumnInfo(name = "content") public String content; }
我们试试不添加主键内容,让它自增
MyDao dao = appDatabase.Dao();//得到实例化的数据操作class MyData data = new MyData();//实例一个数据class data.name = "橘子"; data.content = "酸酸的"; MyData data2 = new MyData(); data2.name = "苹果"; data2.content = "脆脆的"; dao.insert(data,data2);//因为我在MyDao的insert插入方法里写的是数组参数,所以也可以多个添加
效果图:
组合主键 primaryKeys
@Entity (primaryKeys = {"id","num"}) public class MyData { public int id; public int num; @ColumnInfo(name = "name") public String name; @ColumnInfo(name = "content") public String content; }
在kotlin语言下@PrimaryKey主键无法自增的问题
如果你正在使用kotlin语言实现Room的数据库,那么你有可能出现在下面这种代码的情况下无法自增主键id的问题。
出问题的代码,出问题是原因是data类在创建的时候你需要输入id这字段数据
/**
* 这段是错误的代码,下面的id在kotlin的data类下无法自增
*/
@Entity
data class MyData (
@PrimaryKey(autoGenerate = true)
var id : Int,
@ColumnInfo(name = "name")
var name: String,
@ColumnInfo(name = "content")
var content: String,
)
原因是写法错了,正确的写法如下:
在下面的代码中,创建数据就不需要设置id了
@Entity
data class MyData (
@ColumnInfo(name = "name")
var name: String,
@ColumnInfo(name = "content")
var content: String,
){
@PrimaryKey(autoGenerate = true)
var id : Int = 0
}
索引 @Index
了解索引:假如我们比喻数据库是一份字典,如果不添加索引我们去查找某个数据,数据库是逐行逐列的去查询直到整个字典被遍历完成。这样搜索必然慢一些,而这个时候我们可以添加索引,原理跟你在查字典的时候在索引页面里去查询关键信息(比如拼音或者笔画查找)然后在逐步缩小范围最终找到想要的信息并且锁定页数。
使用范围:索引并不是万能的,也有它的优点与缺点,索引可以增加更新、删除、查询的速度,但是会增加插入数据的速度。使用索引不适合使用在:1.数据量少的情况 2.有大量null值,不值得索引查询 3.频繁更新、插入的数据库,插入修改操作频繁反而更慢。
其他:索引的创建不需要添加什么标示字符串,你只需要告诉数据库需要创建索引的列,它会自动添加数据索引。
废话了这么多,我们开始创建索引:
单列索引:
@Entity (indices = {@Index("name")})//这里说明了,我们的name需要索引 public class MyData { @PrimaryKey(autoGenerate = true) public int id; @ColumnInfo(name = "name") public String name; @ColumnInfo(name = "content") public String content; }
多列索引(组合索引):
@Entity (indices = {@Index(value = {"name","content"})})//这个是索引 public class MyData { @PrimaryKey(autoGenerate = true) public int id; @ColumnInfo(name = "name") public String name; @ColumnInfo(name = "content") public String content; }
索引唯一性:
你可以通过在@Index注解下设置unique为true,即可强制实现该字段的唯一性。防止name与content内容一致
@Entity (indices = {@Index(value = {"name","content"},unique = true)})
外键 @ForeignKey
了解外键:外键是什么?按照字面解读“外部的键值”?恩,部分解释到了它的用处。它的确是关联2个列的关键功能。百度这么解释的:如果公共关键字在一个关系中是主关键字,那么这个公共关键字被称为另一个关系的外键。由此可见,外键表示了两个关系之间的相关联系。以另一个关系的外键作主关键字的表被称为主表,具有此外键的表被称为主表的从表。外键又称作外关键字。外键有2个基本特性:1.约束插入的值 (你无法插入一个没有被创建的主键)2.被主键影响(比如主键删除,主键下的全部外键会被删除)
使用范围:聊天记录、好友列表等等需要主次分类关联数据的地方.
其他:注意!Android 好像是不允许主键操作外键,比如直接从主键得到外键数据。但是它是允许关联主外键的
代码演示:
为了思维连续,我将贴全代码。
步骤一 创建数据实体class
我将分别创建 书架数据类-Classify 与 书籍数据类-Book。
书架数据类-Classify
@Entity public class Classify { @PrimaryKey (autoGenerate = true) public int id;//书架id public String classifyName;//书架名称 }
书籍数据类-Book 请注意,我给Book添加的外键属性。
//重点!这里写入了外键属性!声明了,书架Classify是我的主键 , 父类列 = 书架Classify里的id 子类列 = 我下面创建的classifyId变量 @Entity (foreignKeys = @ForeignKey(entity = Classify.class,parentColumns = "id",childColumns = "classifyId")) public class Book { @PrimaryKey (autoGenerate = true) public int bookId;//书籍id public String bookName;//书籍名称 public int classifyId;//书架id }
步骤二 创建数据操作Dao类
也是分别2个ClassifyDao 与 BookDao
@Dao public abstract class ClassifyDao { @Insert(onConflict = OnConflictStrategy.REPLACE) public abstract void insert(Classify... data); @Update public abstract void update(Classify... data); @Delete public abstract void delete(Classify... data); @Query("select * from Classify") public abstract List<Classify> getAll(); }
@Dao public abstract class BookDao { @Insert(onConflict = OnConflictStrategy.REPLACE) public abstract void insert(Book... data); @Update public abstract void update(Book... data); @Delete public abstract void delete(Book... data); @Query("select * from Book") public abstract List<Book> getAll(); }
步骤三 创建应用数据库
@Database(entities = {Classify.class,Book.class},version = 1,exportSchema = false) public abstract class AppDatabase extends RoomDatabase { public abstract ClassifyDao classifyDao(); public abstract BookDao bookDao(); private static AppDatabase mAppDataBase; public static AppDatabase getI(Context context){ //实现单例模式 if (mAppDataBase == null){ mAppDataBase = Room.databaseBuilder(context,AppDatabase.class,"data.db")//data.db 是你的数据库名称 .build(); } return mAppDataBase; } }
老样子,我导入了2个返回操作抽象类的方法,使用了单例模式得到应用数据库实例。
步骤四 创建应用数据库实例、插入数据、查询数据
public class RoomActivity extends AppCompatActivity { private static final String TAG = "RoomActivity"; private Button mBtnGetData,mBtnSetData; private AppDatabase appDatabase; private TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_room); mTextView = (TextView)findViewById(R.id.textView); mBtnGetData = (Button)findViewById(R.id.btn_getdata); mBtnSetData = (Button)findViewById(R.id.btn_setdata); mBtnSetData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { /** * 实例化应用数据库 */ new Thread(new Runnable() { @Override public void run() { appDatabase = AppDatabase.getI(RoomActivity.this);//单例模式得到应用数据库 ClassifyDao classifyDao = appDatabase.classifyDao();//得到实例化的数据操作class Classify data = new Classify();//实例一个数据class data.classifyName = "科幻类"; Classify data2 = new Classify(); data2.classifyName = "技术类"; classifyDao.insert(data,data2); BookDao bookDao = appDatabase.bookDao(); Book book1 = new Book(); book1.bookName = "三体"; book1.classifyId = 1;//写入主键id Book book2 = new Book(); book2.bookName = "黑暗森林"; book2.classifyId = 1; Book book3 = new Book(); book3.bookName = "Java从入门到精通"; book3.classifyId = 2; Book book4 = new Book(); book4.bookName = "Android第一行代码"; book4.classifyId = 2; bookDao.insert(book1,book2,book3,book4); Log.e(TAG, "数据导入完成"); } }).start(); } }); mBtnGetData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { new Thread(new Runnable() { @Override public void run() { final StringBuffer sb = new StringBuffer(); sb.append("数据库内容是:"+"\n"+"-------------------------------\n"); ClassifyDao classifyDao = appDatabase.classifyDao(); BookDao bookDao = appDatabase.bookDao(); List<Classify> classifyList = classifyDao.getAll(); List<Book> bookList = bookDao.getAll(); for (Classify classify : classifyList){ sb.append("ClassifyId:"+String.valueOf(classify.id)+"\n"); sb.append("Name:"+classify.classifyName+"\n"); sb.append("-------------------------------\n"); } for (Book book : bookList){ sb.append("BookId:"+String.valueOf(book.bookId)+"\n"); sb.append("Name:"+book.bookName+"\n"); sb.append("ClassifyId:"+book.classifyId+"\n"); sb.append("-------------------------------\n"); } runOnUiThread(new Runnable() { @Override public void run() { mTextView.setText(sb.toString()); } }); } }).start(); } }); } }
唯一需要注意的地方,我写入的主键id,因为我在书架数据class标示的id是自增,然后我又创建了2个书架,所以在书籍class里写入的主键id是1和2。到此为止,演示外键的demo代码就已经贴全了。
效果图:
外键的功能探索:
约束:
或许某些同学会这么有这些疑问,ヾ(。`Д´。) 看到目前为止外键好像并没有什么用啊。恩,这是我一开始的疑惑,特别是我发现还需要手动添加外键(原谅我之前没有接触过数据库),这不是鸡肋么。下面,我们就来演示外键的功能之一”约束“。
Classify data = new Classify();//实例一个数据class data.classifyName = "科幻类"; Classify data2 = new Classify(); data2.classifyName = "技术类"; classifyDao.insert(data,data2); BookDao bookDao = appDatabase.bookDao(); Book book1 = new Book(); book1.bookName = "三体"; book1.classifyId = 1; Book book2 = new Book(); book2.bookName = "黑暗森林"; book2.classifyId = 1; Book book3 = new Book(); book3.bookName = "Java从入门到精通"; book3.classifyId = 3; Book book4 = new Book(); book4.bookName = "Android第一行代码"; book4.classifyId = 3; bookDao.insert(book1,book2,book3,book4);
因为,我们只创建了2个书架。按照自增id,最多自增到了“2”。所以我们这里给它一个错误的书架id “3”,看看会出现什么情况。
恩,报错了。我们看看,为什么报错了?
android.database.sqlite.SQLiteConstraintException: FOREIGN KEY constraint failed (code 787) at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method) at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:782) at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788) at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86) at androidx.sqlite.db.framework.FrameworkSQLiteStatement.executeInsert(FrameworkSQLiteStatement.java:51) at androidx.room.EntityInsertionAdapter.insert(EntityInsertionAdapter.java:80) at com.example.user.demo.room.BookDao_Impl.insert(BookDao_Impl.java:79) at com.example.user.demo.room.RoomActivity$1$1.run(RoomActivity.java:56) at java.lang.Thread.run(Thread.java:761)
报错的原因是我们添加了一个不存在的主键id,我们的外键功能成功的约束了。
关联操作:
外键还有一个功能,关联操作,下面我们来演示一个主键id的列被删除了,它关联的外键也被删除的功能。在代码相同的部分我就不贴了,可以参考上面已经贴出的代码。下面将贴出具体实现的代码。
在之前的Book数据class的外键属性里添加了onDelete属性:
@Entity (foreignKeys = @ForeignKey(entity = Classify.class, parentColumns = "id", childColumns = "classifyId", onDelete = CASCADE))//重点!我们添加onDelete属性为CASCADE串联.表示删除操作串联 public class Book { @PrimaryKey (autoGenerate = true) public int bookId; public String bookName; public int classifyId; }
下面来实现查找一个数据列的方法:
@Query("select * from Classify where id = :num ") public abstract Classify getClassify(int num);
这里在ClassifyDao类里添加了一个根据id查找实体数据列的方法。这里你只要先稍微了解。后续我将详解解释查询功能部分。
在activity里实现:
首先,导入数据的部分不变依然是上面的操作,我们在获取数据的步骤里添加了如下代码:
mBtnGetData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { new Thread(new Runnable() { @Override public void run() { final StringBuffer sb = new StringBuffer(); sb.append("数据库内容是:"+"\n"+"-------------------------------\n"); ClassifyDao classifyDao = appDatabase.classifyDao(); BookDao bookDao = appDatabase.bookDao(); Classify classify = classifyDao.getClassify(2);//找到id为2的列 classifyDao.delete(classify);//删除这个列 List<Classify> classifyList = classifyDao.getAll();//得到全部书架数据列 List<Book> bookList = bookDao.getAll();//得到全部书籍数据列 for (Classify classifyItem : classifyList){ sb.append("ClassifyId:"+String.valueOf(classifyItem.id)+"\n"); sb.append("Name:"+classifyItem.classifyName+"\n"); sb.append("-------------------------------\n"); } for (Book bookItem : bookList){ sb.append("BookId:"+String.valueOf(bookItem.bookId)+"\n"); sb.append("Name:"+bookItem.bookName+"\n"); sb.append("ClassifyId:"+bookItem.classifyId+"\n"); sb.append("-------------------------------\n"); } runOnUiThread(new Runnable() { @Override public void run() { mTextView.setText(sb.toString()); } }); } }).start(); } });
先找到id为2的列,然后在删除这个列(删除的方法请向上看之前的代码),然后在分别读出数据库里的数据。
效果图:
删除了主键书架里,id等于2 名称是技术类的书架,但是它的外键数据也被删除了。关联删除实现了。
嵌入对象 @Embedded
有时你可能想把一个entity或者一个POJOs(数据类)作为一个整体看待。这种情况下,你可以使用@Embedded注解,表示你想把一个对象分解为表的子字段。然后你就可以像其它独立字段那样查询这些嵌入的字段。通俗点的说法就是,让带有多个成员的类的每个变量都作为表中的字段。下面我就来演示一下如何添加一个class作为字段对象。
首先我们需要创建实体数据,并添加对象:
@Entity public class Book { @PrimaryKey (autoGenerate = true) public int bookId; public String bookName; @Embedded //重点,这里使用的是下面的内部class作为对象 public Detailed detailed; } class Detailed{ public String author; public String press; @ColumnInfo(name = "paginal_number") public int num; }
在activity里插入数据、查询数据:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_room); mTextView = (TextView)findViewById(R.id.textView); mBtnGetData = (Button)findViewById(R.id.btn_getdata); mBtnSetData = (Button)findViewById(R.id.btn_setdata); mBtnSetData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { /** * 实例化应用数据库 */ new Thread(new Runnable() { @Override public void run() { appDatabase = AppDatabase.getI(RoomActivity.this);//单例模式得到应用数据库 BookDao bookDao = appDatabase.bookDao(); Book book1 = new Book(); book1.bookName = "Android第一行代码"; Detailed detailed = new Detailed(); detailed.author = "郭霖"; detailed.press = "人民邮电出版社"; detailed.num = 570; book1.detailed = detailed; bookDao.insert(book1); Log.e(TAG, "数据导入完成"); } }).start(); } }); mBtnGetData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { new Thread(new Runnable() { @Override public void run() { final StringBuffer sb = new StringBuffer(); sb.append("数据库内容是:"+"\n"+"-------------------------------\n"); BookDao bookDao = appDatabase.bookDao(); List<Book> bookList = bookDao.getAll(); for (Book bookItem : bookList){ sb.append("BookId:"+String.valueOf(bookItem.bookId)+"\n"); sb.append("Name:"+bookItem.bookName+"\n"); sb.append("author:"+bookItem.detailed.author+"\n"); sb.append("press"+bookItem.detailed.press+"\n"); sb.append("paginal_number:"+bookItem.detailed.num+"\n"); sb.append("-------------------------------\n"); } runOnUiThread(new Runnable() { @Override public void run() { mTextView.setText(sb.toString()); } }); } }).start(); } }); } }
效果图:
深入学习@Dao
在room框架里@Dao注释的的class 都是用于数据增、删、查、更新方法的提供。另外你需要注意它必需使用接口或者抽象class来实现。
创建Dao Class
@Dao 注释的Class对具体操作那个数据class并没有要求,你可以将所有的操作方法都放如一个@Dao类,也可以分别添加多个@Dao类。
重载方法操作不同表
老样子,按照我的习惯,不厌其烦的贴全代码
步骤一 创建操作Dao
@Dao public abstract class AllDao { @Insert(onConflict = OnConflictStrategy.REPLACE) public abstract void insert(Fruits... data); @Insert(onConflict = OnConflictStrategy.REPLACE) public abstract void insert(Drinks...data); @Update public abstract void update(Fruits... data); @Update public abstract void update(Drinks... data); @Delete public abstract void delete(Fruits... data); @Delete public abstract void delete(Drinks... data); @Query("select * from Fruits") public abstract List<Fruits> getFruitsAll(); @Query("select * from Drinks") public abstract List<Drinks> getDrinksAll(); }
我们这里重载了分别为Fruits水果 与 Drinks饮料的 数据库操作方法
步骤二 创建对应的数据class 水果与饮料
水果
@Entity public class Fruits { @PrimaryKey(autoGenerate = true) public int id; public String name; public Fruits(String name){ this.name = name; } }
饮料
@Entity public class Drinks { @PrimaryKey(autoGenerate = true) public int id; public String name; public Drinks(String name){ this.name = name; } }
步骤三 创建应用程序数据库的抽象class
@Database(entities = {Drinks.class,Fruits.class},version = 1,exportSchema = false) //注意! 这里分别添加了 Drinks.class和Fruits.class public abstract class AppDatabase extends RoomDatabase { public abstract AllDao Dao(); private static AppDatabase mAppDataBase; public static AppDatabase getI(Context context){ //实现单例模式 if (mAppDataBase == null){ mAppDataBase = Room.databaseBuilder(context,AppDatabase.class,"data.db")//data.db 是你的数据库名称 .build(); } return mAppDataBase; } }
注意!请别忘记了导入数据class。我这里分别添加了2个Drinks.class和Fruits.class
步骤四 导入数据和获取数据
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_room); mTextView = (TextView)findViewById(R.id.textView); mBtnGetData = (Button)findViewById(R.id.btn_getdata); mBtnSetData = (Button)findViewById(R.id.btn_setdata); mBtnSetData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { new Thread(new Runnable() { @Override public void run() { appDatabase = AppDatabase.getI(RoomActivity.this); AllDao dao = appDatabase.Dao(); dao.insert(new Drinks("肥仔快乐水"));//使用了重载方式 dao.insert(new Drinks("解奶宝矿力")); dao.insert(new Fruits("香蕉")); dao.insert(new Fruits("西瓜")); } }).start(); } }); mBtnGetData.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { new Thread(new Runnable() { @Override public void run() { final StringBuffer sb = new StringBuffer(); sb.append("数据库内容是:"+"\n"+"-------------------------------\n"); AllDao dao = appDatabase.Dao(); List<Drinks> DrinksList = dao.getDrinksAll(); List<Fruits> FruitsList = dao.getFruitsAll(); for (Drinks drinks : DrinksList){ sb.append("Id:"+String.valueOf(drinks.id)+"\n"); sb.append("Name:"+drinks.name+"\n"); sb.append("-------------------------------\n"); } for (Fruits fruits : FruitsList){ sb.append("Id:"+String.valueOf(fruits.id)+"\n"); sb.append("Name:"+fruits.name+"\n"); sb.append("-------------------------------\n"); } runOnUiThread(new Runnable() { @Override public void run() { mTextView.setText(sb.toString()); } }); } }).start(); } }); }
效果图:
插入 @Insert
其实@Insert可以说明的东西不多,在上面这么多demo演示下,你也应该明白了@Insert是干什么的。简单的来说就是插入一份数据。但是当然还是有一些东西可以纠结的。。。
@Insert 方法的参数:
@Insert(onConflict = OnConflictStrategy.REPLACE) public abstract void insert(Fruits data); //导入单个数据 @Insert(onConflict = OnConflictStrategy.REPLACE) public abstract void insert(Fruits... data); //以数组导入数据 @Insert(onConflict = OnConflictStrategy.REPLACE) public abstract void insert(List<Fruits> data); //以list导入数据
目前,本人就验证了这3种。第三种以list导入数据的形式。请放心,本人用demo验证过,可以使用。
@Insert 的属性:
Insert只有一个属性onConflict(数据冲突策略),所以我列举一下这个属性的值:
/** *冲突策略-替换旧数据并继续事务。 */ int REPLACE=1; /** *冲突策略-回滚事务。 */ int ROLLBACK=2; /** *冲突策略-中止事务。 */ int ABORT=3; /** *冲突策略-使事务失败。 */ int FAIL=4; /** *冲突策略-忽略冲突。 */ int IGNORE=5;
更新 @Update
更新@update与@Insert方法的使用方法是类似的。
@update 方法的参数 (与@Insert一样可以使用单个POJO类数据,POJO数组、POJO List,作为参数)
@update 的属性(与@Insert一样使用onConflict 作为属性,并且属性值完全一致,可以参考上面的Insert)
@Update(onConflict = OnConflictStrategy.REPLACE) public abstract void update(Fruits... data);
删除 @Delete
删除@Delete 就很简单了,会根据你导入的POJO实体,去删除表里对应的数据列,除了基本的参数一样可以设置为单个、数组、list以外,并没有其他的可以设置的属性了。
@Delete public abstract void delete(Fruits...data);
查询 @Query
查询@Query 就复杂了许多,因为涉及到了基本的SQList语法的使用。这里我会举例几个常用的查询语法作为例子。
查询数据表里所有的数据
@Query("select * from Fruits") public abstract List<Fruits> getAll();
解释括号中的语法:
select 代表查询结果关键字,这里写入一个 * 星号表示我们得到所有表里的数据。
from 代表从哪里查询,这里我们写入了我们的表名Fruits
使用上面的方法,我们将会得到一个表里所有数据的List。
根据id查询单列数据
@Query("select * from Fruits where id=:num")//根据id查找一列数据 public abstract Fruits queryId(int num);
解释括号中的语法:
select 代表查询结果关键字,这里写入一个 * 星号表示我们得到所有表里的数据。这个与上面一样
from 代表从哪里查询,这里我们写入了我们的表名Fruits。
where 代表查询条件 这里我们查询id = 下面方法输入的值。 注意这里的写法 :num ,这是room特有的参数写法,只要想要导入方法参数就需要在参数名前面添加:。
使用上面的方法,我们将会得到方法里输入参数值的对应id,列里的所有数据。
查询指定数据列
@Query("select id from Fruits ")//返回id字段里的所有id值数据 public abstract int[] queryIdArray();
解释括号中的语法:
select 代表查询结果关键字,这里写入一个 id 星号表示我们得到id字段里的所有数据。
使用上面的方法,我们将会得到id字段里的所有id值
查询数据数量
@Query("select count(*) from Fruits") public abstract int queryIsEmpty();
查询符合指定2个值的数据
/** * 查询指定密码id与指定类型的数据 * * @param passwordIdValue 指定密码id值 * @param typeValue 指定类型值 * @return */ @Query("select * from PasswordBean where passwordId=:passwordIdValue and type=:typeValue") abstract List<PasswordBean> queryIdAndTypeData(int passwordIdValue, int typeValue);
以id倒序形式,获取指定数量的数据
排序的格式 : Order By 目标字段 DESC
- ASC 默认值,从小到大,升序排列
- DESC 从大到小,降序排列
/** * 以id倒序形式,获取指定数量的数据 * * @param num * @return */ @Query("select * from OpenDoorLogBean order by id DESC limit :num") abstract List<OpenDoorLogBean> getTargetQuantityData(int num);
以id倒序形式,从指定id位置获取指定数量的数据
/** * 以id倒序形式,从指定id位置获取指定数量的数据 * * @param num * @return */ @Query("select * from OpenDoorLogBean where id <:targetId order by id DESC limit :num") abstract List<OpenDoorLogBean> getTargetIdQuantityData(int targetId, int num);
获取小于目标时间的数据
/** * 获取小于目标时间的数据 * * @param time * @return */ @Query("select * from HomeVideoBean where time <=:time") public abstract List<HomeVideoBean> getLessThanTimeData(long time);
删除过期数据
/** * 删除过期数据 * * @param expiredTime 过期时间 * @return */ @Query("delete from HomeVideobean Where time <=:expiredTime ") public abstract void deleteExpiredData(long expiredTime);
删除全部数据
/** * 删除全部数据 */ @Query("delete from HomeVideobean") public abstract void deleteAll();
更新某项数据
/** * 设置全部是否已读 * @param isAllHaveRead */ @Query("update HomeVideoBean set isHaveRead =:isAllHaveRead") public abstract void isAllHaveRead(boolean isAllHaveRead); }
模糊查询
@Query("select * from NetworkBean where content like '%'||:content||'%'") abstract List<NetworkBean> searchByContain(String content);
模糊查询与排序组合
@Query("select * from NetworkBean where content like '%'||:content||'%' order by id DESC") abstract List<NetworkBean> searchByContain(String content);
多条件查询
@Query("select * from AppLockBean where childId =:childId and appLockStatus = 1 and startTime <=:minute and endTime >=:minute")
fun getAppsAvailableDuringATimePeriod(childId: Int, minute: Int): List<AppLockBean>?
数据去重查询
去重的关键词distinct
@Query("select distinct packageName from UsageBean")
fun findDistinctPackageNameByChildId(childId: Int):List<String>
时间与日期相关的函数
1 | date(timestring, modifier, modifier, ...) | 以 YYYY-MM-DD 格式返回日期。 |
2 | time(timestring, modifier, modifier, ...) | 以 HH:MM:SS 格式返回时间。 |
3 | datetime(timestring, modifier, modifier, ...) | 以 YYYY-MM-DD HH:MM:SS 格式返回。 |
4 | julianday(timestring, modifier, modifier, ...) | 这将返回从格林尼治时间的公元前 4714 年 11 月 24 日正午算起的天数。 |
5 | strftime(format, timestring, modifier, modifier, ...) | 这将根据第一个参数指定的格式字符串返回格式化的日期。具体格式见下边讲解。 |
正确的使用例子,注意unixepoch必须要填
/**
* 根据小孩id[childId]与 年月日时 查找指定数据
*/
@Query("select * from Usage24HourBean where childId =:childId and strftime('%Y%m%d%H',recordTimestamp/1000, 'unixepoch') = strftime('%Y%m%d%H',:timestamp/1000, 'unixepoch')")
fun findDataByChildIdAndDate(childId: Int, timestamp: Long): List<Usage24HourBean>
更多数据库命令请查询:https://www.runoob.com/sqlite/sqlite-tutorial.html
更新数据库
请参考:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2017/0728/8278.html
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/9708574.html