Android Room 记录一个Update语句不生效的问题解决记录
代码展示
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 {@Updatevoid updateUser(User user);}
说明:只展示更新语句
3.用户仓库类
/*** 用户仓库类,负责用户数据的增删改查操作。*/
public class UserRepository {private final UserDao userDao;/*** 构造函数,初始化用户数据访问对象。** @param database 应用数据库实例。*/public UserRepository(AppDatabase database) {this.userDao = database.getUserDao();}/*** 更新用户信息。如果用户不存在,则操作失败。** @param user 要更新的用户对象。* @return 返回操作结果,成功或失败。*/@Transactionpublic 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@Overridepublic <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。*/@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 启用边缘到边缘的UIEdgeToEdge.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操作
@Transactionpublic 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();}@Updatevoid 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
直接展示最终的处理办法
@Transactionpublic 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方法了
相关文章:

Android Room 记录一个Update语句不生效的问题解决记录
代码展示 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 …...

使用SpringBoot3+Vue3开发公寓管理系统
项目介绍 公寓管理系统可以帮助公寓管理员更方便的进行管理房屋。功能包括系统管理、房间管理、租户管理、收租管理、房间家具管理、家具管理、维修管理、维修师傅管理、退房管理。 功能介绍 系统管理 用户管理 对系统管理员进行管理,新增管理员,修改…...
有且仅有的10个常见的排序算法,东西不多,怎么就背不下来呢
就这么跟你说吧,面试中肯定会出排序算法的算法题,你只需要背下来代码背下来他们的时间复杂度和空间复杂度就能蒙混过关。 不管你是前端还是后端,关于排序的算法有且仅有这 10个,如果你用心了,怎么会记不住呢。看完这篇…...

Mac安装配置ElasticSearch和Kibana 8.13.2
系统环境:Mac M1 (MacOS Sonoma 14.3.1) 一、准备 从Elasticsearch:官方分布式搜索和分析引擎 | Elastic上下载ElasticSearch和Kibana 笔者下载的是 elasticsearch-8.13.2-darwin-aarch64.tar.gz kibana-8.13.2-darwin-aarch64.tar.gz 并放置到个人…...

javaWeb项目-快捷酒店管理系统功能介绍
项目关键技术 开发工具:IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架:ssm、Springboot 前端:Vue、ElementUI 关键技术:springboot、SSM、vue、MYSQL、MAVEN 数据库工具:Navicat、SQLyog 1、Spring Boot框架 …...

闲不住,手写一个数据库文档生成工具
shigen坚持更新文章的博客写手,擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长,分享认知,留住感动。 个人IP:shigen 逛博客的时候,发现了一个很有意思的文章:数据库表结构导…...

在群晖上安装GPT4Free
什么是 GPT4Free ? GPT4Free 简称 G4F,是一个强大的大型语言模型命令行界面(LLM-CLI),旨在去中心化并提供免费访问先进人工智能技术的能力。G4F 的目标是通过提供用户友好和高效的工具,使人工智能民主化&am…...
C# 语言类型(四)—传递参数及其修饰符
总目录 C# 语法总目录 参考链接: C#语法系列:C# 语言类型(一)—预定义类型值之数值类型 C#语法系列:C# 语言类型(二)—预定义类型之字符串及字符类型简述 C#语法系列:C# 语言类型(三)—数组/枚举类型/结构体 C#语法系列:C# 语言类型(四)—传递参数及其修饰符 C#语法…...

刷穿力扣006-剑指offer一数组——02寻找目标值-二维数组
刷穿力扣006-剑指offer<一>数组——02寻找目标值-二维数组 基本面试题都是我带大家刷的力扣热题100和剑指offer的75道题,建议刷两遍!(ps:想找工作实习的同学,文末有面试八股和简历模板) 题目: 语言…...
爬虫(小案例)
点开其中一个链接, http://desk.zol.com.cn/dongman/huoyingrenzhe/(前面为浏览器自动补全,在代码里需要自己补全) 可以看到图片的下载地址以及打开本图集下一张图片的链接 了解完网站的图片构造后动手写代码,我们筛…...

