0

I have an entity that has a collection of entities, as bellow:

@Entity
@Table
public class Company extends AbstractEntity implements Serializable {

@OneToMany(cascade = {CascadeType.MERGE, CascadeType.REFRESH,
        CascadeType.REMOVE}, targetEntity = Meter.class,
            fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "owner")
    private Collection<Meter> meters;

    public Collection<Meter> getMeters() {
        return this.meters;
    }

    public void addMeter(Meter meter) {
        this.meters.add(meter);
    }
}

The problem is when I try to associate another "meter" to the "company". I'm trying with this:

private void buttonSaveAsActionPerformed(java.awt.event.ActionEvent evt) {
        final Meter edited = meterEditionPanel.getMeter();

        if (edited == null)
            return;
        final Company company = edited.getOwner();
        company.addMeter(edited);
        try {
            service.updateEntity(company);
            model.update();
            listMeters.setSelectedValue(edited.getAlias(), true);
            meterEditionPanel.setMeter(edited);
        } catch (Exception ex) {
            FieldMarker.showEntityAlreadyExistsInCompanyDialog(
                    dictionary.getString("commons.title.meter"),
                    dictionary.getString("commons.text.serialNumber"), false);
            LOGGER.debug("Não foi possível atualizar o medidor: " + ex.toString());
        }
}

As one can see, I'm using the company update to create the meter. When I run this code, I get an org.hibernate.LazyInitializationException.

I have tried also to create the "meter" directly, like bellow:

private void buttonSaveAsActionPerformed(java.awt.event.ActionEvent evt) {                                             
        final StringListModel<Meter> model = getListModel();
        final Meter edited = meterEditionPanel.getMeter();

        if (edited == null)
            return;
//        final Company company = edited.getOwner();
//        company.addMeter(edited);
        try {
            service.createEntity(edited);
            model.update();
            listMeters.setSelectedValue(edited.getAlias(), true);
            meterEditionPanel.setMeter(edited);
        } catch (Exception ex) {
            FieldMarker.showEntityAlreadyExistsInCompanyDialog(
                    dictionary.getString("commons.title.meter"),
                    dictionary.getString("commons.text.serialNumber"), false);
            LOGGER.debug("Não foi possível atualizar o medidor: " + ex.toString());
        }
    } 

With this change I got another error: 2016-03-03 11:36:37,145 [AWT-EventQueue-0] ERROR - SqlExceptionHelper:logExceptions:146 - ERROR: duplicate key value violates unique constraint "meter_pkey" Detalhe: Key (id)=(11) already exists. But the meter extends AbstractEntity:

@MappedSuperclass
public abstract class AbstractEntity implements Serializable {

    @Column(unique=true,nullable=false)
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;

It seems that the GeneratedValue annotation can't get a correct id to lazily initialized entities.

The createEntity is the method bellow:

public <T extends AbstractEntity> T createEntity(T entity) {

        try {
            JpaRepository<T, Long> repository = getRepository(entity.getClass());
            return repository.saveAndFlush(entity);
        } catch (Exception e) {
            logger.error("Erro de criação de entidade: " + e.toString());
            return null;
        }
    }

This is the meter class:

package com.metrum.mtuapp.persistence.model;

import java.io.Serializable;
import java.util.Collection;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import org.springframework.util.Assert;

@Entity
public class Meter extends AbstractEntity implements Serializable {

    @Basic
    @Column(nullable = false)
    private String serialNumber;

    @Basic
    @Column(nullable = false, unique = true)
    private String identification;

    @ManyToOne(optional = false, targetEntity = Company.class)
    private Company owner;

    @Basic
    private String description;

    @ManyToOne(optional = false, targetEntity = MeterModel.class)
    private MeterModel meterModel;

    @OneToMany(cascade = {CascadeType.ALL}, targetEntity = Calibration.class,
            orphanRemoval = true, mappedBy = "meter")
    private Collection<Calibration> calibrations;

    public Meter() {

    }

    public Meter(String description, String serialNumber,
            Company owner, MeterModel meterModel) {
        Assert.hasText(serialNumber);
        Assert.notNull(meterModel);
        Assert.notNull(owner);

        this.description = description;
        this.serialNumber = serialNumber;
        this.owner = owner;
        this.meterModel = meterModel;
        this.identification = getAlias(owner.getName(), serialNumber);
    }


    private static String getAlias(String companyName, String serialNumber) {
        return serialNumber + " [" + companyName + "]";
    }

    public Collection<Calibration> getCalibrations() {
        return this.calibrations;
    }

