【问题标题】:EntityManager merge() causes duplicate rowsEntityManager merge() 导致重复行
【发布时间】:2016-11-05 12:30:39
【问题描述】:

EntityManager.merge() 出现了一些非常奇怪的行为。这就是正在发生的事情。我有一个带有 Spring MVC、Hibernate 和 JPA 的 REST API 设置,用于从 MySQL 数据库中插入、更新和删除项目。插入和更新函数都使用EntityManager.merge(),并且它们在 99% 的时间都可以正常工作。

然而,有一次,似乎是第一次调用更新记录,它会在数据库中创建记录的副本,然后返回原始记录并按应有的方式对其进行更改。

我真的不知道为什么会发生这种情况。您可能会认为,如果它创建了重复记录,它会编辑该记录,但不,在更新时它会创建重复记录,放弃它,然后返回到第一条记录进行更改。

我在想这是因为我在实体 bean 中设置了哈希码和 equals,它可能是这样。虽然我认为它们没有任何问题,因为我基于唯一键而不是 ID。

我的代码如下。任何关于正在发生的事情的想法都会有很大帮助。

这是实体:

@Entity
@Table(name = "DAY_ITEM")
public class DayItem implements GtEntity, Serializable{
    private Long id;
    private String email;
    private String name;
    private BigDecimal amount;
    private Integer modId;
    private Integer fmOrder;
    private String modName;
    private Date daysDate;
    private String daysDateString;
    private Long foodId;
    private Long mealId;
    private FoodItem foodItem;
    private MealItem mealItem;
    private ArrayList<FoodItem> foodItems;
    private ArrayList<MealItem> mealItems;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Basic
    @Column(name = "EMAIL", nullable = false, length = 50)
    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    @Basic
    @Column(name = "NAME", nullable = true, length = 100)
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Basic
    @Column(name = "AMOUNT", nullable = true)
    public BigDecimal getAmount() {
        return amount;
    }

    public void setAmount(BigDecimal amount) {
        this.amount = amount;
    }

    @Basic
    @Column(name = "MOD_ID", nullable = false)
    public Integer getModId() {
        return modId;
    }

    public void setModId(Integer modId) {
        this.modId = modId;
    }

    @Basic
    @Column(name = "FM_ORDER", nullable = false)
    public Integer getFmOrder() {
        return fmOrder;
    }

    public void setFmOrder(Integer fmOrder) {
        this.fmOrder = fmOrder;
    }

    @Basic
    @Column(name = "MOD_NAME", nullable = true, length = 50)
    public String getModName() {
        return modName;
    }

    public void setModName(String modName) {
        this.modName = modName;
    }

    @Basic
    @Column(name = "DAYS_DATE", nullable = false)
    public Date getDaysDate() {
        return daysDate;
    }

    public void setDaysDate(Date daysDate) {
        this.daysDate = daysDate;
    }

    @Transient
    public String getDaysDateString() {
        if(daysDateString == null) {
            DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
            return formatter.format(daysDate);
        }
        return daysDateString;
    }

    public void setDaysDateString(String daysDateString) {
        this.daysDateString = daysDateString;
    }

    @Basic
    @Column(name = "FOOD_ID", nullable = true)
    public Long getFoodId() {
        return foodId;
    }

    public void setFoodId(Long foodId) {
        this.foodId = foodId;
    }

    @Basic
    @Column(name = "MEAL_ID", nullable = true)
    public Long getMealId() {
        return mealId;
    }

