Android 文件与数据存储实战:SharedPreferences、SQLite 与 Room 的渐进式实现
目录
- [Android 文件与数据存储实战:SharedPreferences、SQLite 与 Room 的渐进式实现](#Android 文件与数据存储实战:SharedPreferences、SQLite 与 Room 的渐进式实现)
- [1. 前言](#1. 前言)
- [2. SharedPreferences:先把键值对存储流程跑通](#2. SharedPreferences:先把键值对存储流程跑通)
- [2.1 搭建 SharedPreferences 示例页面](#2.1 搭建 SharedPreferences 示例页面)
- [2.2 写入 SharedPreferences 数据](#2.2 写入 SharedPreferences 数据)
- [2.3 查询 SharedPreferences 数据](#2.3 查询 SharedPreferences 数据)
- [2.4 删除 SharedPreferences 数据](#2.4 删除 SharedPreferences 数据)
- [3. SQLite:从建库建表开始进入结构化存储](#3. SQLite:从建库建表开始进入结构化存储)
- [3.1 创建 SQLiteOpenHelper 并定义数据库版本](#3.1 创建 SQLiteOpenHelper 并定义数据库版本)
- [3.2 在 onCreate 中建表,在 onUpgrade 中处理升级](#3.2 在 onCreate 中建表,在 onUpgrade 中处理升级)
- [3.3 搭建 SQLite 示例页面](#3.3 搭建 SQLite 示例页面)
- [3.4 插入数据](#3.4 插入数据)
- [3.5 查询数据](#3.5 查询数据)
- [3.6 添加查询条件与排序](#3.6 添加查询条件与排序)
- [3.7 更新数据](#3.7 更新数据)
- [3.8 删除数据](#3.8 删除数据)
- [4. Room:在 SQLite 之上用对象化方式组织数据库](#4. Room:在 SQLite 之上用对象化方式组织数据库)
- [4.1 添加 Room 依赖](#4.1 添加 Room 依赖)
- [4.2 定义实体类](#4.2 定义实体类)
- [4.3 定义 DAO 接口](#4.3 定义 DAO 接口)
- [4.4 定义 RoomDatabase](#4.4 定义 RoomDatabase)
- [4.5 搭建 Room 示例页面](#4.5 搭建 Room 示例页面)
- [4.6 插入数据](#4.6 插入数据)
- [4.7 查询数据](#4.7 查询数据)
- [4.8 更新数据](#4.8 更新数据)
- [4.9 删除数据](#4.9 删除数据)
- [4.10 数据库结构升级](#4.10 数据库结构升级)
- [5. 小结](#5. 小结)
- [6. 相关代码附录](#6. 相关代码附录)
- [6.1 app/build.gradle](#6.1 app/build.gradle)
- [6.2 activity_spactivity.xml](#6.2 activity_spactivity.xml)
- [6.3 SPActivity.java](#6.3 SPActivity.java)
- [6.4 MySqliteHelper.java](#6.4 MySqliteHelper.java)
- [6.5 activity_sqlite.xml](#6.5 activity_sqlite.xml)
- [6.6 SQLiteActivity.java](#6.6 SQLiteActivity.java)
- [6.7 User.java](#6.7 User.java)
- [6.8 activity_room.xml](#6.8 activity_room.xml)
- [6.9 MyUser.java](#6.9 MyUser.java)
- [6.10 MyUserDao.java](#6.10 MyUserDao.java)
- [6.11 MyUserDatabase.java](#6.11 MyUserDatabase.java)
- [6.12 RoomActivity.java](#6.12 RoomActivity.java)
1. 前言
在 Android 中,数据存储并不是单一方案的问题,而是需要根据数据规模、结构复杂度和访问方式逐步选择合适的实现。简单配置项适合用键值对保存,结构化数据适合放进本地数据库,而当数据库读写逻辑逐渐复杂时,再继续往上抽象到 Room 会更容易维护。
这一组示例正好沿着这样的技术路径展开。先从 SharedPreferences 的页面搭建和键值对读写开始,把最轻量的数据持久化跑通;再进入 SQLite,完成数据库创建、建表以及增删改查;最后过渡到 Room,用实体类、Dao 和数据库对象把同样的数据库操作改造成更面向对象的写法,并补上数据库迁移处理。
2. SharedPreferences:先把键值对存储流程跑通
SharedPreferences 适合保存少量、轻量级的键值对数据,例如登录状态、用户名、主题配置等。这一部分先从一个最简单的示例页面开始,把保存、获取和删除三种常用操作依次接起来。
2.1 搭建 SharedPreferences 示例页面
先准备页面布局。这个页面包含两个输入框、三个按钮和一个用于显示结果的文本区域,正好对应后续的保存、读取和删除操作。
代码路径:/FileAndDataByJavaProject/app/src/main/res/layout/activity_spactivity.xml
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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".SPActivity">
<EditText
android:id="@+id/et_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名" />
<EditText
android:id="@+id/et_user_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入年龄" />
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存数据" />
<Button
android:id="@+id/btn_get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="获取数据" />
<Button
android:id="@+id/btn_remove"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="删除数据" />
<TextView
android:text="label"
android:id="@+id/tv_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
页面效果如下,这里先把输入、点击和结果显示的交互位都预留出来:

接着在 Activity 中先把控件初始化和按钮点击骨架补齐:
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/SPActivity.java
java
public class SPActivity extends AppCompatActivity {
private EditText etUserName;
private EditText etUserAge;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_spactivity);
etUserName = findViewById(R.id.et_user_name);
etUserAge = findViewById(R.id.et_user_age);
TextView textView = findViewById(R.id.tv_label);
findViewById(R.id.btn_save).setOnClickListener(view -> {
});
findViewById(R.id.btn_get).setOnClickListener(view -> {
});
findViewById(R.id.btn_remove).setOnClickListener(view -> {
});
}
}
2.2 写入 SharedPreferences 数据
SharedPreferences 的写入流程很固定,顺序上是:
- 调用
getSharedPreferences(文件名, 模式)获取实例。 - 调用
edit()获取SharedPreferences.Editor。 - 通过
putString()、putBoolean()、putInt()等方法写入键值对。 - 调用
apply()或commit()提交变更。
对应的保存按钮实现如下:
java
findViewById(R.id.btn_save).setOnClickListener(view -> {
//获取sp实例
SharedPreferences sp = getSharedPreferences("mysharedPre", Context.MODE_PRIVATE);
//开始编辑
SharedPreferences.Editor edit = sp.edit();
//保存数据
edit.putString("key_user_name", etUserName.getText().toString());
edit.putBoolean("key_is_login", true);
edit.putInt("key_user_age", Integer.valueOf(etUserAge.getText().toString()));
//异步数据提交
edit.apply();
//同步提交数据,同步会阻塞主线程
//edit.commit();
});
这里的 Context.MODE_PRIVATE 表示该文件是应用私有文件,其他应用无法直接访问。apply() 是异步提交,适合主线程场景;commit() 是同步提交,会阻塞当前线程,通常更适合放在子线程里。
在输入框中输入用户名和年龄后点击保存,系统会在应用私有目录生成对应的 XML 文件。运行后可以在 Device Explorer 中查看:
/data/data/com.ls.fileanddatabyjavaproject/shared_prefs/mysharedPre.xml
相关界面如下:


2.3 查询 SharedPreferences 数据
写入完成后,下一步就是从相同的文件里按 key 读回对应的值。SharedPreferences 的读取方法和写入方法一一对应,例如 putString() 对应 getString(),putBoolean() 对应 getBoolean()。
java
findViewById(R.id.btn_get).setOnClickListener(view -> {
//获取sp实例
SharedPreferences sp = getSharedPreferences("mysharedPre", Context.MODE_PRIVATE);
String userName = sp.getString("key_user_name", "notfound");
boolean isLogin = sp.getBoolean("key_is_login", false);
textView.setText("用户:" + userName + (isLogin ? "已登录" : "未登录"));
});
get(key, defaultValue) 中的第二个参数表示没有查到时返回的默认值。这样即使当前 key 不存在,页面逻辑也不会直接因为空值而中断。
这一类存储方式很适合体量较小的配置数据,但它并不适合存储大量结构化内容。应用卸载后,对应的 SharedPreferences 文件也会被一并清空。
2.4 删除 SharedPreferences 数据
SharedPreferences 的删除也分两类:删除指定 key,或者清空整个文件。
java
findViewById(R.id.btn_remove).setOnClickListener(view -> {
//获取sp实例
SharedPreferences sp = getSharedPreferences("mysharedPre", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.remove("key_is_login");
edit.clear();//删除mysharedPre文件下所有数据
edit.apply();
});
这段代码先调用 remove("key_is_login") 删除单个键,再调用 clear() 清空整个文件中的数据,最后通过 apply() 提交更改。因为 clear() 会把当前文件里的所有键值对都删掉,所以它的效果会覆盖前面的单 key 删除。
3. SQLite:从建库建表开始进入结构化存储
SharedPreferences 更适合少量键值对,而一旦数据进入结构化、批量化读写场景,例如浏览记录、用户列表、本地业务数据,就更适合切换到 SQLite。
SQLite 是 Android 内置的轻量级关系型数据库。它适合存储大量、结构化的数据,但建表和增删改查都属于耗时操作,示例中虽然直接在点击事件里完成,实际项目里仍然更建议放到后台线程中执行。
3.1 创建 SQLiteOpenHelper 并定义数据库版本
使用 SQLite 时,第一步通常不是直接写 SQL,而是先定义一个继承自 SQLiteOpenHelper 的辅助类,用它统一管理数据库名称、版本号、首次建库和升级逻辑。
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/MySqliteHelper.java
java
public class MySqliteHelper extends SQLiteOpenHelper {
//数据库的名字
private static final String DATABASE_NAME = "MyDatabase.db";
//数据库版本号,假如修改表结构、删除表、添加表 需要手动提升版本号
private static final int DATABASE_VERSION = 1;
public MySqliteHelper(@Nullable Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
}
数据库名和版本号会直接影响数据库文件的创建和升级行为。后续如果表结构发生变化,就需要手动提升版本号,才能触发升级流程。
3.2 在 onCreate 中建表,在 onUpgrade 中处理升级
SQLiteOpenHelper 中最关键的两个方法是 onCreate() 和 onUpgrade()。
onCreate()会在数据库第一次真正创建时执行,一般用来建表。onUpgrade()会在数据库版本号升级时触发,一般用来迁移或重建表结构。
当前示例中的实现如下:
java
@Override
public void onCreate(SQLiteDatabase db) {
//建立用户表:id、name、age
// 创建表的SQL语句
String CREATE_USER_TABLE =
"CREATE TABLE " + "user" + "(" +
//将id设定为主键,并且使用自增
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT NOT NULL, " +
"age INTEGER NOT NULL" +
");";
//通过db对象执行sql语句
db.execSQL(CREATE_USER_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
//删除旧表
db.execSQL("DROP TABLE IF EXISTS uuer");
//重新onCreate 创建新的表
onCreate(db);
}
这里的建表 SQL 定义了 id、name 和 age 三列,其中 id 是主键并自动递增。
需要特别注意两点:
第一,数据库文件不会因为 new MySqliteHelper(context) 就立刻创建,真正创建动作发生在第一次调用 getWritableDatabase() 或 getReadableDatabase() 时。
第二,当前升级示例里的删除表语句写的是 uuer,而建表名称是 user。这类问题可以在讲解里指出,但不直接改写原代码。
3.3 搭建 SQLite 示例页面
在进行增删改查之前,先把 SQLite 操作页面搭起来。这个页面和前面的 SharedPreferences 示例类似,也是一组输入框加四个按钮,只是这次四个按钮分别对应插入、删除、更新和查询。
代码路径:/FileAndDataByJavaProject/app/src/main/res/layout/activity_sqlite.xml
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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".SQLiteActivity">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入姓名" />
<EditText
android:id="@+id/et_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入年龄" />
<Button
android:id="@+id/btn_insert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="插入数据" />
<Button
android:id="@+id/btn_delete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="删除数据" />
<Button
android:id="@+id/btn_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="更新数据" />
<Button
android:id="@+id/btn_query"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询数据" />
<TextView
android:id="@+id/tv_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询到如下信息:\n" />
</LinearLayout>
页面效果如下:

Activity 先把控件和点击事件全部准备好:
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/SQLiteActivity.java
java
public class SQLiteActivity extends AppCompatActivity {
private static final String TAG = "SQLiteActivity";
private EditText etName;
private EditText etAge;
private TextView tvInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite);
etName = findViewById(R.id.et_name);
etAge = findViewById(R.id.et_age);
tvInfo = findViewById(R.id.tv_info);
//插入数据
findViewById(R.id.btn_insert).setOnClickListener(view -> {
});
//查询数据
findViewById(R.id.btn_query).setOnClickListener(view -> {
});
//更新数据
findViewById(R.id.btn_update).setOnClickListener(view -> {
});
//删除数据
findViewById(R.id.btn_delete).setOnClickListener(view -> {
});
}
}
3.4 插入数据
插入数据时,先拿到可写数据库对象,再用 ContentValues 组织一条记录,最后调用 insert() 写入指定表。
java
//插入数据
findViewById(R.id.btn_insert).setOnClickListener(view -> {
MySqliteHelper mySqliteHelper = new MySqliteHelper(this);
//获取可以写入的数据库对象
SQLiteDatabase writableDatabase = mySqliteHelper.getWritableDatabase();
//创建contentValues对象,往里面放一条数据
ContentValues values = new ContentValues();
values.put("name", etName.getText().toString().trim());
values.put("age", etAge.getText().toString().trim());
//把这条数据插入到数据库中的user表,如果插入成功返回插入行号,失败返回-1
long newRowId = writableDatabase.insert("user", null, values);
Log.i(TAG, "onCreate: 插入数据 newRowId = " + newRowId);
});
insert() 成功时返回新插入行的行号,失败时返回 -1。这一步一旦调用了 getWritableDatabase(),数据库文件 MyDatabase.db 也会真正被创建出来。
运行后可以在 Device Explorer 中找到数据库文件:
/data/data/com.ls.fileanddatabyjavaproject/databases/MyDatabase.db
对应查看过程如下:




3.5 查询数据
查询阶段会用到 query() 方法和 Cursor。整体过程是:先获取可读数据库,再执行查询,再循环遍历 Cursor 的每一行,最后把查询结果收集起来并显示到页面上。
java
findViewById(R.id.btn_query).setOnClickListener(view -> {
MySqliteHelper mySqliteHelper = new MySqliteHelper(this);
//获取可以读写的数据库对象
SQLiteDatabase readableDatabase = mySqliteHelper.getReadableDatabase();
//传入查询条件给query方法,并且获取到cursor,用它来遍历查询结果
Cursor cursor = readableDatabase.query(
"user",
new String[]{"id", "name", "age"},
null,
null,
null,
null,
null);
ArrayList<User> users = new ArrayList<>();
//moveToNext把cursor光标移动到下一行数据,如果返回true,表示移动成功并且数据有效,返回false表示没有数据了
while (cursor.moveToNext()) {
//如果id这一列存在,那么返回这列的位置,如果不存在,直接抛异常
int idIndex = cursor.getColumnIndexOrThrow("id");
//通过id索引的位置,获取这一行数据在id索引存储的数据
int id = cursor.getInt(idIndex);
//获取name
String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
//获取age
int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
//查询到的数据 包装成user类型对象
User user = new User(name, id, age);
//把user放到容器,方便管理
users.add(user);
}
//关闭对象,释放资源
cursor.close();
//显示数据到textview
tvInfo.setText(users.toString());
Log.i(TAG, "onCreate: users = " + users.toString());
});
这里的关键点不在于 API 数量,而在于每一步的职责:
query()返回的是 Cursor,而不是最终数据列表。moveToNext()每执行一次,Cursor 就会移动到下一行。getColumnIndexOrThrow()是先拿列索引,再通过索引取出当前行该列的具体值。- 每循环一次,就构造一个
User对象,并加入ArrayList<User>。 - 查询完成后必须调用
cursor.close()释放资源。
承接查询结果的 User 类如下:
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/User.java
java
public class User {
public String name;
public int id;
public int age;
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
3.6 添加查询条件与排序
无条件查询跑通之后,接着把查询条件加上去。query() 最常用的几个参数包括表名、列名数组、条件表达式、条件参数数组以及排序规则。
如下写法表示查询年龄大于 30,并且姓名包含"老"的记录,最后按年龄升序排列:
java
findViewById(R.id.btn_query).setOnClickListener(view -> {
MySqliteHelper mySqliteHelper = new MySqliteHelper(this);
SQLiteDatabase readableDatabase = mySqliteHelper.getReadableDatabase();
Cursor cursor = readableDatabase.query(
"user",
new String[]{"id", "name", "age"},
"age > ? AND name LIKE ?",
new String[]{"30","%老%"},
null,
null,
//orderBy如果为null,表示默认排序。 ASC=升序 DESC=降序
"age ASC");
ArrayList<User> users = new ArrayList<>();
while (cursor.moveToNext()) {
int idIndex = cursor.getColumnIndexOrThrow("id");
int id = cursor.getInt(idIndex);
String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
User user = new User(name, id, age);
users.add(user);
}
使用 ? 占位符再配合参数数组,不仅写法更清晰,也可以避免把参数直接拼进 SQL 字符串带来的风险。
下面这张图对应的就是 query() 参数列表在 IDE 中的定义位置:

3.7 更新数据
更新数据和插入数据一样,都要先拿到可写数据库对象,然后用 ContentValues 表示要更新成什么值,再通过条件指定更新哪些记录。
java
findViewById(R.id.btn_update).setOnClickListener(view -> {
MySqliteHelper mySqliteHelper = new MySqliteHelper(this);
//获取可以写入的数据库对象
SQLiteDatabase writableDatabase = mySqliteHelper.getWritableDatabase();
//准备好需要更新的数据
ContentValues values = new ContentValues();
values.put("name",etName.getText().toString());
values.put("age",etAge.getText().toString());
//执行更新操作,获取被更新的记录数量
int count = writableDatabase.update("user", values,
"name = ?",
new String[]{"张消"});
});
这里的意思是:把 name = 张消 的记录更新为当前输入框中的姓名和年龄,update() 返回值是被更新的记录数量。
3.8 删除数据
删除的写法和更新类似,也是先拿到可写数据库对象,再通过条件语句指定要删掉哪些记录。
java
findViewById(R.id.btn_delete).setOnClickListener(view -> {
MySqliteHelper mySqliteHelper = new MySqliteHelper(this);
//获取可以写入的数据库对象
SQLiteDatabase writableDatabase = mySqliteHelper.getWritableDatabase();
//删除age=30的所有数据
int count = writableDatabase.delete("user", "age = ?", new String[]{"30"});
});
这个例子删除的是所有 age = 30 的数据,返回值同样表示实际删除的记录数量。
4. Room:在 SQLite 之上用对象化方式组织数据库
SQLite 的原生 API 很灵活,但随着数据库对象、SQL 语句、实体映射和线程管理越来越多,代码也会越来越分散。Room 就是在这个阶段引入的,它对 SQLite 做了一层抽象,让数据库操作可以围绕实体类和 Dao 接口展开。
这一部分继续沿着示例的原始顺序,先加依赖,再定义 Entity、Dao 和 Database,最后在 Activity 中完成插入、查询、更新、删除和迁移。
4.1 添加 Room 依赖
Room 先从依赖接入开始。当前工程在 app/build.gradle 中添加了运行时库和编译期注解处理器:
代码路径:/FileAndDataByJavaProject/app/build.gradle
javascript
//添加room运行时依赖库
implementation "androidx.room:room-runtime:2.6.1"
// Room 编译时注解处理器
annotationProcessor "androidx.room:room-compiler:2.6.1"
完整依赖配置如下:
gradle
dependencies {
implementation libs.appcompat
implementation libs.material
implementation libs.activity
implementation libs.constraintlayout
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
//添加room运行时依赖库
implementation "androidx.room:room-runtime:2.6.1"
// Room 编译时注解处理器
annotationProcessor "androidx.room:room-compiler:2.6.1"
//Okhttp
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
}
4.2 定义实体类
Room 的第一步是定义实体类,也就是数据库表对应的 Java 对象。通过注解可以把类和字段映射到具体的表和列上。
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/room/MyUser.java
java
@Entity(tableName = "user-room")
public class MyUser {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "user_age")
private int age;
@ColumnInfo(name = "user_name")
private String name;
@ColumnInfo(name = "user_email")
private String email;
}
这里的注解关系很清楚:
@Entity(tableName = "user-room")指定表名。@PrimaryKey(autoGenerate = true)指定主键自增。@ColumnInfo指定字段映射到的数据表列名。
4.3 定义 DAO 接口
实体类定义好之后,下一步是定义数据库操作方法。Room 把这一层抽象成 Dao 接口,所有增删改查都通过接口方法暴露出来。
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/room/MyUserDao.java
java
@Dao
public interface MyUserDao {
@Insert
void insertUser(MyUser user);
/**
* 通过name查询user-room表中的某个数据
*
* @param name
*/
@Query("SELECT * FROM `user-room` WHERE user_name = :name")
List<MyUser> getUserByName(String name);
/**
* 通过MyUSer类型直接更新数据库,Room会根据MyUser中的主键(id)来查找对应的数据,并且更新
*
* @param user
* @return
*/
@Update
int updateUser(MyUser user);
/**
* 更新user-room这个表中,名字是等于name(第一个参数)的数据,把这条数据的user_age改成第二个参数
*
* @param name
* @param age
* @return
*/
@Query("UPDATE `user-room` SET user_age = :age WHERE user_name = :name")
int updateUser(String name, int age);
/**
* 删除对应的user信息,并且需要通过user中id主键做匹配
*
* @param user
* @return
*/
@Delete
int delUser(MyUser user);
/**
* 通过name删除对应的用户
*
* @param name
* @return
*/
@Query("DELETE FROM `user-room` WHERE user_name = :name")
int delUser(String name);
}
这里既演示了用 @Insert、@Update、@Delete 直接操作实体,也演示了用 @Query 自定义 SQL 条件更新和删除。这样在使用时可以根据场景选择更贴切的写法。
4.4 定义 RoomDatabase
接着定义数据库对象本身。Room 里的数据库类需要继承 RoomDatabase,并通过 @Database 注解声明当前数据库包含哪些实体、当前版本号是多少。
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/room/MyUserDatabase.java
java
@Database(entities = {MyUser.class}, version = 2)
public abstract class MyUserDatabase extends RoomDatabase {
public abstract MyUserDao userDao();
}
这里的 userDao() 是一个抽象方法,真正运行时会由 Room 生成实现,用来返回数据库操作对象。
4.5 搭建 Room 示例页面
Room 的示例页面在交互上和 SQLite 页面保持一致,仍然是姓名、年龄输入框加四个按钮。这样可以直接对照两种方案在代码结构上的区别。
代码路径:/FileAndDataByJavaProject/app/src/main/res/layout/activity_room.xml
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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".RoomActivity">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入姓名" />
<EditText
android:id="@+id/et_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入年龄" />
<Button
android:id="@+id/btn_insert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="插入数据" />
<Button
android:id="@+id/btn_delete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="删除数据" />
<Button
android:id="@+id/btn_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="更新数据" />
<Button
android:id="@+id/btn_query"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询数据" />
<TextView
android:id="@+id/tv_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询到如下信息:\n" />
</LinearLayout>
页面效果如下:

Activity 的基础结构如下:
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/RoomActivity.java
java
public class RoomActivity extends AppCompatActivity {
private EditText etName;
private EditText etAge;
private TextView tvInfo;
private static final String TAG = "RoomActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_room);
etName = findViewById(R.id.et_name);
etAge = findViewById(R.id.et_age);
tvInfo = findViewById(R.id.tv_info);
//插入数据
findViewById(R.id.btn_insert).setOnClickListener(view -> {
});
//查询数据
findViewById(R.id.btn_query).setOnClickListener(view -> {
});
//更新数据
findViewById(R.id.btn_update).setOnClickListener(view -> {
});
//删除数据
findViewById(R.id.btn_delete).setOnClickListener(view -> {
});
}
}
4.6 插入数据
真正开始操作 Room 之前,先通过 Room.databaseBuilder() 构建数据库实例:
java
//获取MyUserDatabase实例
MyUserDatabase userDatabase =
Room
.databaseBuilder(this,MyUserDatabase.class, "user-database")
.build();
接着把插入逻辑补进按钮点击事件里。因为数据库操作属于耗时任务,所以这里明确放进了子线程中执行:
java
//插入数据
findViewById(R.id.btn_insert).setOnClickListener(view -> {
new Thread() {
@Override
public void run() {
super.run();
//通过db对象获取到了userDao
MyUserDao userDao = userDatabase.userDao();
MyUser user = new MyUser();
user.setName(etName.getText().toString().trim());
user.setAge(Integer.valueOf(etAge.getText().toString().trim()));
//调用插入方法
userDao.insertUser(user);
//为了方便测试.db文件,需要手动close一次数据库操作
//userDatabase.close();
}
}.start();
});
这里的顺序依然很明确:
- 先从数据库对象拿到
MyUserDao。 - 再创建
MyUser实体并填充字段。 - 最后把实体传给
insertUser()完成写入。
运行后可以在 Device Explorer 中看到数据库文件目录:
/data/data/com.ls.fileanddatabyjavaproject/databases
如果需要导出一个更明确的 .db 文件,示例中还给出了一种更方便的写法:把数据库名直接写成带 .db 后缀的形式,并在必要时调用 userDatabase.close() 让文件更完整地落盘。
java
MyUserDatabase userDatabase =
Room
.databaseBuilder(this,MyUserDatabase.class, "user-database.db")
.build();
对应的查看过程如下:


4.7 查询数据
Room 查询时不再需要手动处理 Cursor,逻辑会直接回到 Dao 接口定义的方法上。先在 Dao 中定义好查询语句:
java
@Query("SELECT * FROM `user-room` WHERE user_name = :name")
List<MyUser> getUserByName(String name);
然后在页面中继续放到子线程执行,再通过 runOnUiThread() 把结果更新回主线程界面:
java
//查询数据
findViewById(R.id.btn_query).setOnClickListener(view -> {
new Thread() {
@Override
public void run() {
super.run();
List<MyUser> users = userDatabase.userDao().getUserByName(etName.getText().toString().trim());
Log.i(TAG, "run:users.size" + users.size());
if (users.size() > 0) {
runOnUiThread(new Runnable() {
@Override
public void run() {
//显示查询到的用户数据
MyUser user = users.get(0);
tvInfo.setText(tvInfo.getText() + "\n"
+ user.getName() + " " + user.getAge());
}
});
}
}
}.start();
});
和 SQLite 相比,这里最大的变化是:查询结果已经直接被映射成了 List<MyUser>,不再需要自己逐列读取、再手动组装对象。
4.8 更新数据
Room 的更新有两种思路。
一种是传入一个包含主键的实体对象,用 @Update 让 Room 根据主键去定位并更新对应记录:
java
@Update
int updateUser(MyUser user);
按钮点击中对应的用法如下:
java
//更新数据
findViewById(R.id.btn_update).setOnClickListener(view -> {
new Thread() {
@Override
public void run() {
super.run();
MyUser user = new MyUser();
//指定要修改的用户对应的id
user.setId(1);
user.setName(etName.getText().toString().trim());
user.setAge(Integer.valueOf(etAge.getText().toString().trim()));
userDatabase.userDao().updateUser(user);
//通过名字查找到对应的信息,修改这个用户的年龄
userDatabase.userDao().updateUser("老王", 99);
}
}.start();
});
另一种是像上面最后一行那样,直接在 Dao 中定义自定义 SQL:
java
@Query("UPDATE `user-room` SET user_age = :age WHERE user_name = :name")
int updateUser(String name, int age);
这样既可以按主键整体更新实体,也可以按自定义条件只修改某几个字段。
4.9 删除数据
删除同样也有两套方式。
先看 Dao 中的定义:
java
@Delete
int delUser(MyUser user);
@Query("DELETE FROM `user-room` WHERE user_name = :name")
int delUser(String name);
再看按钮点击里的实际调用:
java
//删除数据
findViewById(R.id.btn_delete).setOnClickListener(view -> {
new Thread() {
@Override
public void run() {
super.run();
// 删除对应id的用户
MyUser user = new MyUser();
user.setId(1);
userDatabase.userDao().delUser(user);
//在数据库当中删除名叫老王的数据
userDatabase.userDao().delUser("老王");
}
}.start();
});
一种是通过带主键的实体删除,另一种是通过名字执行条件删除。两种方式都被放在这个示例里,便于对比不同写法的适用场景。
4.10 数据库结构升级
当数据库结构发生变化,例如新增字段、修改表结构时,就不能只停留在初始建表阶段了。Room 为这种场景提供了 Migration 机制。
当前示例中,MyUserDatabase 把数据库版本设为 2,并定义了从 1 升级到 2 的迁移策略:
java
@Database(entities = {MyUser.class}, version = 2)
public abstract class MyUserDatabase extends RoomDatabase {
public abstract MyUserDao userDao();
/**
* 数据库升级策略:
* 1、如果说手机安卓了app,这个时候数据库并没有真的被创建
* 2、数据库在真正执行数据库操作的时候才会被创建
* 3、这个时候数据库版本是 = 1
* 4、重新运行run(或者是更新app)
* 5、如果新的app数据库版本升级,就会执行当前策略
*/
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {
//数据库从版本1升级到版本2的时候,添加一条数据库的升级策略
supportSQLiteDatabase.execSQL("ALTER TABLE `user-room` ADD COLUMN user_email TEXT");
}
};
}
这里的迁移逻辑是给 user-room 表新增一列 user_email,对应的实体类 MyUser 中也确实已经增加了 email 字段。
定义完迁移对象之后,还需要在构建数据库实例时显式挂上这条迁移策略:
java
MyUserDatabase userDatabase = Room.databaseBuilder(this,
MyUserDatabase.class, "user-database")
.addMigrations(MyUserDatabase.MIGRATION_1_2)
.build();
如果需要检查升级后的数据库结构,同样可以在合适的时机调用 close() 关闭数据库对象,再到设备中的数据库目录查看最新生成的文件。

5. 小结
这一组数据存储示例是按存储能力逐步升级的过程展开的。
先用 SharedPreferences 完成轻量键值对数据的保存、获取和删除,理解应用私有 XML 文件的生成方式;再进入 SQLite,用 SQLiteOpenHelper 管理数据库创建和升级,并围绕 SQLiteDatabase、ContentValues、Cursor 把增删改查逐步接起来;最后再切换到 Room,把实体类、Dao、数据库实例和迁移策略都组织成更面向对象的结构,同时把数据库操作放进子线程中执行。
从这条实现路径回头看,不同方案的差异主要体现在三件事上:数据结构是否简单、是否需要批量和条件查询、以及代码是否已经复杂到需要更高层的抽象。当数据仍然只是少量配置项时,SharedPreferences 已经足够;当数据开始结构化并需要 SQL 查询能力时,SQLite 更合适;当数据库代码规模进一步扩大,Room 则能显著降低样板代码和维护成本。
6. 相关代码附录
6.1 app/build.gradle
代码路径:/FileAndDataByJavaProject/app/build.gradle
gradle
plugins {
alias(libs.plugins.androidApplication)
}
android {
namespace 'com.ls.fileanddatabyjavaproject'
compileSdk 34
defaultConfig {
applicationId "com.ls.fileanddatabyjavaproject"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation libs.appcompat
implementation libs.material
implementation libs.activity
implementation libs.constraintlayout
testImplementation libs.junit
androidTestImplementation libs.ext.junit
androidTestImplementation libs.espresso.core
//添加room运行时依赖库
implementation "androidx.room:room-runtime:2.6.1"
// Room 编译时注解处理器
annotationProcessor "androidx.room:room-compiler:2.6.1"
//Okhttp
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
}
6.2 activity_spactivity.xml
代码路径:/FileAndDataByJavaProject/app/src/main/res/layout/activity_spactivity.xml
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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".SPActivity">
<EditText
android:id="@+id/et_user_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入用户名" />
<EditText
android:id="@+id/et_user_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入年龄" />
<Button
android:id="@+id/btn_save"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存数据" />
<Button
android:id="@+id/btn_get"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="获取数据" />
<Button
android:id="@+id/btn_remove"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="删除数据" />
<TextView
android:text="label"
android:id="@+id/tv_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
6.3 SPActivity.java
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/SPActivity.java
java
public class SPActivity extends AppCompatActivity {
private EditText etUserName;
private EditText etUserAge;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_spactivity);
etUserName = findViewById(R.id.et_user_name);
etUserAge = findViewById(R.id.et_user_age);
TextView textView = findViewById(R.id.tv_label);
findViewById(R.id.btn_save).setOnClickListener(view -> {
SharedPreferences sp = getSharedPreferences("mysharedPre", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.putString("key_user_name", etUserName.getText().toString());
edit.putBoolean("key_is_login", true);
edit.putInt("key_user_age", Integer.valueOf(etUserAge.getText().toString()));
edit.apply();
});
findViewById(R.id.btn_get).setOnClickListener(view -> {
SharedPreferences sp = getSharedPreferences("mysharedPre", Context.MODE_PRIVATE);
String userName = sp.getString("key_user_name", "notfound");
boolean isLogin = sp.getBoolean("key_is_login", false);
textView.setText("用户:" + userName + (isLogin ? "已登录" : "未登录"));
});
findViewById(R.id.btn_remove).setOnClickListener(view -> {
SharedPreferences sp = getSharedPreferences("mysharedPre", Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sp.edit();
edit.remove("key_is_login");
edit.clear();
edit.apply();
});
}
}
6.4 MySqliteHelper.java
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/MySqliteHelper.java
java
public class MySqliteHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "MyDatabase.db";
private static final int DATABASE_VERSION = 1;
public MySqliteHelper(@Nullable Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String CREATE_USER_TABLE =
"CREATE TABLE " + "user" + "(" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT NOT NULL, " +
"age INTEGER NOT NULL" +
");";
db.execSQL(CREATE_USER_TABLE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS uuer");
onCreate(db);
}
}
6.5 activity_sqlite.xml
代码路径:/FileAndDataByJavaProject/app/src/main/res/layout/activity_sqlite.xml
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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".SQLiteActivity">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入姓名" />
<EditText
android:id="@+id/et_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入年龄" />
<Button
android:id="@+id/btn_insert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="插入数据" />
<Button
android:id="@+id/btn_delete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="删除数据" />
<Button
android:id="@+id/btn_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="更新数据" />
<Button
android:id="@+id/btn_query"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询数据" />
<TextView
android:id="@+id/tv_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询到如下信息:\n" />
</LinearLayout>
6.6 SQLiteActivity.java
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/SQLiteActivity.java
java
public class SQLiteActivity extends AppCompatActivity {
private static final String TAG = "SQLiteActivity";
private EditText etName;
private EditText etAge;
private TextView tvInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite);
etName = findViewById(R.id.et_name);
etAge = findViewById(R.id.et_age);
tvInfo = findViewById(R.id.tv_info);
findViewById(R.id.btn_insert).setOnClickListener(view -> {
MySqliteHelper mySqliteHelper = new MySqliteHelper(this);
SQLiteDatabase writableDatabase = mySqliteHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", etName.getText().toString().trim());
values.put("age", etAge.getText().toString().trim());
long newRowId = writableDatabase.insert("user", null, values);
Log.i(TAG, "onCreate: 插入数据 newRowId = " + newRowId);
});
findViewById(R.id.btn_query).setOnClickListener(view -> {
MySqliteHelper mySqliteHelper = new MySqliteHelper(this);
SQLiteDatabase readableDatabase = mySqliteHelper.getReadableDatabase();
Cursor cursor = readableDatabase.query(
"user",
new String[]{"id", "name", "age"},
"age > ? AND name LIKE ?",
new String[]{"30","%老%"},
null,
null,
"age ASC");
ArrayList<User> users = new ArrayList<>();
while (cursor.moveToNext()) {
int idIndex = cursor.getColumnIndexOrThrow("id");
int id = cursor.getInt(idIndex);
String name = cursor.getString(cursor.getColumnIndexOrThrow("name"));
int age = cursor.getInt(cursor.getColumnIndexOrThrow("age"));
User user = new User(name, id, age);
users.add(user);
}
cursor.close();
tvInfo.setText(users.toString());
Log.i(TAG, "onCreate: users = " + users.toString());
});
findViewById(R.id.btn_update).setOnClickListener(view -> {
MySqliteHelper mySqliteHelper = new MySqliteHelper(this);
SQLiteDatabase writableDatabase = mySqliteHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name",etName.getText().toString());
values.put("age",etAge.getText().toString());
int count = writableDatabase.update("user", values,
"name = ?",
new String[]{"张消"});
});
findViewById(R.id.btn_delete).setOnClickListener(view -> {
MySqliteHelper mySqliteHelper = new MySqliteHelper(this);
SQLiteDatabase writableDatabase = mySqliteHelper.getWritableDatabase();
int count = writableDatabase.delete("user", "age = ?", new String[]{"30"});
});
}
}
6.7 User.java
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/User.java
java
public class User {
public String name;
public int id;
public int age;
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
6.8 activity_room.xml
代码路径:/FileAndDataByJavaProject/app/src/main/res/layout/activity_room.xml
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:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".RoomActivity">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入姓名" />
<EditText
android:id="@+id/et_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入年龄" />
<Button
android:id="@+id/btn_insert"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="插入数据" />
<Button
android:id="@+id/btn_delete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="删除数据" />
<Button
android:id="@+id/btn_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="更新数据" />
<Button
android:id="@+id/btn_query"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询数据" />
<TextView
android:id="@+id/tv_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查询到如下信息:\n" />
</LinearLayout>
6.9 MyUser.java
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/room/MyUser.java
java
@Entity(tableName = "user-room")
public class MyUser {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "user_age")
private int age;
@ColumnInfo(name = "user_name")
private String name;
@ColumnInfo(name = "user_email")
private String email;
}
6.10 MyUserDao.java
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/room/MyUserDao.java
java
@Dao
public interface MyUserDao {
@Insert
void insertUser(MyUser user);
@Query("SELECT * FROM `user-room` WHERE user_name = :name")
List<MyUser> getUserByName(String name);
@Update
int updateUser(MyUser user);
@Query("UPDATE `user-room` SET user_age = :age WHERE user_name = :name")
int updateUser(String name, int age);
@Delete
int delUser(MyUser user);
@Query("DELETE FROM `user-room` WHERE user_name = :name")
int delUser(String name);
}
6.11 MyUserDatabase.java
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/room/MyUserDatabase.java
java
@Database(entities = {MyUser.class}, version = 2)
public abstract class MyUserDatabase extends RoomDatabase {
public abstract MyUserDao userDao();
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase supportSQLiteDatabase) {
supportSQLiteDatabase.execSQL("ALTER TABLE `user-room` ADD COLUMN user_email TEXT");
}
};
}
6.12 RoomActivity.java
代码路径:/FileAndDataByJavaProject/app/src/main/java/com/ls/fileanddatabyjavaproject/RoomActivity.java
java
public class RoomActivity extends AppCompatActivity {
private EditText etName;
private EditText etAge;
private TextView tvInfo;
private static final String TAG = "RoomActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_room);
etName = findViewById(R.id.et_name);
etAge = findViewById(R.id.et_age);
tvInfo = findViewById(R.id.tv_info);
MyUserDatabase userDatabase = Room.databaseBuilder(this,
MyUserDatabase.class, "user-database")
.addMigrations(MyUserDatabase.MIGRATION_1_2)
.build();
findViewById(R.id.btn_insert).setOnClickListener(view -> {
new Thread() {
@Override
public void run() {
super.run();
MyUserDao userDao = userDatabase.userDao();
MyUser user = new MyUser();
user.setName(etName.getText().toString().trim());
user.setAge(Integer.valueOf(etAge.getText().toString().trim()));
userDao.insertUser(user);
}
}.start();
});
findViewById(R.id.btn_query).setOnClickListener(view -> {
new Thread() {
@Override
public void run() {
super.run();
List<MyUser> users = userDatabase.userDao().getUserByName(etName.getText().toString().trim());
Log.i(TAG, "run:users.size" + users.size());
if (users.size() > 0) {
runOnUiThread(new Runnable() {
@Override
public void run() {
MyUser user = users.get(0);
tvInfo.setText(tvInfo.getText() + "\n"
+ user.getName() + " " + user.getAge());
}
});
}
}
}.start();
});
findViewById(R.id.btn_update).setOnClickListener(view -> {
new Thread() {
@Override
public void run() {
super.run();
MyUser user = new MyUser();
user.setId(1);
user.setName(etName.getText().toString().trim());
user.setAge(Integer.valueOf(etAge.getText().toString().trim()));
userDatabase.userDao().updateUser(user);
userDatabase.userDao().updateUser("老王", 99);
}
}.start();
});
findViewById(R.id.btn_delete).setOnClickListener(view -> {
new Thread() {
@Override
public void run() {
super.run();
MyUser user = new MyUser();
user.setId(1);
userDatabase.userDao().delUser(user);
userDatabase.userDao().delUser("老王");
}
}.start();
});
}
}