    public void addCalibration(Calibration calibration) {
        this.calibrations.add(calibration);
    }

    public String getSerialNumber() {
        return this.serialNumber;
    }

    public void setSerialNumber(String serialNumber) {
        this.serialNumber = serialNumber;
        this.identification = getAlias(owner.getName(), serialNumber);
    }

    public String getDescription() {
        return this.description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public MeterModel getMeterModel() {
        return this.meterModel;
    }

    public void setMeterModel(MeterModel meterModel) {
        this.meterModel = meterModel;
    }

    public Company getOwner() {
        return this.owner;
    }

    public void setOwner(Company owner) {
        this.owner = owner;
        this.identification = getAlias(owner.getName(), serialNumber);
    }

    public String getIdentification() {
        return identification;
    }

    public void setIdentification(String identification) {
        this.identification = identification;
    }

    public void copy(Meter edited) {
        this.identification = edited.identification;
        this.description = edited.description;
        this.meterModel = edited.meterModel;
        this.owner = edited.owner;
        this.serialNumber = edited.serialNumber;
    }

    @Override
    public String getAlias() {
        return getIdentification();
    }
}

And this is the company class:

package com.metrum.mtuapp.persistence.model;

import java.io.Serializable;
import java.util.Collection;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity
@Table
public class Company extends AbstractEntity implements Serializable {

    @Column(unique = true, nullable = false)
    @Basic
    private String name;

    @Basic
    private String address;

    @Basic
    private String telephone;

    @Basic
    private String email;

    @Basic
    private String note;

    @OneToMany(cascade = {CascadeType.MERGE, CascadeType.REFRESH,
        CascadeType.REMOVE}, targetEntity = Employee.class,
            fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "company")
    private Collection<Employee> employees;

    @OneToMany(cascade = {CascadeType.MERGE, CascadeType.REFRESH,
        CascadeType.REMOVE}, targetEntity = Standard.class,
            fetch = FetchType.EAGER, orphanRemoval = true, mappedBy = "owner")
    private Collection<Standard> standards;

    @OneToMany(cascade = {CascadeType.MERGE, CascadeType.REFRESH,
        CascadeType.REMOVE}, targetEntity = Meter.class,
            fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "owner")
    private Collection<Meter> meters;

    public Company() {

    }

    public Company(String name, String address, String telephone, String email,
            String note) {
        this.name = name;
        this.address = address;
        this.telephone = telephone;
        this.email = email;
        this.note = note;
    }

    public Collection<Standard> getStandards() {
        return this.standards;
    }

    public void addStandard(Standard standard) {
        this.standards.add(standard);
    }

    public String getNote() {
        return this.note;
    }

    public void setNote(String note) {
        this.note = note;
    }

    public String getAddress() {
        return this.address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getName() {
        return this.name;
    }

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

    public String getTelephone() {
        return this.telephone;
    }

    public void setTelephone(String telephone) {
        this.telephone = telephone;
    }

    public Collection<Employee> getEmployees() {
        return this.employees;
    }

    public void addEmployee(Employee employee) {
        this.employees.add(employee);
    }

    public String getEmail() {
        return this.email;
    }

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

    public Collection<Meter> getMeters() {
        return this.meters;
    }

    public void addMeter(Meter meter) {
        this.meters.add(meter);
    }

    public Meter getMeter(String serialNumber) {
        for (Meter meter : meters) {
            if (meter.getSerialNumber().equals(serialNumber)) {
                return meter;
            }
        }

        return null;
    }

    public Standard getStandard(String serialNumber) {
        for (Standard standard : standards) {
            if (standard.getSerialNumber().equals(serialNumber)) {
                return standard;
            }
        }

        return null;
    }

    public void copy(Company source) {
        setName(source.getName());
        setAddress(source.getAddress());
        setEmail(source.getEmail());
        setTelephone(source.getTelephone());
        setNote(source.getNote());
    }

    @Override
    public String getAlias() {
        return getName();
    }
}

What is the best way to solve this problem, without using Earge initialization.

Possible sulution

SessionFactory sessionFactory = context.getBean(SessionFactory.class);
final Session session = sessionFactory.openSession();
session.beginTransaction();
session.persist(edited);
session.getTransaction().commit();
session.close();

It works fine, but I'll wait for the more suitable solution for this problem.

Thanks!

Leandro Lima
  • 1,140
  • 3
  • 24
  • 42

2 Answers2

0

You have a foreign key column to the Company in the Meter table. So you don't need to add Meter to the Company. Just set Company to the Meter and save Meter to update a foreign key in the Meter table.

Update

@OneToMany(cascade = {CascadeType.MERGE, CascadeType.REFRESH,
        CascadeType.REMOVE}, targetEntity = Meter.class,
            fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "owner")
private Collection<Meter> meters;

This cascades don't mean (I am not sure, but I think so), that when you add new Meter to the company and update company, a new Meter will be inserted in the table.

And try to set LAZY to the properties of Meter

public class Meter {

