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

2024-04-17 11:04

本文主要是介绍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语句不生效的问题解决记录的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/911618

相关文章

IDEA和GIT关于文件中LF和CRLF问题及解决

《IDEA和GIT关于文件中LF和CRLF问题及解决》文章总结:因IDEA默认使用CRLF换行符导致Shell脚本在Linux运行报错,需在编辑器和Git中统一为LF,通过调整Git的core.aut... 目录问题描述问题思考解决过程总结问题描述项目软件安装shell脚本上git仓库管理,但拉取后,上l

解决docker目录内存不足扩容处理方案

《解决docker目录内存不足扩容处理方案》文章介绍了Docker存储目录迁移方法:因系统盘空间不足,需将Docker数据迁移到更大磁盘(如/home/docker),通过修改daemon.json配... 目录1、查看服务器所有磁盘的使用情况2、查看docker镜像和容器存储目录的空间大小3、停止dock

idea npm install很慢问题及解决(nodejs)

《ideanpminstall很慢问题及解决(nodejs)》npm安装速度慢可通过配置国内镜像源(如淘宝)、清理缓存及切换工具解决,建议设置全局镜像(npmconfigsetregistryht... 目录idea npm install很慢(nodejs)配置国内镜像源清理缓存总结idea npm in

pycharm跑python项目易出错的问题总结

《pycharm跑python项目易出错的问题总结》:本文主要介绍pycharm跑python项目易出错问题的相关资料,当你在PyCharm中运行Python程序时遇到报错,可以按照以下步骤进行排... 1. 一定不要在pycharm终端里面创建环境安装别人的项目子模块等,有可能出现的问题就是你不报错都安装

idea突然报错Malformed \uxxxx encoding问题及解决

《idea突然报错Malformeduxxxxencoding问题及解决》Maven项目在切换Git分支时报错,提示project元素为描述符根元素,解决方法:删除Maven仓库中的resolv... 目www.chinasem.cn录问题解决方式总结问题idea 上的 maven China编程项目突然报错,是

在Ubuntu上打不开GitHub的完整解决方法

《在Ubuntu上打不开GitHub的完整解决方法》当你满心欢喜打开Ubuntu准备推送代码时,突然发现终端里的gitpush卡成狗,浏览器里的GitHub页面直接变成Whoathere!警告页面... 目录一、那些年我们遇到的"红色惊叹号"二、三大症状快速诊断症状1:浏览器直接无法访问症状2:终端操作异常

mybatis直接执行完整sql及踩坑解决

《mybatis直接执行完整sql及踩坑解决》MyBatis可通过select标签执行动态SQL,DQL用ListLinkedHashMap接收结果,DML用int处理,注意防御SQL注入,优先使用#... 目录myBATiFBNZQs直接执行完整sql及踩坑select语句采用count、insert、u

Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题

《Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题》在爬虫工程里,“HTTPS”是绕不开的话题,HTTPS为传输加密提供保护,同时也给爬虫带来证书校验、... 目录一、核心问题与优先级检查(先问三件事)二、基础示例:requests 与证书处理三、高并发选型:

前端导出Excel文件出现乱码或文件损坏问题的解决办法

《前端导出Excel文件出现乱码或文件损坏问题的解决办法》在现代网页应用程序中,前端有时需要与后端进行数据交互,包括下载文件,:本文主要介绍前端导出Excel文件出现乱码或文件损坏问题的解决办法,... 目录1. 检查后端返回的数据格式2. 前端正确处理二进制数据方案 1:直接下载(推荐)方案 2:手动构造

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT