2016-07-03 3 views
0

Я получаю очень странное поведение с EntityManager.merge(). Вот что происходит. У меня есть настройка REST API с Spring MVC, Hibernate и JPA для вставки, обновления и удаления элементов из базы данных MySQL. Функции вставки и обновления используют EntityManager.merge(), и оба они отлично работают в 99% случаев.EntityManager merge() вызывает повторяющиеся строки

Однако в какой-то момент, который, по-видимому, является первым вызовом для обновления записи, он создает дубликат записи в базе данных и затем возвращается к исходной записи и вносит в нее изменения, как следует.

Я действительно в недоумении, почему это происходит. Вы могли бы подумать, что если бы он создал дубликат записи, он отредактировал бы это, но нет, при обновлении он создаст дубликат, покинет его и вернется к первой записи, чтобы внести изменения.

Я думал, что это из-за того, что у меня есть мой хэш-код и равна настройке в моих сущностях, что это может быть. Хотя я не вижу в них ничего плохого, так как я основывал их на уникальных ключах, а не на 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; 
    } 
} 
+0

Имеет ли объект идентификатор при обновлении? –

+0

@ DanielJipa Да, он имеет тот же идентификатор, что и оригинальная запись. – Graham

ответ

0

Поскольку вы реализовали equals и hasCode , Hibernate будет использовать эти методы для поиска enti для обновления. Предполагая, что удаленный объект был изменен, equals и hasCode никогда не вернут то же значение, что и прикрепленный объект, поэтому Hibernate предположит, что это новый экземпляр.

Вы должны прочитать документ спящего режима о equals and hasCode.

+0

После тщательной отладки я решил, что это не проблема. Каждый раз, когда запускается функция обновления, 'EntityManager.contains()' возвращает false для объекта, который я передаю, что означает, что он не находится в контексте сохранения. Он возвращает false каждый раз, но он только создает дубликат один раз. Так что происходит что-то еще. – Graham

+0

Измените свои значения equals и hashCode, чтобы использовать только Id –

+0

@DanielJipa. Я использовал 'EntityManager.find()', который принимает класс объекта, который будет искать его идентификатор. При одном из вызовов функции обновления он возвращает нулевое значение. Это означает, что он не смог найти его. Только на одном из звонков. И это всегда в той же точке, когда поле количества DayItem равно 1.02. Это всегда 1.02, ни один другой номер не приводит к дублированию DayItem. Я действительно в недоумении. – Graham

Смежные вопросы