解决spring-data-jpa 事物中修改属性自动更新update问题

问题还原

项目orm层用的是spring-data-jpa,服务端接口实现的是树节点间的拖拽功能,测试环境联调发现异常问题,其中拖拽到目标目录后节点名称总会重名,重名规则是originName转化为originName(n)

    @Transactional(rollbackFor = Exception.class)
    public void move(MoveWorkSpaceDto moveWorkSpaceDto) {
        /***
         * 个人空间节点移动
         * 1、源节点移动到空目录,那么目标父节点必传,前置和后置节点都为空
         * 2、源节点移动到非空目录:
         *   2.1、移动到目标目录的第一个节点,那么后置节点必传,前置节点为空
         *   2.2、移动到目标目录的最后个节点,那么前置节点毕传,后置节点为空
         *   2.3、移动到目标目录的中间节点,那么前置节点和后置节点都要传入
         * 3、节点拖动到目录中,此时不知道目录是否非空,前置后置节点都不传,服务器追加到目录的最后个节点
         */

        log.info(">>>MoveParams: [{}]", JSONObject.toJSONString(moveWorkSpaceDto));
        WorkSpaceEntity sourceEntity = workSpaceRepository.findById(moveWorkSpaceDto.getSourceWorkId()).orElse(null);
        if (sourceEntity == null) {
            throw new PublicsException("源工作空间节点不存在!");
        }
        WorkSpaceEntity parentEntity = workSpaceRepository.findById(moveWorkSpaceDto.getDestParentId()).orElse(null);
        if (parentEntity == null) {
            throw new PublicsException("目标父工作空间节点不存在!");
        }
        log.info(">>>>Noooooooo<<<<<");
        sourceEntity.setParentId(parentEntity.getId());
        log.info(">>>>UpdateNow<<<<<");
        Long cnt = workSpaceRepository.countAllByParentIdAndSpaceName(parentEntity.getId(), sourceEntity.getSpaceName());
        log.info("destParentId: [{}], spaceName: [{}], destCount: [{}]", parentEntity.getId(), sourceEntity.getSpaceName(), cnt);
        if (cnt > 0L) {
            String newName = RenameUtil.rename(sourceEntity.getSpaceName(), sourceEntity.getRunType(), cnt);
            sourceEntity.setSpaceName(newName);
        }
        if (moveWorkSpaceDto.getFrontWorkId() == null && moveWorkSpaceDto.getPostWorkId() == null) {
            List<WorkSpaceEntity> children = workSpaceRepository.findByParentIdOrderBySortIndexDesc(
                    moveWorkSpaceDto.getDestParentId());
            if (CollectionUtils.isEmpty(children)) {
                sourceEntity.setSortIndex(sourceEntity.getId() * SORT_INDEX_STEP);
            } else {
                sourceEntity.setSortIndex(children.get(0).getSortIndex() + SORT_INDEX_STEP);
            }
        } else if (moveWorkSpaceDto.getFrontWorkId() != null && moveWorkSpaceDto.getPostWorkId() == null) {
            WorkSpaceEntity frontEntity = getFrontEntity(moveWorkSpaceDto);
            sourceEntity.setSortIndex(frontEntity.getSortIndex() + SORT_INDEX_STEP);
        } else if (moveWorkSpaceDto.getFrontWorkId() == null && moveWorkSpaceDto.getPostWorkId() != null) {
            WorkSpaceEntity postEntity = getPostEntity(moveWorkSpaceDto);
            sourceEntity.setSortIndex(postEntity.getSortIndex() - SORT_INDEX_STEP);
        } else {
            WorkSpaceEntity frontEntity = getFrontEntity(moveWorkSpaceDto);
            WorkSpaceEntity postEntity = getPostEntity(moveWorkSpaceDto);
            sourceEntity.setSortIndex((frontEntity.getSortIndex() + postEntity.getSortIndex()) / 2);
        }
        workSpaceRepository.save(sourceEntity);
    }

排查后发现代码逻辑正常,于是加日志定位,果然在更新项目前多了update操作,具体位置如下:

说明entity实体更新属性后,jpa自动执行了update属性,导致count判重始终大于0。

问题原因

