SpringDataJpa set()方法自动保存失效

问题描述:

springdatajpa支持直接操作对象设置属性进行更新数据库记录的方式,正常情况下,get()得到的对象直接进行set后,即使不进行save操作,也将自动更新数据记录,将改动持久化到数据库中,但这里可以看到并没有生效。

问题分析:

根据问题分析可推测,大概有几种可能导致不生效:

  1. 对象不属于托管态,所以set后不生效

  1. 字段设置了@Transient注解

  1. 设置了readOnly=true,导致不生效

  1. 未flush缓存,导致不生效

    • 对象不属于托管态,所以set后不生效

首先需要了解springdatajpa自动保存的原理,这里涉及到springdatajpa中对象的几种存在状态以及jpa的缓存机制。感兴趣的可以自己详细了解一下。这里不展开说了,

Heibernate 中对象分为以下几种状态:

一、瞬时状态

瞬时状态的实体就是一个普通的java对象,和持久化上下文无关联,数据库中也没有数据与之对应。

二、托管状态

使用EntityManager进行find或者persist操作返回的对象即处于托管状态,此时该对象已经处于持久化上下文中,因此任何对于该实体的更新都会同步到数据库中。JavaBean与EntityManager发生关系后将被持久化,该Bean的任何属性改动都会牵涉到数据库记录的改动。也就是说只要我们改动这个Bean的属性,无需我们去调用其他方法,数据库的数据会自动同步。

表现为 :对 Jpa 对象进行set,但是不save,数据库也能自动更新。

三、游离状态

当事务提交后,处于托管状态的对象就转变为了游离状态。此时该对象已经不处于持久化上下文中,因此任何对于该对象的修改都不会同步到数据库中。

四、删除状态

当调用EntityManger对实体进行delete后,该实体对象就处于删除状态。其本质也就是一个瞬时状态的对象。

不同状态之间,可通过编码调用不同方法或执行相关操作触发进行转换,如刚new出来的对象是瞬时态,瞬时态可通过调用persist方法转变成托管状态,当处在托管状态的实体 Bean 被管理器 flush 了,那么就在极短暂的时间进入了持久化状态,持久态的对象属性的更改都会牵涉到数据库记录的改动。

五、持久化状态

当处在托管状态的实体Bean被管理器flush了,那么就在极短暂的时间进入了持久化状态,事务提交之后,立刻变为了游离状态。可以把持久化状态当做实实在在的数据库记录。

回到问题:

book对象为从数据库中查询得到,属于托管态,正常情况下,此时对book对象进行set操作,修改name后,应该会改变数据库中的name,但实际上执行后却并未生效。手动执行save操作后发现生效了。

第二种情况:设置了@Transient注解

查看代码可知,name字段并未设置@Transient注解

三、设置了readOnly=true,flush不刷新,导致不生效

这里需要了解jpa的缓存机制

一级缓存

Spring Data JPA的一级缓存就是当使用自定义Repositoryfind()或者findxx()方法去查询记录时,第一次会去查询数据库,然后会把查询结果存放到内存中作为缓存,后面再查询相同的记录时会直接把缓存中的结果返回,不再去查询数据库。此后对查询出来的实体进行属性修改时,均会修改缓存中的数据。

flush()方法将缓存中所有发生修改的实体的状态信息同步到数据库中。当在一个事务内通过save()方法去update一个从数据库中查询出来的实体时,Spring Data JPA并不会马上执行Update SQL语句,将修改同步到数据库,而是等到事务提交时才会通过某种机制(下面会对该机制进行描述)决定是否调用flush()方法将缓存中的实体信息同步到数据库中,当调用 flush()方法时才会执行Update SQL语句。

快照区

Spring Data JPA除了一级缓存外,还有一个快照区,当将查询结果放到一级缓存中时,会同时复制一份数据放入快照区中,Spring Data JPA通过快照区与缓存中的数据是否一致来判断数据从数据库查询出来后是否发生过修改。

Spring Data JPA在事务提交时,为了保持数据库和缓存的数据同步,会清理一级缓存并根据主键字段值判断一级缓存中的对象属性值和快照中的对象属性值是否一致,如果两个对象的属性值不一致,则调用flush()方法执行Update SQL语句,将缓存的内容同步到数据库,并更新快照;如果一致,则不调用flush()方法。

在设置了@Transactional(readOnly = true)不会进行自动flush操作

设置了@Transactional(readOnly = false)会在事务commit时触发flush方法,所以在图中第二部分执行结果中,Flushing session 在Initiating transaction commit之后

而对于手动执行flush操作的,也就是图中第三部分执行结果中,Flushing session会在Flushing session 在Initiating transaction commit之前。

Flush与事务Commit的关系

在当前的事务执行commit时会触发flush方法

在当前的事务执行完commit时,如果隔离级别是可重复度,flush之后执行的update,insert,delete的操作会被其他的新事物看到最新的结果。

假设当前的事务是可重复度,手动执行flush方法之后,没有执行commit,那么其他事务是看不到最新值的变化的。但最新值变化对当前没有commit的事务是有效的。

如果执行了flush之后,当前事务发生了rollback操作,那么数据将会被回滚(数据库的机制)

Flush的自动机制

前面讲到了当前的事务执行commit时会触发flush方法,那么除此之外还有什么情况下会自动触发flush呢?

1.事务commit之前,即指执行transactionManager.commit()之前都会触发

