java - Hibernate merge is generating new rows instead of updating

12,226

Alright, I found the solution. Thanks a lot to Kulbhushan Singh who made me find out about this:

I am using @GeneratedValue in my code:

@Id
@Column(name = "product_id")
@GeneratedValue
private int id;

What I didn't know is that when using @GeneratedValue only the database entries get a ID. The local objects will have the same ID as before. (0 by default). In my case that would mean that product_id in the database was 1, 2, 3, ... and in my objects it was 0.

When I tried to update my object I did the following:

CalculationStorage dbReference = em.getReference(CalculationStorage.class, calculations.getCreated());
dbReference.setProducts(localStorageObject.getProducts());

I basically set the generated Ids from the database back to 0, because the Id was still 0 in my local object.

And because of me setting the Id back to 0, the database generated new Ids.

EDIT: Obviously, if you restart your application the local id values will be 0 again. So try to pull the data from the db when you need it and let the Garbage Collector have it when you are done.

Share:
12,226
EXSolo
Author by

EXSolo

Updated on June 09, 2022

Comments

  • EXSolo
    EXSolo almost 2 years

    I am using Hibernate-EntityManager version 4.3.5. I have multiple tables using an id with a generated value.

    Inserting and such works fine, but when I try to merge Hibernate does not update the old rows and creates new ones.

    The content is the same, but the id is incrementing.

    Before merge: Before merge

    After merge: After merge

    I tried to use a custom Id generator to check if the Id already exists. With this new strategy i get the following error:

    Caused by: java.sql.SQLException: Field 'product_id' doesn't have a default value
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1094)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4226)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:4158)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2615)
    

    EDIT:

    I found out that @GeneratedValues will not set the ids in the local variables. That means that you might override the existing ones while merging. Though, that did not solve my problem.

    Here is the code I am using:

    @Entity(name = "calculation_storage")
    @Proxy(lazy = false)
    public class CalculationStorage {
    
    @Id
    private Date created;
    
    @OneToMany(mappedBy = "storage", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<Product> products;
    
    public CalculationStorage(Date created) {
        this.created = created;
        this.products = new LinkedList<Product>() {
            @Override
            public boolean add(Product product) {
                product.setStorage(CalculationStorage.this);
                return super.add(product);
            }
        };
    }
    
    public CalculationStorage(Date created, List<Product> products) {
        this.created = created;
        this.products = products;
    }
    
    public CalculationStorage() {
    }
    
    public Date getCreated() {
        return created;
    }
    
    public void setCreated(Date created) {
        this.created = created;
    }
    
    /**
     * Durch das aufrufen get add Methode wird der benoetigte Fremdschluesseleintrag gesetzt
     *
     * @return Alle Produkte die registriert sind
     */
    public List<Product> getProducts() {
        return products;
    }
    
    public void setProducts(List<Product> entities) {
        this.products = entities;
    }
    

    Produkt:

    @Entity(name = "product")
    public class Product implements Serializable {
    
    @Id
    @Column(name = "product_id")
    //@GeneratedValue
    @GeneratedValue(strategy=GenerationType.IDENTITY, generator="IdOrGenerated")
    @GenericGenerator(name="IdOrGenerated",
            strategy="at.iktopia.firmenverwaltung.abendabrechnung.control.hibernate.ProductIdGenerator"
    )
    private int id;
    
    private double costs;
    
    private String name;
    
    private ProductType type;
    
    @OneToMany(mappedBy = "product", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    private List<CalculatorEntity> entities;
    
    @ManyToOne
    @JoinColumn(name = "product_fk", nullable = false)
    private CalculationStorage storage;
    
    public Product(String name, ProductType type, double costs) {
        this.name = name;
        this.type = type;
        this.costs = costs;
        this.entities = new LinkedList<>();
    }
    
    /**
     * JPA - Konstruktor
     */
    public Product() {
    }
    
    public int getId() {
        return id;
    }
    
    public void setId(int id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public ProductType getType() {
        return type;
    }
    
    public void setType(ProductType type) {
        this.type = type;
    }
    
    public List<CalculatorEntity> getEntities() {
        return entities;
    }
    
    public void setEntities(List<CalculatorEntity> entities) {
        this.entities = entities;
    }
    
    public double getCosts() {
        return costs;
    }
    
    public void setCosts(double costs) {
        this.costs = costs;
    }
    
    public CalculationStorage getStorage() {
        return storage;
    }
    
    public void setStorage(CalculationStorage calculationStorage) {
        this.storage = calculationStorage;
    }
    }
    

    The Generator:

    public class ProductIdGenerator extends IdentityGenerator {
    @Override
    public Serializable generate(SessionImplementor session, Object obj) throws HibernateException {
        if (obj == null) throw new HibernateException(new NullPointerException());
    
        if ((((Product) obj).getId()) == 0) return super.generate(session, obj);
    
        else return ((Product) obj).getId();
    }
    }
    

    The update method that is called to merge the object:

    public class CalculationManager implements Manager<CalculationStorage, Date, CalculationStorageList> {
    
    /**
     * @see Manager#save(Object)
     */
    @Override
    public void save(CalculationStorage calculationStorage) {
        Validate.notNull(calculationStorage);
    
        EntityManager em = PersistenceManager.getInstance().getEntityManager();
        EntityTransaction transaction = em.getTransaction();
    
        transaction.begin();
    
        try {
            em.persist(calculationStorage);
            transaction.commit();
        } catch (Exception e) {
            ErrorHandler.handleException(e, JPA_PERSIST_ERROR_MESSAGE);
            if (transaction.isActive())
                transaction.rollback();
        }
    
        em.close();
    }
    
    /**
     * @see Manager#update(Object)
     */
    @Override
    public void update(CalculationStorage calculations) {
        Validate.notNull(calculations);
    
        EntityManager em = PersistenceManager.getInstance().getEntityManager();
        EntityTransaction transaction = em.getTransaction();
    
        transaction.begin();
    
        try {
            CalculationStorage c = em.getReference(CalculationStorage.class, calculations.getCreated());
            c.setProducts(calculations.getProducts());
    
            em.merge(c);
    
            transaction.commit();
        } catch (EntityNotFoundException e) {
            if (transaction.isActive())
                transaction.rollback();
            save(calculations);
        } catch (Exception e) {
            ErrorHandler.handleException(e, JPA_PERSIST_ERROR_MESSAGE);
            if (transaction.isActive())
                transaction.rollback();
        }
    
        em.close();
    }
    
    /**
     * @see Manager#getAll()
     */
    @Override
    public CalculationStorageList getAll() {
        EntityManager em = PersistenceManager.getInstance().getEntityManager();
    
        List<CalculationStorage> calculations;
    
        try {
            calculations = em.createQuery("SELECT c FROM calculation_storage c").getResultList();
        } catch (Exception e) {
            ErrorHandler.handleException(e, JPA_LOAD_ERROR_MESSAGE);
            return null;
        }
    
        em.close();
    
        return new CalculationStorageList(calculations);
    }
    
    /**
     * @see Manager#remove(Object)
     */
    @Override
    public void remove(Date primaryKey) {
        Validate.notNull(primaryKey);
    
        EntityManager em = PersistenceManager.getInstance().getEntityManager();
        EntityTransaction transaction = em.getTransaction();
    
        transaction.begin();
    
        try {
            em.remove(em.getReference(CalculationStorage.class, primaryKey));
            transaction.commit();
        } catch (Exception e) {
            ErrorHandler.handleException(e, JPA_PERSIST_ERROR_MESSAGE);
            if (transaction.isActive())
                transaction.rollback();
        }
    
        em.close();
    }
    
    /**
     * @see Manager#get(Object)
     */
    @Override
    public CalculationStorage get(Date primaryKey) {
        Validate.notNull(primaryKey);
    
        EntityManager em = PersistenceManager.getInstance().getEntityManager();
    
        try {
            CalculationStorage storage = em.getReference(CalculationStorage.class, primaryKey);
            em.close();
            return storage;
        } catch (Exception e) {
            ErrorHandler.handleException(e, JPA_LOAD_ERROR_MESSAGE);
            return null;
        }
    }
    }
    

    I am clueless what could cause this issue.