jpa在hibernate上更进一步,我把单表的查询逻辑定义在Repository层的方法上,不用谢SQL,简单明了,不曾想,忽略的hibernate的entity在session的3种状态。

  • 临时态,刚创建new的对象,还没有持久化,session缓存中也没有。
  • 游离态,已经持久化,但不在session缓存中。
  • 持久态,已经持久化,也在session缓存中。

问题原因明确,sourceEntity在持久态,修改属性自然会更新到数据库,判重查询已经更新了,查的还是自己,所以总是误认为有重复节点。

解决办法--隔离entity

避开session中缓存的sourceEntity的修改,创建新entity,修改临时态的entity,设置好属性后再调用save更新数据

    @Transactional(rollbackFor = Exception.class)
    public void move(MoveWorkSpaceDto moveWorkSpaceDto) {
        /***
         * 个人空间节点移动
         * 1、源节点移动到空目录,那么目标父节点必传,前置和后置节点都为空
         * 2、源节点移动到非空目录:
         *   2.1、移动到目标目录的第一个节点,那么后置节点必传,前置节点为空
         *   2.2、移动到目标目录的最后个节点,那么前置节点毕传,后置节点为空
         *   2.3、移动到目标目录的中间节点,那么前置节点和后置节点都要传入
         * 3、节点拖动到目录中,此时不知道目录是否非空,前置后置节点都不传,服务器追加到目录的最后个节点
         */

        log.info(">>>MoveParams: [{}]", JSONObject.toJSONString(moveWorkSpaceDto));
        WorkSpaceEntity sourceEntity = workSpaceRepository.findById(moveWorkSpaceDto.getSourceWorkId()).orElse(null);
        if (sourceEntity == null) {
            throw new PublicsException("源工作空间节点不存在!");
        }
        WorkSpaceEntity parentEntity = workSpaceRepository.findById(moveWorkSpaceDto.getDestParentId()).orElse(null);
        if (parentEntity == null) {
            throw new PublicsException("目标父工作空间节点不存在!");
        }
        WorkSpaceEntity updateEntity = new WorkSpaceEntity();
        BeanUtils.copyProperties(sourceEntity, updateEntity);

        Long cnt = workSpaceRepository.countByParentIdAndSpaceName(parentEntity.getId(), updateEntity.getSpaceName());
        log.info("destParentId: [{}], spaceName: [{}], destCount: [{}]", parentEntity.getId(), updateEntity.getSpaceName(), cnt);
        if (cnt > 0L) {
            String newName = RenameUtil.rename(updateEntity.getSpaceName(), updateEntity.getRunType(), cnt);
            updateEntity.setSpaceName(newName);
        }
        if (moveWorkSpaceDto.getFrontWorkId() == null && moveWorkSpaceDto.getPostWorkId() == null) {
            List<WorkSpaceEntity> children = workSpaceRepository.findByParentIdOrderBySortIndexDesc(
                    moveWorkSpaceDto.getDestParentId());
            if (CollectionUtils.isEmpty(children)) {
                updateEntity.setSortIndex(updateEntity.getId() * SORT_INDEX_STEP);
            } else {
                updateEntity.setSortIndex(children.get(0).getSortIndex() + SORT_INDEX_STEP);
            }
        } else if (moveWorkSpaceDto.getFrontWorkId() != null && moveWorkSpaceDto.getPostWorkId() == null) {
            WorkSpaceEntity frontEntity = getFrontEntity(moveWorkSpaceDto);
            updateEntity.setSortIndex(frontEntity.getSortIndex() + SORT_INDEX_STEP);
        } else if (moveWorkSpaceDto.getFrontWorkId() == null && moveWorkSpaceDto.getPostWorkId() != null) {
            WorkSpaceEntity postEntity = getPostEntity(moveWorkSpaceDto);
            updateEntity.setSortIndex(postEntity.getSortIndex() - SORT_INDEX_STEP);
        } else {
            WorkSpaceEntity frontEntity = getFrontEntity(moveWorkSpaceDto);
            WorkSpaceEntity postEntity = getPostEntity(moveWorkSpaceDto);
            updateEntity.setSortIndex((frontEntity.getSortIndex() + postEntity.getSortIndex()) / 2);
        }
        updateEntity.setParentId(parentEntity.getId());
        workSpaceRepository.save(updateEntity);
    }

