【问题标题】:spring data jpa composite key duplicate key record insertion resulting in updatespring data jpa复合键重复键记录插入导致更新
【发布时间】:2015-01-24 21:32:25
【问题描述】:

我有一个具有复合键的实体,我正在尝试通过使用 spring data jpa 存储库到 mysql 数据库来将其持久化,如下所示:

@Embeddable
public class MobileVerificationKey implements Serializable{
private static final long serialVersionUID = 1L;

@Column(name="CUSTOMERID")
private Long customerId;

@Column(name="CUSTOMERTYPE")
private Integer customerType;

@Column(name="MOBILE")
private Long mobile;
@Embeddable
public class MobileVerificationKey implements Serializable{

    private static final long serialVersionUID = 1L;

    @Column(name="CUSTOMERID")
    private Long customerId;

    @Column(name="CUSTOMERTYPE")
    private Integer customerType;

    @Column(name="MOBILE")
    private Long mobile;
//getter and setters
}

实体为

@Entity
@Table(name="mobileverificationdetails")
public class MobileVerificationDetails {

    @EmbeddedId
    private MobileVerificationKey key;

    @Column(name="MOBILETYPE")
    private String mobileType;

    @Column(name="MOBILEPIN")
    private Integer mobilePin;
//getters and setters

}

我的 spring 数据 jpa 存储库如下所示:

public interface MobileVerificationDetailsRepository extends
        CrudRepository<MobileVerificationDetails, MobileVerificationKey> {

    @Override
    MobileVerificationDetails save(MobileVerificationDetails mobileVerificationDetails);

    @Override
    MobileVerificationDetails  findOne(MobileVerificationKey id);
}

现在,如果我尝试为原始记录添加具有相同键的重复记录,而为其他字段添加不同值。当我尝试插入第二条记录时,它会导致使用新值更新现有记录,而不是因违反主键而引发异常约束...任何人都可以向我解释这种行为。

【问题讨论】:

  • 您的更新代码 (DAO) 是什么?您这里只有型号代码。
  • Spring data jpa 存储库会合并您的实体(如果它已经存在)。裸 EntityManager#persist() 将产生您要求的行为。
  • @Michal 有没有使用 spring data jpa 的解决方案?
  • 你可以用自定义的persist()方法扩展你的spring数据jpa存储库/存储库,并通过EntityManager#persist()实现它,EntityManager可以注入到自定义方法的实现类中。这将是官方支持的方式。
  • 您也可以尝试直接覆盖 SimpleJpaRepository 上的 save() 方法。然后,您需要更改 spring 内部接线,以便使用您的扩展代替 SimpleJpaRepository。请注意,修改 SimpleJpaRepository 不是官方/支持的做事方式,并且可能(或可能不会)产生一些副作用。

标签: java spring jpa spring-data spring-data-jpa


【解决方案1】:

Spring Data Jpa Repository 功能是通过包含以下 save(..) 方法的 SimpleJpaRepository 类实现的:

@Transactional
public <S extends T> S save(S entity) {

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}

因此 Spring Jpa Data Repository save(...) 方法合并了一个已经存在的实体。

与裸EntityManager#persist()相反,如果使用已经存在的实体调用,则会引发异常。

可以通过将custom behavior 添加到 Spring Data Repository/ies 来解决问题。可以使用1.3.1 Adding custom behavior to single repositoriesexample here1.3.2 Adding custom behavior to all repositoriesexample here 中描述的方法之一添加自定义行为。在这两种情况下,自定义行为都将包括一个委托给 EntityManager#persist() 的新 persist() 方法。请注意,在方法 1.3.2 中。您已经有一个 EntityManager 实例,在方法 1.3.1 中,您可以使用 @PersistenceContext 注入 EntityManager 实例。

与我的评论相反,我建议向存储库添加新方法,而不是覆盖现有的 save(...)。

【讨论】:

  • 我建议向存储库添加新方法,而不是覆盖现有的保存(...)。您能否建议,如何做到这一点?
  • 它已经存在于答案中 - 链接的文档和示例显示了如何做到这一点。还有什么不明白的吗?
【解决方案2】:

解决此问题的最简单(且侵入性最小)的方法可能是确保仅在持久化之前设置 id。这可以在@PrePersist 回调中实现:

abstract class MobileVerificationDetails {

  @EmbeddedId
  private MobileVerificationKey id;

  @PrePersist
  void initIdentifier() {

    if (id == null) {
      this.id = … // Create ID instance here.
    }
  }
}

或者,您可以通过实现Persistable 并相应地实现isNew() 来强制使用persist(…)。确保此方法在第一次插入时返回 true。我们通常会看到人们持有一个在 @PostPersist/@PostLoad 注释方法中更新的瞬态布尔标志。

abstract class AbstractEntity<ID extends Serializable> implements Persistable<ID> {

  private @Transient boolean isNew = true;

  @Override
  public boolean isNew() {
    return isNew;
  }

  @PostPersist
  @PostLoad
  void markNotNew() {
    this.isNew = false;
  }
}

【讨论】:

  • 感谢您指出这一点,我不知道这一点。我认为这是一个很好的解决方案,只要可以在模型项目中的某处使用 import org.springframework.data.domain.Persistable。可能对某些用户没问题,而对另一些用户则不行。
  • @Michal - 您还可以通过自定义JpaRepositoryFactoryFactoryBean 来调整存储库使用的EntityInformation 实例。然而,这通常需要更多的努力。另一种方法是确保标识符属性仅被设置为预先保持。我会相应地更新我的答案。
猜你喜欢
  • 2017-06-26
  • 1970-01-01
  • 2019-10-09
  • 1970-01-01
  • 2023-03-10
  • 1970-01-01
  • 1970-01-01
  • 2021-06-22
  • 1970-01-01
相关资源
最近更新 更多