Android开发之Room(Java)
Room 在 SQLite 上提供了一个抽象层,以便在充分利用 SQLite 的强大功能的同时,能够流畅地访问数据库。
处理大量结构化数据的应用可极大地受益于在本地保留这些数据。最常见的用例是缓存相关数据。这样,当设备无法访问网络时,用户仍可在离线状态下浏览相应内容。设备之后重新连接到网络后,用户发起的所有内容更改都会同步到服务器。
官方建议使用Room,而不是SQLite
使用Room库要在应用或模块的build.gradle文件中添加依赖:
dependencies {
def room_version = "2.2.3"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
// optional - Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:$room_version"
// optional - RxJava support for Room
implementation "androidx.room:room-rxjava2:$room_version"
// optional - Guava support for Room, including Optional and ListenableFuture
implementation "androidx.room:room-guava:$room_version"
// Test helpers
testImplementation "androidx.room:room-testing:$room_version"
}
Room包括3个主要组件:
-
数据库:包含数据库持有者,并作为应用已保留的持久关系型数据的底层连接的主要接入点。
-
Entity:表示数据库中的表。
-
DAO:包含用于访问数据库的方法。
它们之间的关系如下
使用Room实体定义数据
使用示例:
@Entity(primaryKeys = {"firstName", "lastName"})
public class User {
//@PrimaryKey
//public int id;
public String firstName;
public String lastName;
}
定义主键如上有两种方式
关于字段的访问权限:要保留某个字段,Room 必须拥有该字段的访问权限。您可以将某个字段设为公开字段,也可以为其提供 getter 和 setter。如果您使用 getter 和 setter 方法,则请注意,这些方法需遵循 Room 中的 JavaBeans 规范。
更多详情请参见
定义对象之间的关系
定义一对多关系
即使您不能使用直接关系,Room 仍允许您定义实体之间的外键约束。
例如,如果存在另一个名为 Book 的实体,您可以使用 @ForeignKey 注释定义该实体与 User 实体的关系,如以下代码段所示:
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "user_id"))
public class Book {
@PrimaryKey public int bookId;
public String title;
@ColumnInfo(name = "user_id") public int userId;
}
在 @ForeignKey 注释中添加 onDelete = CASCADE,可以实现级联删除
更多应用请参见
使用RoomDAO访问数据
@Dao
public interface MyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(User... users);
@Update
public void updateUsers(User... users);
@Delete
public void deleteUsers(User... users);
}
查询信息:
@Query 是 DAO 类中使用的主要注释。它允许您对数据库执行读/写操作
简单查询示例:
@Dao
public interface MyDao {
@Query("SELECT * FROM user")//括号内可以是任意其他SQL语句
public User[] loadAllUsers();
}
在方法返回对象与查询结果字段不匹配时会发出警告
可观察查询: 查询方法对应的返回指声明为LiveData<?>,例如:
@Dao
public interface MyDao {
@Query("SELECT first_name, last_name FROM user WHERE region IN (:regions)")
public LiveData<List<User>> loadUsersFromRegionsSync(List<String> regions);
}
这样,应用的界面就会在数据发生变化时自动更新了
更多数据库访问相关请参见
最后一步:编写数据库类
使用 @Database 注释的类应满足以下条件:
- 是扩展 RoomDatabase 的抽象类。
- 在注释中添加与数据库关联的实体列表。
- 包含具有 0 个参数且返回使用 @Dao 注释的类的抽象方法。
在运行时,您可以通过调用 Room.databaseBuilder() 或 Room.inMemoryDatabaseBuilder() 获取
示例模板:
// Song and Album are classes annotated with @Entity.
@Database(version = 1, entities = {Song.class, Album.class})
abstract class MusicDatabase extends RoomDatabase {
// SongDao is a class annotated with @Dao.
abstract public SongDao getSongDao();
// AlbumDao is a class annotated with @Dao.
abstract public ArtistDao getArtistDao();
// SongAlbumDao is a class annotated with @Dao.
abstract public SongAlbumDao getSongAlbumDao();
}
在上面的例子中有两个表(有entities属性声明)和三个DAO(对应三个获取DAO的方法)
两个获取方法:
public static Builder databaseBuilder (Context context,
Class klass,
String name)
参数/返回名 | 含义 |
---|---|
context | 数据库的上下文。这通常是应用程序上下文。 |
kclass | 用注释Database和扩展 的抽象类RoomDatabase。 |
name | 数据库文件的名称。 |
Builder | 一个RoomDatabaseBuilder实例,用于创建数据库 |
public static Builder inMemoryDatabaseBuilder (Context context,
Class klass)
参数/返回名 | 含义 |
---|---|
context | 数据库的上下文。这通常是应用程序上下文。 |
kclass | 用注释Database和扩展 的抽象类RoomDatabase。 |
Builder | 一个RoomDatabaseBuilder实例,用于创建数据库 |
完成以上步骤,就可以在应用中获取database实例来对数据库进行操作了
实践案例
User
package com.example.demoroom;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity
public class User {
@PrimaryKey
public int uid;
@ColumnInfo(name = "first_name")
public String firstName;
@ColumnInfo(name = "last_name")
public String lastName;
}
UserDAO
package com.example.demoroom;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import java.util.List;
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
List<User> getAll();
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
List<User> loadAllByIds(int[] userIds);
@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1")
User findByName(String first, String last);
@Insert
void insertAll(User... users);
@Delete
void delete(User user);
}
AppDataBase
package com.example.demoroom;
import androidx.room.Database;
import androidx.room.RoomDatabase;
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao getuserDao();
}
MainActivity
package com.example.demoroom;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.room.Room;
import java.util.List;
public class MainActivity extends AppCompatActivity {
UserDao uDAO;
AppDatabase db;
User u;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
u.firstName = new String("Mike");
u.lastName = new String("Mathue");
uDAO = db.getuserDao();
Log.e("", "onCreate: here");
((Button)findViewById(R.id.insert)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
uDAO.insertAll(u);
Toast.makeText(MainActivity.this, "刚刚插入信息:firstname:"+u.firstName+"lastname:"+u.lastName, Toast.LENGTH_SHORT).show();
}
});
((Button)findViewById(R.id.show)).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
List<User> allUsers = uDAO.getAll();
StringBuffer message = new StringBuffer("刚刚插入信息:");
for(int i = 0 ; i < allUsers.size() ; i++) {
message.append("firstname-"+i+allUsers.get(i).firstName+"lastname-"+i+allUsers.get(i).lastName);
}
Toast.makeText(MainActivity.this, message, Toast.LENGTH_SHORT).show();
}
});
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="horizontal">
<Button
android:id="@+id/insert"
android:text="insert a tuple"
android:layout_height="wrap_content"
android:layout_width="match_parent"
tools:ignore="MissingConstraints" />
<Button
android:id="@+id/show"
android:text="show the first tuple"
android:layout_height="wrap_content"
android:layout_width="match_parent"
tools:ignore="MissingConstraints" />
</LinearLayout>