场景复现

1、无事物的service中修改

    public void test() {
        CronTaskEntity cronTaskEntity = cronTaskRepository.findById(18L).orElse(null);
        // 更新记录
        cronTaskEntity.setUsername("鲁班七号");
        CronTaskEntity newEntity = cronTaskRepository.findById(18L).orElse(null);
        Assert.isTrue(cronTaskEntity.getUsername().equals(newEntity.getUsername()), "查询‘鲁班七号'没有更新啊");
    }

运行后:

Caused by: java.lang.IllegalArgumentException: 查询‘鲁班七号'没有更新啊

2、有事物中的service修改

    @Transactional
    public void test() {
        CronTaskEntity cronTaskEntity = cronTaskRepository.findById(18L).orElse(null);
        // 更新记录
        cronTaskEntity.setUsername("鲁班七号");
        CronTaskEntity newEntity = cronTaskRepository.findById(18L).orElse(null);
        Assert.isTrue(cronTaskEntity.getUsername().equals(newEntity.getUsername()), "查询‘鲁班七号'没有更新啊");
    }

运行后:

Hibernate: update `cron_task` set `createtime`=?, `updatetime`=?, `is_deleted`=?, `cron_expression`=?, `remark`=?, `staff_id`=?, `tag_id`=?, `username`=? where `id`=?

通过

