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 {

    @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.mfbz.cn/a/550783.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux 磁盘管理和文件系统

硬盘的物理结构&#xff1a; 盘片硬盘有多个盘片&#xff0c;每盘片2面磁头每面一个磁头 硬盘的数据结构&#xff1a; 扇区盘片被分为多个扇形区域&#xff0c;扇区:每个扇区存放512字节的数据&#xff0c;硬盘的最小存储单位磁道同一盘片不同半径的同心圆&#xff0c;是由磁…

postgresql|数据库|实时数据库监控利器 pg_activity 的部署和初步使用

前言&#xff1a; postgresql的调优是比较重要的&#xff0c;那么&#xff0c;如何调优呢&#xff1f;自然是在某一个时间段内&#xff0c;通常是业务高峰期或者压测时间内实时观察数据库的运行情况&#xff0c;然后通过观察到的信息判断数据库的瓶颈&#xff0c;比如&#xf…

Windows 安装 A UDP/TCP Assistant 网络调试助手

Windows 安装 A UDP/TCP Assistant 网络调试助手 0. 引言1. 下载地址2. 安装和使用 0. 引言 需要调试一个实时在线聊天程序&#xff0c;安装一个UDP/TCP Assistant 网络调试助手&#xff0c;方便调试。 1. 下载地址 https://github.com/busyluo/NetAssistant/releases 2. 安…

【Android AMS】startActivity流程分析

文章目录 AMSActivityStackstartActivity流程startActivityMayWaitstartActivityUncheckedLocked startActivityLocked(ActivityRecord r, boolean newTask, boolean doResume, boolean keepCurTransition)resumeTopActivityLocked 参考 AMS是个用于管理Activity和其它组件运行…

华为云CodeArts IDE For Python 快速使用指南

CodeArts IDE 带有 Python 扩展&#xff0c;为 Python 语言提供了广泛的支持。Python 扩展可以利用 CodeArts IDE 的代码补全、验证、调试和单元测试等特性&#xff0c;与多种 Python 解释器协同工作&#xff0c;轻松切换包括虚拟环境和 conda 环境的 Python 环境。本文简要概述…

chrome浏览器取消右上方的更新红点提示

在桌面找到chrome浏览器的快捷方式&#xff0c;右键打开属性 在目标 引号后添加 --disable-background-netwroking

git上传代码

git上传代码 先写好本地代码&#xff0c;按照下面步骤操作

初识--Linux的虚拟地址空间

重新了解地址空间 在学习c/c语言的时候,大家一定见过以下这张图 说的是程序会加载在如图的结构上,实际上,我们真的对他很了解吗,而在Linux进程控制这,就会有一个奇怪的现象 前提提要:简要介绍一下fork函数 进程内核数据结构(PCB)自己的代码以及数据 在Linux中,fork可以从当…

Docker Desktop 卡死在 “Starting the Docker Engine“问题解决

docker desktop启动卡死在这个界面长时间没有反应 wsl --status输入以上命令查看wsl状态&#xff0c;发现也是卡死的状态&#xff0c;长时间没有反应&#xff0c;猜测是因为WSL卡死导致的docker desktop卡死的 netsh winsock reset通过以上命令重置 重启电脑后问题解决

【南京艺术学院×朗汀留学】部分录取案例合集

留学申请正在紧张的进行中&#xff0c;作为深耕留学的专业资深团队&#xff0c;朗汀留学成功帮助上千名学生出国留学。 在此我们将部分留学案例作以总结&#xff0c;以供新生参考。再次恭喜所有获得理想大学offer的学生们&#xff0c;你们的努力让梦想照进现实。 学校介绍 南京…

用Scrapy抓取当当网站数据

setting.py实验目的及要求&#xff1a; 【实验目的】 通过本实验了解Scrapy爬虫框架&#xff1b;熟练掌握Scrapy的基本使用方法和常用技巧。 【实验要求】 使用Scrapy框架&#xff0c;抓取网站商品信息&#xff08;京东、淘宝、当当等任选一个&#xff09;&#xff0c;并将结…

实战 K8s ConfigMap:打造动态可配置的云原生应用

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《Kubernetes航线图&#xff1a;从船长到K8s掌舵者》 &#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、前言 1、k8s简介 2、ConfigMap简介 二、Con…

社交创新的标杆:解读Facebook的社交模式

引言 在当今数字化时代&#xff0c;社交媒体已成为人们日常生活和沟通的重要工具。作为全球最大的社交媒体平台&#xff0c;Facebook不仅改变了我们的社交模式&#xff0c;而且对全球的社交文化、商业活动和公共事务产生了深远的影响。本文将深入探讨Facebook的社交模式&#…

面试:lock 和 synchronized

一、语法层面 synchronized 是关键字&#xff0c;源码在jvm中&#xff0c;用c语言实现Lock 是接口&#xff0c;源码由jdk提供&#xff0c;用java语言实现使用synchronized时&#xff0c;退出同步代码块锁会自动释放&#xff0c;而使用Lock时&#xff0c;需要手动调用unlock方法…

数学建模完整版

模型与适用题型 微分方程传染病预测模型 神经网络 层次分析法 粒子群算法 matlab 优劣解距离法

停车资产数字化运营管理方案内容包括哪些?

随着新兴信息技术的蓬勃发展&#xff0c;如大数据、云服务、机器学习以及数字孪生等&#xff0c;停车行业正经历着前所未有的变革。这些技术的应用不仅推动了智慧停车领域的迅猛扩张&#xff0c;而且已成为全球各地数字化城市构建和城市治理现代化的关键驱动力。在数字化、平台…

【可实战】测试体系与测试方案设计(业务按公司实际情况,技术可参考通用测试方案)

一、如果我们要测试一个系统&#xff0c;首先我们要了解被测系统的架构 &#xff08;一&#xff09;业务架构-从需求里面去了解&#xff08;角色和行为&#xff09;&#xff1a; 业务模型分析&#xff08;是一个电商&#xff0c;还是一个企业的crm&#xff0c;还是一个网站&a…

java的volatile

在Java中&#xff0c;线程之间对内存写入操作的可见性是一个重要的问题&#xff0c;因为每个线程都有自己的工作内存&#xff0c;并且线程之间共享主内存。当一个线程修改了共享变量的值&#xff0c;其他线程并不一定能立即看到这个修改&#xff0c;这就是所谓的可见性问题。 例…

STM32H7 QSPI的寄存器和功能介绍

目录 概述 1 认识QSPI 1.1 QSPI介绍 1.2 QUADSPI 主要特性 2 QUADSPI 功能说明 2.1 双闪存模式禁止 2.2 双闪存模式使能 3 QUADSPI 的用法 3.1 间接模式的操作步骤 3.1.1 QUADSPI 间接模式时配置帧模式 3.1.2 写控制寄存器 (QUADSPI_CR) 3.1.3 写通信配置寄存器 (…

6-pytorch - 网络的保存和提取

前言 我们训练好的网络&#xff0c;怎么保存和提取呢&#xff1f; 总不可以一直不关闭电脑吧&#xff0c;训练到一半&#xff0c;想结束到明天再来训练&#xff0c;这就需要进行网络的保存和提取了。 本文以前面博客3-pytorch搭建一个简单的前馈全连接层网络&#xff08;回归问…
最新文章