环信 IM 客户端将适配鸿蒙 HarmonyOS
自华为推出了自主研发操作系统鸿蒙 HarmonyOS 后,国内许多应用软件开始陆续全面兼容和接入鸿蒙操作系统。环信 IM 客户端计划将全面适配统鸿蒙 HarmonyOS ,助力开发者快速实现社交娱乐、语聊房、在线教育、智能硬件、社交电商、在线金融、线上医疗等广泛…...
伪元素的使用
.box::after{content: ;display: block;// 定义元素位置margin-top: 12rpx;margin-right: 20rpx;// 定义元素宽高width: 36rpx;height: 36rpx;// background-image无法引用本地资源,故需要用网络地址background-image: url($urlcalendar.png);background-size: 100%…...
TensorFlow学习之:高级应用和扩展
生成对抗网络:了解GAN的基本原理,使用TensorFlow实现简单的GAN 生成对抗网络(Generative Adversarial Networks,GAN)由两部分组成:生成器(Generator)和判别器(Discrimin…...
maya模板导入动画
maya模板导入动画,第一帧为模板姿态 要将一个FBX文件中的动画数据导入另一个FBX文件的模板,并使得第一帧是模板的初始姿势,第二帧开始是动画,你可以在Maya中采用以下步骤来操作: 步骤 1: 导入模板FBX 首先ÿ…...

【微信小程序之分包】
微信小程序之分包 什么是分包分包的好处分包前的结构图分包后的结构图分包的加载规则分包的体积限制使用分包打包原则引用原则独立分包独立分包的配置方法独立分包的引用原则分包预下载配置分包的预下载分包预下载限制 什么是分包 分包指的是把一个完整小程序项目,…...

STM32-ADC(独立模式、双重模式)
ADC简介 18个通道:外部信号源就是16个GPIO回。在引脚上直接接模拟信号就行了,不需要侄何额外的电路。引脚就直接能测电压。2个内部信号源是内部温度传感器和内部参考电压。 逐次逼近型ADC: 它是一个独立的8位逐次逼近型ADC芯片,这个ADC0809是…...

03.卸载MySQL
卸载MySQL 1.Windows卸载MySQL8 停止服务 用命令停止或者在服务中停止都可以 net stop mysql(服务名字可以去服务里面看一下)控制面板卸载MySQL 卸载MySQL8.0的程序可以和其他桌面应用程序一样直接在控制面板选择卸载程序,并在程序列表中…...
2024.4.13 蓝桥杯软件类C++B组山东省赛 小记
大三老狗了 , 还是把精力放在考研上了 ,所以只是蓝桥杯的前一晚上把常用算法翻了翻。 其实还做了一场小模拟,两个题分值200分我狂砍了17分,bfs写半小时写不明白,所以晚上已经是心如死灰了,所以就早早睡觉了…...

Windows下IntelliJ IDEA远程连接服务器中Hadoop运行WordCount(详细版)
使用IDEA直接运行Hadoop项目,有两种方式,分别是本地式:本地安装HadoopIDEA;远程式:远程部署Hadoop,本地安装IDEA并连接, 本文介绍第二种。 一、安装配置Hadoop (1)虚拟机伪分布式 见上才艺&a…...

【每日刷题】Day16
【每日刷题】Day16 🥕个人主页:开敲🍉 🔥所属专栏:每日刷题🍍 🌼文章目录🌼 1. 24. 两两交换链表中的节点 - 力扣(LeetCode) 2. 160. 相交链表 - 力扣&…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

九天毕昇深度学习平台 | 如何安装库?
pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子: 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...
多模态图像修复系统:基于深度学习的图片修复实现
多模态图像修复系统:基于深度学习的图片修复实现 1. 系统概述 本系统使用多模态大模型(Stable Diffusion Inpainting)实现图像修复功能,结合文本描述和图片输入,对指定区域进行内容修复。系统包含完整的数据处理、模型训练、推理部署流程。 import torch import numpy …...

Unity中的transform.up
2025年6月8日,周日下午 在Unity中,transform.up是Transform组件的一个属性,表示游戏对象在世界空间中的“上”方向(Y轴正方向),且会随对象旋转动态变化。以下是关键点解析: 基本定义 transfor…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...