    public void setMealId(Long mealId) {
        this.mealId = mealId;
    }

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "FOOD_ID", referencedColumnName = "ID",
            updatable = false, insertable = false)
    public FoodItem getFoodItem() {
        return foodItem;
    }

    public void setFoodItem(FoodItem foodItem) {
        this.foodItem = foodItem;
    }

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "MEAL_ID", referencedColumnName = "ID",
            updatable = false, insertable = false)
    public MealItem getMealItem() {
        return mealItem;
    }

    public void setMealItem(MealItem mealItem) {
        this.mealItem = mealItem;
    }

    @Transient
    public ArrayList<FoodItem> getFoodItems() {
        return foodItems;
    }

    public void setFoodItems(ArrayList<FoodItem> foodItems) {
        this.foodItems = foodItems;
    }

    @Transient
    public ArrayList<MealItem> getMealItems() {
        return mealItems;
    }

    public void setMealItems(ArrayList<MealItem> mealItems) {
        this.mealItems = mealItems;
    }

    public void makeDaysDate() throws ParseException{
        DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
        this.daysDate = new Date(formatter.parse(this.daysDateString).getTime());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof DayItem)) return false;

        DayItem dayItem = (DayItem) o;

        if (!getEmail().equals(dayItem.getEmail())) return false;
        if (!getModId().equals(dayItem.getModId())) return false;
        if (!getFmOrder().equals(dayItem.getFmOrder())) return false;
        if (!getDaysDateString().equals(dayItem.getDaysDateString())) return false;
        if (getFoodId() != null ? !getFoodId().equals(dayItem.getFoodId()) : dayItem.getFoodId() != null) return false;
        return getMealId() != null ? getMealId().equals(dayItem.getMealId()) : dayItem.getMealId() == null;

    }

    @Override
    public int hashCode() {
        int result = getEmail().hashCode();
        result = 31 * result + getModId().hashCode();
        result = 31 * result + getFmOrder().hashCode();
        result = 31 * result + getDaysDateString().hashCode();
        result = 31 * result + (getFoodId() != null ? getFoodId().hashCode() : 0);
        result = 31 * result + (getMealId() != null ? getMealId().hashCode() : 0);
        return result;
    }
}

这是进行初始创建的代码:

@Override
public List<DayItem> saveDayItem(DayItem item) throws Exception {
    EntityManager manager = entityManagerFactory.createEntityManager();
    EntityTransaction tx = manager.getTransaction();
    List<DayItem> results = null;
    try {
        tx.begin();
        manager.merge(item);
        manager.flush();
        manager.clear();
        results = findUserDay(item.getDaysDate(), item.getEmail(), manager);
        tx.commit();
    } catch (Exception e) {
        tx.rollback();
        throw e;
    } finally {
        manager.close();
        return results;
    }
}

以下是执行更新的代码:

public <T extends GtEntity> T store(T entity) throws Exception {
    T managedEntity = null;
    EntityManager manager = entityManagerFactory.createEntityManager();
    EntityTransaction tx = manager.getTransaction();
    try{
        tx.begin();
        managedEntity = manager.merge(entity);
        tx.commit();
    }catch(RuntimeException e){
        tx.rollback();
        throw e;
    }finally {
        manager.close();
        return managedEntity;
    }
}

【问题讨论】:

  • 实体更新时是否有ID?
  • @DanielJipa 是的,它与原始记录具有相同的 ID。

标签: mysql hibernate jpa entitymanager


【解决方案1】:

由于您实现了equalshasCode,Hibernate 将使用这些方法来查找要更新的实体。假设分离的对象已更改,equalshasCode 将永远不会返回与附加实体相同的值,因此 Hibernate 将假定它是一个新实例。

您应该阅读有关 equals and hasCode 的休眠文档。

【讨论】:

  • 经过仔细调试,我确定不是这个问题。每次运行更新函数时,EntityManager.contains() 都会为我传递的实体返回 false,这意味着它不在持久性上下文中。它每次都返回 false,但它只创建一个副本。所以还有其他事情发生。
  • 更改您的 equals 和 hashCode 实现以仅使用 Id
  • @DanielJipa 我使用了EntityManager.find(),它采用它将在其 id 中查找的实体的类。在对更新函数的调用之一它返回空值。意味着它找不到它。不过,仅在其中一个电话上。而且它总是在同一点上,它是 DayItem 的金额字段等于 1.02 的时候。它始终是 1.02,没有其他数字会导致 DayItem 重复。我真的很茫然。
  • @Graham EntityManager.contains() 将使用equalshashCode 来确定实体是否加载到上下文中。如果更改实体的一个字段并提交给merge()EntityManager.contains() 将始终返回false,因为该字段已更改。
  • 对了,你说的“其中一个调用”是指顺序调用还是并发调用?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多