    @ManyToOne(fetch = FetchType.LAZY, optional = false, targetEntity = Company.class)
    private Company owner;

    @ManyToOne(fetch = FetchType.LAZY, optional = false, targetEntity = MeterModel.class)
    private MeterModel meterModel;

}
v.ladynev
  • 19,275
  • 8
  • 46
  • 67
  • When I try this, I get an error saying that the primary key already exists. – Leandro Lima Mar 03 '16 at 14:32
  • @LeandroLima You just need to update `Meter` don't save it. Please, add `Meter` class to your question. – v.ladynev Mar 03 '16 at 14:35
  • But i fact I'm creating another entity. The save as button creates another entity in the database. – Leandro Lima Mar 03 '16 at 14:45
  • I'm still getting `could not initialize proxy - no Session` – Leandro Lima Mar 03 '16 at 17:15
  • @LeandroLima Try to solve the task by saving `Meter`, not `Company`. And lazy or eager don't have any relation to your problem – v.ladynev Mar 03 '16 at 17:17
  • Saving meter by jparepository doesn't work, It doesn't give a correct primary key. I update the question with a possible solution. – Leandro Lima Mar 03 '16 at 18:13
  • @LeandroLima I think you miss something. May be you need an other generation `strategy` or you shouldn't to use `AbstractEntity`. Or use other method than `saveAndFlush()`. – v.ladynev Mar 03 '16 at 18:30
  • Well, seems now that the problem is to fill the database externally and then try to put something there. I put some values using PostgreSQL tool and then I tried to use the software that I made, and the problem of repeated primary keys comes out again. – Leandro Lima Mar 03 '16 at 19:09
  • So if I put some values in the database using an external application and then try to use my software to insert more values it seems do not update the primary key generation. – Leandro Lima Mar 03 '16 at 19:14
  • @LeandroLima It is strange. It looks like using `hibernate_sequence` table in Oracle. Don't know, can `hibernate_sequence` be used in PostgreSQL. Maybe, you need just recreate table with valid sequence. Try to log schema update to figure out what exactly SQL Hibernate uses to create a table. – v.ladynev Mar 04 '16 at 06:20
0

The problem could be solved, as @v.ladynev said, persisting "Meter" directly, not using the method addMeter() from Company.

The solution however showed up another problem: how to create entities using a third party software and using also my application. The hibernate seemed to cash the ids generated automatically by it self without synchronize with the database, thus, the result is obvious, when I put some data with other application, and also try to insert other data from my application, it throws exception saying that id was already used.

The solution was to define using columnDefinition=bigserialin AbstractEntity to define the Id:

@Id
@Column(unique=true,nullable=false, columnDefinition = "bigserial")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;

It allows PostgreSQL to automatic fill the primary keys. However, again, it comes with a problem, the hibernate sets every relationship to not nullable.

To solve this, I used the solution proposed by solution. The other post doesn't mentioned that it is mandatory to use the name of the joincolum annotation. The code finished like this:

private void buttonSaveAsActionPerformed(java.awt.event.ActionEvent evt) {                                             
        final StringListModel<Meter> model = getListModel();
        final Meter edited = meterEditionPanel.getMeter();

        if (edited == null)
            return;

        try { 
            service.createEntity(edited);
            model.update();
            listMeters.setSelectedValue(edited.getAlias(), true);
            meterEditionPanel.setMeter(edited);
        } catch (BeansException | HibernateException ex) {
            FieldMarker.showEntityAlreadyExistsInCompanyDialog(
                    dictionary.getString("commons.title.meter"),
                    dictionary.getString("commons.text.serialNumber"), false);
            LOGGER.debug("Não foi possível atualizar o medidor: " + ex.toString());
        }
    }

In another entity:

@ManyToOne
@JoinColumn(columnDefinition = "bigint", name = "defaultTestPlan_id")
private TestPlan defaultTestPlan;
Community
  • 1
  • 1
Leandro Lima
  • 1,140
  • 3
  • 24
  • 42