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. 相交链表 - 力扣&…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...
VTK如何让部分单位不可见
最近遇到一个需求,需要让一个vtkDataSet中的部分单元不可见,查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行,是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示,主要是最后一个参数,透明度…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序
一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
CVE-2020-17519源码分析与漏洞复现(Flink 任意文件读取)
漏洞概览 漏洞名称:Apache Flink REST API 任意文件读取漏洞CVE编号:CVE-2020-17519CVSS评分:7.5影响版本:Apache Flink 1.11.0、1.11.1、1.11.2修复版本:≥ 1.11.3 或 ≥ 1.12.0漏洞类型:路径遍历&#x…...