由此可见,事物中会保存entity的缓存,修改entity属性引起jpa自动update,因此避免误操作,建议创建临时态的entity修改属性。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 解决spring data jpa 批量保存更新的问题

    spring data jpa 批量保存更新问题 使用jpa批量保存时,看日志发现是一条一条打印的,然后去看了下源码,果然是循环调用的单个保存(巨坑啊) 经查询jpa是可以实现批量保存更新的,具体设置如下: spring.jpa.properties.hibernate.jdbc.batch_size=500 spring.jpa.properties.hibernate.jdbc.batch_versioned_data=true spring.jpa.properties.hibernate

  • 解决springjpa的局部更新字段问题

    问题描述: 使用springjpa更新数据时,有时候我们需要更新部分字段,对已有的内容保持不变,通常我们可以通过Spring提供的bean工具类BeanUtils来实现 解决方法: BeanUtils复制对象,BeanUtils中的构造方法属性中可以通过传入更新时忽略的属性值来实现选择性复制原对象的字段.更新部分字段时,我们仅需要传入复制后的字段即可. 解析和实现: BeanUtils的构造方法: 具体更新部分字段的步骤: 查询出待更新对象的原有信息 通过传入的更新的象去复制产生一个新对象,其中

  • 详解Spring Data Jpa当属性为Null也更新的完美解决方案

    开场白 我本来是一名android开发者,突然就对java后端产生了浓烈的兴趣.所以,立马就转到了后端.第一个项目使用的使用Spring Data Jpa来操作数据库的,可是在更新数据的时候发现一个问题,属性值为Null竟然也更新,这就会导致本来没有更新的属性值,全部就成了Null. 原因 经过一番度娘操作,原来Jpa,不知道你是想把属性设置为Null,还是不想. 解决方法 找到一个方法,就是在数据模型上加上注解@DynamicUpdate,可是发现并不好使.而后经过整理,找到以下解决方案 我们

  • Jpa 实现自动更新表中的创建日期和修改时间

    一般来说创建时间和修改时间 两个字段是一个实体类必备的. 在阿里Java开发手册中也对此的说明: [强制]表必备三字段:id, create_time, update_time. 说明:其中 id 必为主键,类型为 bigint unsigned.单表时自增.步长为 1.create_time, update_time 的类型均为 datetime 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新. mysql 实现添加时间自动添加更新时间自动更新 在JPA 中也是支持新的数据保存是自

  • 解决spring-data-jpa 事物中修改属性自动更新update问题

    问题还原 项目orm层用的是spring-data-jpa,服务端接口实现的是树节点间的拖拽功能,测试环境联调发现异常问题,其中拖拽到目标目录后节点名称总会重名,重名规则是originName转化为originName(n) @Transactional(rollbackFor = Exception.class) public void move(MoveWorkSpaceDto moveWorkSpaceDto) { /*** * 个人空间节点移动 * 1.源节点移动到空目录,那么目标父节点

  • spring data jpa开启批量插入、批量更新的问题解析

    最近准备上spring全家桶写一下个人项目,该学的都学学,其中ORM框架,最早我用的是jdbcTemplate,后来用了Mybatis,唯独没有用过JPA(Hibernate)系的,过去觉得Hibernate太重量级了,后来随着springboot和spring data jpa出来之后,让我觉得好像还不错,再加上谷歌趋势... 只有中日韩在大规模用Mybatis(我严重怀疑是中国的外包),所以就很奇怪,虽然说中国的IT技术在慢慢抬头了,但是这社会IT发展的主导目前看来还是美国.欧洲,这里JPA

  • 解决spring data jpa saveAll() 保存过慢问题

    目录 spring data jpa saveAll() 保存过慢 问题发现 解决方案1 此方案在第二天失效了 以上方案有问题,下面附上彻底解决的截图和记录 JPA的saveAll方法执行效率很差 spring data jpa saveAll() 保存过慢 问题发现 今天在生产环境执行保存数据时 影响队列中其他程序的运行 随后加日志排查 发现 执行 4500条 insert操作时 耗时 9分钟 我类个去- 解决方案1 此方案在第二天失效了 废话不多说 直接上配置文件参数 application

  • 解决Spring Data Jpa 实体类自动创建数据库表失败问题

    目录 Spring Data Jpa 实体类自动创建数据库表失败 找了半天发现是一个配置的问题 可能导致JPA 无法自动建表的问题汇总 1.没加@Entity或引错Entity所在包 2.jpa配置中ddl-auto未设置update 3.实体类的包不是启动程序所在包的子包 4.mysql配置问题 5.依赖不全 6.实体类间关系错误 7.启动类注解问题 8.其他问题 Spring Data Jpa 实体类自动创建数据库表失败 先说一下我遇到的这个问题,首先我是通过maven创建了一个spring

  • SpringBoot整合Spring Data JPA的详细方法

    目录 前言 核心概念 新建SpringBoot项目 创建MySQL数据库 创建实体类 创建Repository 创建处理器 准备SQL文件 编写配置文件 最终效果 启动SpringBoot项目 查看数据库 自动更新数据表结构 测试JPA的增删改查 测试查询所有 测试保存数据 测试更新数据 测试删除数据 前言 Spring Data JPA 是更大的 Spring Data 家族的一部分,可以轻松实现基于 JPA 的存储库.该模块处理对基于 JPA 的数据访问层的增强支持.它使构建使用数据访问技术

  • spring data JPA 中的多属性排序方式

    目录 springdataJPA的多属性排序 第一步,引包 第二步,service方法代码 springdataJPA排序问题(orderby) spring data JPA的多属性排序 在此介绍我所用的一种方式: 第一步,引包 import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Order; 第二步,service方法代码 @Override     public P

  • spring data jpa @Query注解中delete语句报错的解决

    目录 spring data jpa @Query注解中delete语句报错 项目中需要删除掉表中的一些数据 JPA使用@Query注解实例 1. 一个使用@Query注解的简单例子 2. Like表达式 3. 使用Native SQL Query 4. 使用@Param注解注入参数 5. SPEL表达式(使用时请参考最后的补充说明) 6. 一个较完整的例子 7. S模糊查询注意问题 8. 解释例6中错误的原因 spring data jpa @Query注解中delete语句报错 项目中需要删

  • Spring Data JPA 实体类中常用注解说明

    目录 javax.persistence 介绍 基本注解 关联关系注解 关于关系查询的一些注意事项 javax.persistence 介绍 Spring Data JPA 采用约定大于配置的思想,默认了很多东西 JPA是存储业务实体关联的实体来源,它显示定义了如何定义一个面向普通Java对象(POJO)作为实体,以及如何与管理关系实体提供一套标准 javax.persistence位于hibernate-jpa-**.jar 包里面 jpa类层次结构: JPA类层次结构的显示单元: 单元 描述

随机推荐