Question

I have two entities in in OneToMany Relationship:

The parent Entity:

@Entity
@Table(name = "PIANOTAGLIE")
public class PianoTaglia {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String pianoTaglia;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "pianoTaglia", cascade = CascadeType.ALL)
    private List<Taglia> taglie;

    public PianoTaglia() {
    }

    [...] Getter/Setter [...]

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        PianoTaglia other = (PianoTaglia) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }
}

And Child entity:

@Entity
@Table(name = "TAGLIE")
public class Taglia {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(length = 10, unique = true)
    private String taglia;

    @ManyToOne
    private PianoTaglia pianoTaglia;

    public Taglia() {
    }

    [...] Getter/Setter [...]

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Taglia other = (Taglia) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }
}

For manage my Entities i use this generic Dao:

public abstract class JpaDAO<E> {

    protected Class<E> entityClass;

    @PersistenceContext(unitName = "PrudiPU")
    protected EntityManager em;

    @SuppressWarnings("unchecked")
    public JpaDAO() {
        ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
        this.entityClass = (Class<E>) genericSuperclass.getActualTypeArguments()[0];
    }

    public List<E> findAll() {
        TypedQuery<E> q = em.createQuery("SELECT h FROM " + entityClass.getName() + " h", entityClass);
        return q.getResultList();
    }

    public void persist(E entity) {
        em.persist(entity);
    }

    public E getReference(Long id) {
        return em.getReference(entityClass, id);
    }      
}

Specialized for each class (this is PianoTagliaDao but TagliaDao is the same)

@Repository
public class PianoTaglieDao extends JpaDAO<PianoTaglia> {

}

When I create a PianoTaglia I keep a reference to the object with the generated ID... So i can navigate through my application and at any time i can create a Taglia. When i create a Taglia i use the reference to PianoTaglia, previusly created, in this way:

PianoTaglia pt = getPreviuslyCreatedPianoTaglia(); //this is an example
Taglia tg = new Taglia();
tg.setTaglia("XXL");
tg.setPianoTaglia(pt);
pt.getTaglie().add(tg);
taglieDao.persist(tg);
taglieDao.flush(); //i need to flush for keep generated ID
[...]

If i check the tables into DB is all ok! All the tables are well populated! But if i try to get all PianoTaglia the taglie collections are always empty:

List<PianoTaglia> pianoTagle = pianoTagliaDao.findAll();
for(PianoTaglia pt : pianoTaglie) {
    assert pt.getTaglie().isEmpty();
}

after testing i've found the solution: when i create taglia i have to keep a new reference of PianoTaglie:

PianoTaglia old = getPreviuslyCreatedPianoTaglia();
PianoTaglia pt = pianoTaglieDao.getReference(old.getId()); //getReference call the namesake method in the EntityManager
Taglia tg = new Taglia();
tg.setTaglia("XXL");
tg.setPianoTaglia(pt);
pt.getTaglie().add(tg);
taglieDao.persist(tg);
taglieDao.flush(); //i need to flush for keep generated ID
[...]

In this way when i keep the PianoTaglia Objects the taglie collections are well Populated..

My question is: Why JPA have this behaviour?

Was it helpful?

Solution

It looks like you are storing the previously created PianoTaglia and keeping it well after it's context has closed, so that it is considered unmanaged by the persistence unit. Unmanaged entities are not tracked, so any changes made are not reflected in the database. This means that the pt.getTaglie().add(tg); code isn't done on something that the entityManager is aware of.

By using the getReference or find api, you are retrieving the managed instance of the entity, so that any changes made to it are tracked by the EntityManager. You could also have replaced the getReference and pianoTaglieDao.persist(tg); line with a pianoTaglieDao.merge(old) call which will merge changes made to the old PianoTaglia back into the persistence unit. It is probably better though to use getReference or find rather than cache the unmanaged entity to help reduce overwriting with stale data. Your cached object might not reflect the latest changes made, which might then be overwriten by the merge call, and for performance, it will allow you to expand your app later on to multiple threads and servers without having to make drastic changes.

OTHER TIPS

hi I am not sure what fields do you have in the db but try to add

@JoinColumn("user_id")

for the next line

@ManyToOne
private PianoTaglia pianoTaglia;

result will be

@ManyToOne
@JoinColumn("pianoTagliaId")
private PianoTaglia pianoTaglia;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top