Android Room 记录一个Update语句不生效的问题解决记录

news2025/1/19 17:10:38

代码展示

1.数据实体类
@Entity
public class User {
    @PrimaryKey(autoGenerate = true)
    private long id;
    private String name;
    private String age;
    private String sex;

    public User(String name, String age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

说明:用户表,包括id(作为主键,自动递增)、name、age、sex四个字段

2.Dao类
@Dao
public interface UserDao {

    @Update
    void updateUser(User user);

}

说明:只展示更新语句

3.用户仓库类
/**
 * 用户仓库类,负责用户数据的增删改查操作。
 */
public class UserRepository {

    private final UserDao userDao;

    /**
     * 构造函数,初始化用户数据访问对象。
     *
     * @param database 应用数据库实例。
     */
    public UserRepository(AppDatabase database) {
        this.userDao = database.getUserDao();
    }

    /**
     * 更新用户信息。如果用户不存在,则操作失败。
     *
     * @param user 要更新的用户对象。
     * @return 返回操作结果,成功或失败。
     */
    @Transaction
    public UserRepositoryResult updateUser(User user) {
        // 确认用户存在
        if (userDao.findUserByName(user.getName()) == null) {
            return new UserRepositoryResult(
                    UserRepositoryResult.Type.FAILURE,
                    RESULT_MESSAGE_USER_NOT_EXIST);
        }
        userDao.updateUser(user);
        return UserRepositoryResult.success();
    }

}

说明:只展示update实现

4.MainViewModel
/**
 * 主要的ViewModel类,用于处理与用户相关的数据操作。
 */
public class MainViewModel extends ViewModel {
    // 用户数据仓库接口
    private final UserRepository userRepository;
    // 执行器服务,用于在后台线程中执行数据库操作
    private static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();

    /**
     * 构造函数,初始化用户数据仓库。
     *
     * @param userRepository 用户数据仓库实例。
     */
    public MainViewModel(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    // 用于存储更新用户操作结果的LiveData对象
    private final MutableLiveData<UserRepositoryResult> updateUserResult = new MutableLiveData<>();

    /**
     * 获取更新用户操作的结果。
     *
     * @return UserRepositoryResult 更新操作的结果。
     */
    public LiveData<UserRepositoryResult> getUpdateUserResult() {
        return updateUserResult;
    }

    // 更新用户
    public void updateUser(final User user) {
        EXECUTOR_SERVICE.execute(() -> {
            updateUserResult.postValue(userRepository.updateUser(user));
        });
    }

    /**
     * ViewModel工厂类,用于创建MainViewModel实例。
     */
    public static class Factory extends ViewModelProvider.NewInstanceFactory {
        // 用户数据仓库实例
        private final UserRepository userRepository;

        /**
         * 构造函数,初始化用户数据仓库。
         *
         * @param userRepository 用户数据仓库实例。
         */
        public Factory(UserRepository userRepository) {
            this.userRepository = userRepository;
        }

        /**
         * 创建MainViewModel实例。
         *
         * @param modelClass ViewModel的类类型。
         * @return MainViewModel 实例。
         */
        @NonNull
        @Override
        public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
            return (T) new MainViewModel(userRepository);
        }
    }

}
5.MainActivity
/**
 * 主活动类,负责管理应用程序的主要界面。
 */
public class MainActivity extends AppCompatActivity {

    private MainViewModel viewModel; // 视图模型,用于管理活动背后的业务逻辑
    private ActivityMainBinding binding; // 数据绑定实例,用于简化UI更新
    private UserRepository userRepository;

    /**
     * 在活动创建时调用。
     *
     * @param savedInstanceState 如果活动之前被销毁,这参数包含之前的状态。如果活动没被销毁之前,这参数是null。
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 启用边缘到边缘的UI
        EdgeToEdge.enable(this);
        // 设置数据绑定
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        // 设置视图的内边距,以适应系统栏位的高度
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        userRepository = new UserRepository(AppDatabase.getDatabase(MVVMApplication.getInstance()));
        // 初始化视图模型
        viewModel = new ViewModelProvider(this, new MainViewModel.Factory(userRepository)).get(MainViewModel.class);
        binding.setViewModel(viewModel);
        initListeners();
        initObserver();
    }

    /**
     * 初始化视图监听器。
     */
    private void initListeners() {
        // 清空输入框操作监听
        binding.btnClearEdit.setOnClickListener(v -> {
            clearEditText();
        });

        // 更新用户操作监听
        binding.btnUpdate.setOnClickListener(v -> {
            Log.i("swy","update按钮被点击");
            User userChecked = checkUserInfo();
            if (userChecked != null) {
                Log.i("swy","更新用户");
                viewModel.updateUser(userChecked);
            }
        });

    }

    private void initObserver() {

        // 观察更新结果
        viewModel.getUpdateUserResult().observe(this, result -> {
            Log.i("swy","updateresult发生变化");
            if (result.getType() == UserRepositoryResult.Type.SUCCESS) {
                showToast("更新成功");
            } else {
                showToast(result.getErrorMessage());
            }
        });

    }

    // 封装对用户信息输入的验证
    private User checkUserInfo() {
        String name = binding.etName.getText().toString();
        if (name.isEmpty()) {
            showToast("请输入姓名");
            return null;
        }
        String age = binding.etAge.getText().toString();
        if (age.isEmpty()) {
            showToast("请输入年龄");
            return null;
        }
        String sex = binding.etSex.getText().toString();
        if (sex.isEmpty()) {
            showToast("请输入性别");
            return null;
        }
        return new User(name, age, sex);
    }

    // 封装对姓名输入的检查
    private String checkName() {
        String name = binding.etName.getText().toString();
        if (name.isEmpty()) {
            showToast("请输入姓名");
            return "";
        }
        return name;
    }

    // 清除编辑文本框中的内容
    private void clearEditText() {
        binding.etName.setText("");
        binding.etAge.setText("");
        binding.etSex.setText("");
    }

    // 简化Toast消息的显示
    private void showToast(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }

}

至此,一套MVVM架构的Room数据库Update的语法即调用完成,感兴趣的同学可以多看一下这个流程和代码,观察一下是否有问题,多观察几分钟,分析一下流程,因为或许你正在跟我一样经历这个问题,或者未来可能被这类问题给绕进去。

好的,直接来说结果,上面的代码看似是完整的,且调用无误的。

但是里面有一个比较大的问题,这个问题在根儿上,也就是数据实体类,我们再次查看数据实体

@PrimaryKey(autoGenerate = true)
    private long id;
    private String name;
    private String age;
    private String sex;

前面也说了,这里面id为自增主键。

学过数据库的都知道,主键是用来区分两条数据的,比如说我这里有两条数据

id = 1,name = 张三,age = 18,sex = 男

id = 2,name = 张三,age = 18,sex = 男

假如有这么两条数据,id为主键的话,虽然名字年龄性别都是一样的张三,但是对于数据库而言,这是两条数据,对于用户表而言,这是两个同名的人

比如说,我给用户展现的是这样一个页面,支持常规的增删改查,但是用户只可以输入name、age、sex这三个数据

如果第一次用户输入了“name = 张三,age = 18,sex = 男”,点击增加按钮,显然是可以成功的

那么当用户再次输入“name = 张三,age = 18,sex = 男”,这里就有两种做法

第一种:允许输入

上面说了,user表以id为主键,所以从insert语句的执行来看,没有问题,就是完完全全的两条数据,这也是比较符合现实的,因为中国这么大,人这么多,肯定有很频繁的重名现象,所以一般我们来区分人并不是以姓名来区分,而是采用身份证号(ID),而后再因为身份证号码只有一个且比较重要不能随便示人,同时又要保证关联到人,我们现在一般用手机号来区分人,当然,手机号都有实名认证,即关联了身份证,且一个身份证可以办理多个手机号。当然,那个问题与本文无关,只是说到这里了。

第二种:不允许输入

这种设计方案一般在游戏里面比较常见,比如说,游戏刚开服,大家一窝蜂冲进去很重要一件事是干什么,抢注游戏昵称,因为如果某一个昵称比较好听或者寓意比较好,大家都想用,但是游戏的系统只允许一个人用,也就是不允许相同昵称出现这种策略。当然现在的大部分新游戏都是支持相同昵称的,所以在那些游戏的个人页面都支持一键赋值uid,这个uid就是游戏里面的身份证了,然后我们通过uid进行好友查找。

好了,上面描述了两种管理用户的策略,只是作为辅助思考

回归正题,讨论我前面展示的代码中的错误问题

通过上面的分析,我们知道了把id作为主键之后,其他属性并不足以区分用户这件关键信息了

所以来看这两部分的代码

1.从用户输入,组装更新User对象

// 封装对用户信息输入的验证
    private User checkUserInfo() {
        String name = binding.etName.getText().toString();
        if (name.isEmpty()) {
            showToast("请输入姓名");
            return null;
        }
        String age = binding.etAge.getText().toString();
        if (age.isEmpty()) {
            showToast("请输入年龄");
            return null;
        }
        String sex = binding.etSex.getText().toString();
        if (sex.isEmpty()) {
            showToast("请输入性别");
            return null;
        }
        return new User(name, age, sex);
    }

// 更新用户操作监听
        binding.btnUpdate.setOnClickListener(v -> {
            Log.i("swy","update按钮被点击");
            User userChecked = checkUserInfo();
            if (userChecked != null) {
                Log.i("swy","更新用户");
                viewModel.updateUser(userChecked);
            }
        });

2.传入user对象,执行Update操作

@Transaction
    public UserRepositoryResult updateUser(User user) {
        // 确认用户存在
        if (userDao.findUserByName(user.getName()) == null) {
            return new UserRepositoryResult(
                    UserRepositoryResult.Type.FAILURE,
                    RESULT_MESSAGE_USER_NOT_EXIST);
        }
        userDao.updateUser(user);
        return UserRepositoryResult.success();
    }

  @Update
    void updateUser(User user);

其实,说到这里,我相信大家已经看出来哪里出现问题了

我就不妨再说的细致一些吧,这是Room框架对于@Update注解的处理逻辑:

当您使用@Update注解进行用户数据更新时,Room库会根据传入的User对象的主键(即id)来定位到要更新的记录。具体处理逻辑如下:

a. 查找匹配的主键:Room会根据User对象的id值在数据库中查找具有相同id的记录。

b. 执行更新:如果找到了匹配的记录,则按照传入的User对象中的非空属性值更新对应的字段。这意味着如果您仅修改了name、age或sex中的某个属性值,并将这个更新后的User对象传递给@Update方法,只会更新这些已更改的属性,而不会影响其他未修改的属性或主键id。

c. 无匹配记录则不执行任何操作:如果数据库中找不到与传入User对象id相匹配的记录,Room将不做任何更新操作。 

 所以,问题就出现在了我们调用updateUser方法时,传入的user对象本身,即

new User(name, age, sex);

 可见,这里面是没有id的,那么没有id意味着什么,意味着id为null

对于Room而言,id为null时,它会怎么去理解我们传入的user对象呢——不做任何更新操作

所以,问题就找到了

也非常好解决,有两种解决方法,第一种就是给用户提供id的输入框(这种可以联想一下上面说过的游戏里面输入uid查找好友)

我们这里就简单一点吧

看一下我们的UserRepository中的具体调用

        // 确认用户存在
        if (userDao.findUserByName(user.getName()) == null) {
            return new UserRepositoryResult(
                    UserRepositoryResult.Type.FAILURE,
                    RESULT_MESSAGE_USER_NOT_EXIST);
        }

是的,你更新用户,首先得先确定用户存在对吧。

我是因为考虑一切从简,所以设计时是不允许相同name出现的,所以直接用name来查重了,当然也可以通过id来查找User

直接展示最终的处理办法

    @Transaction
    public UserRepositoryResult updateUser(User user) {
        // 确认用户存在
        User findUser = userDao.findUserByName(user.getName());
        if (findUser == null) {
            return new UserRepositoryResult(
                    UserRepositoryResult.Type.FAILURE,
                    RESULT_MESSAGE_USER_NOT_EXIST);
        }
        user.setId(findUser.getId());
        userDao.updateUser(user);
        return UserRepositoryResult.success();
    }

通过name找到数据库中的旧的User对象,因为id是主键,所以id不会更改,直接把旧的id塞到我们的新的user对象中,然后再传给updateUser方法,即可以实现Update方法了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1608126.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSS基础:table的4个标签的样式详解(6000字长文!附案例)

你好&#xff0c;我是云桃桃。 一个希望帮助更多朋友快速入门 WEB 前端的程序媛。 云桃桃-大专生&#xff0c;一枚程序媛&#xff0c;感谢关注。回复 “前端基础题”&#xff0c;可免费获得前端基础 100 题汇总&#xff0c;回复 “前端工具”&#xff0c;可获取 Web 开发工具合…

llama_factory微调QWen1.5

GitHub - hiyouga/LLaMA-Factory: Unify Efficient Fine-Tuning of 100 LLMsUnify Efficient Fine-Tuning of 100 LLMs. Contribute to hiyouga/LLaMA-Factory development by creating an account on GitHub.https://github.com/hiyouga/LLaMA-FactoryQwen1.5 介绍 | QwenGITH…

EEG基础

01 简介 脑电图(EEG)因其低成本、无创、便携以及毫秒级的高时间分辨率等特点&#xff0c;成为了研究大脑功能、异常和神经生理动力学的可靠且广泛使用的测量工具。 在神经信号处理领域&#xff0c;EEG通常作为一种非侵入性的脑成像技术用于诊断脑部疾病&#xff0c;而正常EE…

50.基于SpringBoot + Vue实现的前后端分离-酒店管理系统(项目 + 论文PPT)

项目介绍 本站是一个B/S模式系统&#xff0c;采用SpringBoot Vue框架&#xff0c;MYSQL数据库设计开发&#xff0c;充分保证系统的稳定性。系统具有界面清晰、操作简单&#xff0c;功能齐全的特点&#xff0c;使得基于SpringBoot Vue技术的酒店管理系统设计与实现管理工作系统…

VMware最新下载安装

1、打开浏览器 搜索VMware官网&#xff0c;点进去。如图&#xff1a; 这里有两种下载方法&#xff0c;便洁就是我这种&#xff0c;还有一种就是注册账号之后下载就完全没有必要了&#xff0c;而且基本注册不了&#xff0c;不太好注册。 2、选择"产品"第二个选项 …

深度剖析扫雷游戏的各个知识点(2)

小伙伴们&#xff0c;大家好。这次继续上次的剖析扫雷游戏的知识点。 那么本次咱们主要是讲扫雷中的宏定义&#xff0c;也就是#define这些 首先#define是用来定义一个宏&#xff0c;后面就是类似于和变量一样的常量名&#xff0c;以及最后的数字就是它的值。 定义规则 #def…

被Claude3的图生代码技术秀到了,前端开发效率,提升到秒级

被Claude3的图生代码技术秀到了&#xff01;前端开发效率&#xff0c;提升到秒级 上传一张网站图片&#xff0c;用Claude3 生成实现这个网站的代码的教程来啦&#xff01; 在Claude3 的中文网站上一分钟就能实现&#xff0c;生成前端代码。中文网站地址是https://askmanyai.c…

【银角大王———Django学习DAY0——基础准备】

银角大王——Django学习前情提要 &#xff08;1&#xff09;在pycharm中下载Flask&#xff08;2&#xff09;使用Flask&#xff08;3&#xff09;下载BootStrap框架&#xff08;4&#xff09; 使用BootStrap框架 &#xff08;1&#xff09;在pycharm中下载Flask 在设置——项目…

【cmu15445c++入门】(14)C++的 =delete和=default

一、定义 &#xff08;1&#xff09;default 在C中&#xff0c; default 是一种特殊的语法&#xff0c;用于显式地请求编译器生成一个函数的默认实现。当应用于构造函数时&#xff0c; default 告诉编译器生成一个默认构造函数。默认构造函数是一个不接受任何参数的构造函数。…

Linux给磁盘扩容(LVM方式)

Linux给磁盘扩容&#xff08;LVM方式&#xff09; 最近测试性能&#xff0c;在本地打数据时&#xff0c;发现磁盘空间不足&#xff0c;于是想手动给/挂载点添加空间。这里介绍通过LVM方式快速给磁盘扩容。 LVM:是一种技术&#xff0c;方便管理磁盘。如果不用LVM&#xff0c;那…

经典网络解读—IResNet

论文&#xff1a;Improved Residual Networks for Image and Video Recognition&#xff08;2020.4&#xff09; 作者&#xff1a;Ionut Cosmin Duta, Li Liu, Fan Zhu, Ling Shao 链接&#xff1a;https://arxiv.org/abs/2004.04989 代码&#xff1a;https://github.com/iduta…

IK分词器安装、配置、分词自定义、Rest使用、SpringBoot使用

文章目录 1. 概述2. 安装配置3. 自定义拆分文本4. 调用4.1 拆分规则4.2 Rest 调用4.3 SpringBoot 调用 1. 概述 IK分词器是ElasticSearch(es)的一个最最最有名插件&#xff0c;能够把一段中文或者别的语句划分成一个个的关键字&#xff0c;进而在搜索的时候对数据库中或者索引库…

并发场景下 缓存击穿 穿透 雪崩如何解决

最近建了一个技术交流群&#xff0c;欢迎志同道合的同学加入&#xff0c;群里主要讨论&#xff1a;分享业务解决方案、深度分析面试题并解答工作中遇到的问题&#xff0c;同时也能为我提供写作的素材。 群号 208236931&#xff0c;欢迎进群交流学习&#xff0c;一起进步、进步、…

3.4 海思SS928开发 - 烧写工具 - BurnTool Emmc 烧写

3.4 烧写工具 - BurnTool Emmc 烧写 BurnTool 工具提供了多种烧写方式&#xff0c;这里只介绍最常用的 烧写emmc方式。 环境准备 PC 与单板之间连接好调试串口以及网线。 将厂商提供的出厂镜像拷贝至 PC 硬盘上&#xff0c;解压后得到的文件如下&#xff1a; . ├── boot_…

ARM学习(26)链接库的依赖查看

笔者今天来聊一下查看链接库的依赖。 通常情况下&#xff0c;运行一个可执行文件的时候&#xff0c;可能会出现找不到依赖库的情况&#xff0c;比如图下这种情况&#xff0c;可以看到是缺少了license.dll或者libtest.so&#xff0c;所以无法运行。怎么知道它到底缺少什么dll呢&…

收藏这份方案,制造业营销管理快人一步【内附下载链接】

随着“中国制造2025”等政策的实施&#xff0c;制造业正经历着技术革新和产业升级&#xff0c;尤其在智能化和绿色制造领域取得了显著进展。 然而&#xff0c; 制造业面临着消费者需求日益多样化和个性化的挑战&#xff0c;迫切需要从生产导向转变为市场导向。与此同时&#…

Transformer中的位置编码详解

什么是位置编码 位置编码概述 位置编码的目的是为了补充序列的位置信息&#xff0c;这是因为自注意力机制本身不包含位置的概念&#xff08;例如顺序信息&#xff09;。位置编码的具体作用是&#xff0c;对于不同的输入序列成分&#xff0c;赋予其不同的位置标识&#xff0c;确…

RIP小实验配置及缺省路由下发

配置如下&#xff1a; IP配置&#xff1a; IP配置完先查看RIP协议学习到的路由表&#xff0c;没有内容则代表没有开启RIP 启用RIP&#xff1a;这里的rip后跟的ID只具有本地意义&#xff0c;可以在1-65535之间随便取&#xff0c;不同路由器之间都可以取用不同的&#xff0c;为了…

宿舍预付费管控云平台

1.宿舍预付费管控云平台概述 宿舍预付费管控云平台是一种创新的智能管理系统&#xff0c;专为学校、公寓等住宿环境设计&#xff0c;旨在提升管理效率&#xff0c;优化用户体验&#xff0c;并实现资源的高效利用。通过云端技术&#xff0c;该平台可以实现远程充值、实时消费记…

Day 30 回溯总结

重新安排行程(*) 给定一个机票的字符串二维数组 [from, to]&#xff0c;子数组中的两个成员分别表示飞机出发和降落的机场地点&#xff0c;对该行程进行重新规划排序。所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&#xff09;出发的先生&#xff0c;所以该行程必…