2.执行任何的JPQL或者nativeSQL(代替直接操作Entity的方法)都会触发flush(可以理解为不走一级缓存,所以这个时候拿到的可能是旧数据,所以在此之前需要把当前的最新改动flush到DB)

回到问题:

检查代码发现并没有设置readOnly。

4、未flush缓存,导致不生效

根据上面的排查,基本可以判断是因为没有触发flush操作,导致数据未自动更新到数据库,结合flush的刷新机制可知,事务执行commit时会触发flush方法,那么既然没有flush,说明事务有问题,这里的事务失效了,方法是使用@Postconstruct注解标识,启动后自动执行方法,这样的方式,无法通过注解方式生成代理类,事务不会生效,自然也不会触发flush操作从而导致set方法自动更新数据库记录失效。

解决办法:将方法放到service中,使用注解事务。在上层controller中去调用,保证事务生效即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Spring Data JPA中的@Query注解可以在抽象方法上使用。该注解允许我们直接编写自定义的SQL查询语句,以替代默认的基于方法命名规则的查询。 使用@Query注解,我们可以在抽象方法上定义自己的查询语句。可以通过在注解中编写原生的SQL查询语句或者使用JPQL查询语句来完成。 当使用原生的SQL查询语句时,我们需要设置nativeQuery参数为true。这样Spring Data JPA就会将查询结果与实体类进行映射。 如果使用JPQL查询语句,我们可以通过在查询语句中引用实体类和其属性来构建查询语句。Spring Data JPA会根据查询语句的返回类型自动进行结果映射。 在@Query注解中,我们还可以使用命名参数或者索引参数来指定查询参数。通过在查询方法的参数前添加@Param注解,我们可以将方法参数与查询参数进行映射。 除了定义查询语句,@Query注解还可以指定查询的排序方式、分页和锁定等。我们可以使用关键字ORDER BY来设置排序字段,使用关键字LIMIT和OFFSET来设置分页查询的起始位置和返回记录数。同时,我们还可以使用关键字FOR UPDATE来设置查询结果的锁定。 通过在抽象方法上使用@Query注解,我们可以实现更加灵活和复杂的查询需求。它为我们提供了一种强大的方式来利用Spring Data JPA进行自定义查询,以满足特定业务场景的需求。 ### 回答2: Spring Data JPA提供了一个 @Query 注解,可以在抽象方法上使用。 @Query 注解用于在 Repository 接口中定义查询方法的具体查询语句。通过使用 @Query 注解,我们可以将自定义的 JPQL 或者 SQL 语句与方法绑定在一起。 使用 @Query 注解有以下几个优点: 1. 灵活性:可以使用自定义的查询语句,满足特定的数据查询需求。 2. 类型安全:由于使用了命名参数或者索引参数,可以避免 SQL 注入等安全问题。 3. 提高代码可读性:通过在方法上添加 @Query 注解,可以直接看到方法的具体查询是如何实现的,提高代码的可读性和可维护性。 4. 内置分页支持:可以在 @Query 注解中使用内置的分页查询方法,如 Pageable 和 Sort。 使用 @Query 注解时,可以将查询语句写在注解的 value 属性中。语句可以是原生的 SQL 语句,也可以是 JPQL 查询语句。在查询语句中,可以使用实体类的属性名来代替数据库表字段名。 另外,@Query 注解还支持使用命名参数和索引参数。命名参数使用冒号(:)加参数名的方式,在方法参数中使用 @Param 注解指定参数名。索引参数使用问号(?)加索引位置的方式。 需要注意的是,使用 @Query 注解时,方法的返回类型可以是具体的实体类,也可以是包装类、List、Set 或者其他集合类。如果需要分页查询,返回类型可以是 Page 或者 Slice 类型。 总的来说,通过在抽象方法中使用 Spring Data JPA 的 @Query 注解,可以更加灵活地定义自定义查询,提高代码的可读性和可维护性。 ### 回答3: Spring Data JPA提供了 @Query 注解,它可以用在抽象方法上,用于自定义查询语句。 使用 @Query 注解,我们可以在抽象方法上定义自己的JPQL(Java Persistence Query Language)或SQL查询语句。通过在注解中定义查询语句,我们可以灵活地执行各种复杂的查询操作。 在定义查询语句时,我们可以使用实体类的属性名来引用实体的字段,并且可以使用 JPA 提供的各种查询关键字和函数进行组合。除此之外,我们还可以使用一些特殊的 JPA 提供的关键字,如:distinct、is not null、is null、order by等。 在定义查询语句时,@Query 注解还支持使用命名参数和位置参数两种方式来传递参数。命名参数使用 :paramName 的方式来引用参数,而位置参数使用 ?1、?2 等符号来引用参数。我们可以根据实际情况选择适合的参数传递方式。 除了定义查询语句,@Query 注解还有其他一些属性。例如,我们可以使用 @Modifying 注解来告诉 Spring Data JPA 这个查询是一个更新操作,需要通过 @Transactional 注解来开启事务。另外,还可以使用 @Query 注解的 nativeQuery 属性来指示是否执行原生 SQL 查询。 总之,通过在抽象方法上使用 @Query 注解,我们可以自定义我们需要的查询语句,使得我们可以方便地执行各种复杂的查询操作。这样,在使用 Spring Data JPA 进行数据访问时,我们就能够更加灵活地控制查询操作,并且减少重复代码的编写。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值