Android 文件与数据存储实战:SharedPreferences、SQLite 与 Room 的渐进式实现

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 的写入流程很固定,顺序上是:

  1. 调用 getSharedPreferences(文件名, 模式) 获取实例。
  2. 调用 edit() 获取 SharedPreferences.Editor
  3. 通过 putString()putBoolean()putInt() 等方法写入键值对。
  4. 调用 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 定义了 idnameage 三列,其中 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();
});

这里的顺序依然很明确:

  1. 先从数据库对象拿到 MyUserDao
  2. 再创建 MyUser 实体并填充字段。
  3. 最后把实体传给 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 管理数据库创建和升级,并围绕 SQLiteDatabaseContentValuesCursor 把增删改查逐步接起来;最后再切换到 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();
        });
    }
}
相关推荐
scofield_gyb2 小时前
【MySQL】表空间丢失处理(Tablespace is missing for table 错误处理)
数据库·mysql
MegaDataFlowers2 小时前
认识O(NlogN)的排序
java·开发语言·排序算法
蘑菇小白2 小时前
基于嵌入式的数据库SQLite
linux·数据库·sqlite
梨落秋霜2 小时前
Python入门篇【连接数据库】
数据库·python·oracle
w1225h2 小时前
【SpringBoot】Spring Boot 项目的打包配置
java·spring boot·后端
李少兄2 小时前
解析 IntelliJ IDEA “Immutable object is modified”警告
java·ide·intellij-idea
客卿1232 小时前
二叉树的层序遍历--思路===bfs的应用,以及java中队列的方法实操
java·算法·宽度优先
健康平安的活着2 小时前
java中事务@Transaction的正确使用和触发回滚机制【经典】
java·开发语言
耶叶2 小时前
Android开发:基于SharedPreferences实现的状态缓存
android·kotlin