Fetch of children with JPA + Hibernate is not working

4

I have two classes, Terminal (which is the parent) and Portaria (child).

I'm using JPA and it's working, but it has a bug that I can not resolve.

I load a list of Terminal and when testing t.getPortarias() it is instantiated and showing the children to those who have and showing size 0 to those who do not have children, so far everything is right.

So I create a new Terminal , this new object does not instantiate t.getPortarias() gets as null , the other objects are there with the children, but the new object does not. If I close the system and open it again the new object appears with the child instantiated.

The problem is that it gives NullPointerException when testo t.getPortarias().isEmpty() because it is not loading the child object when I give persist to the object.

I have tried to use different manager in every DAO, I have tried to use the same one and also did not give. I already put lazy and eager fetch and did not work either.

Terminal Class

@Entity
public class Terminal implements Serializable {
    @Id
    @GeneratedValue
    private int terminalId;

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

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

    @Column(nullable = false)
    private String endereco;

    @Lob
    private byte[] logo;

    @OneToMany(mappedBy = "terminal")
    private List<Portaria> portarias;

Ordinance Class

@Entity
public class Portaria implements Serializable {
    @Id
    @GeneratedValue
    private int portariaId;

    @Column(nullable = false)
    private String descricao;

    @ManyToOne
    @JoinColumn(name = "terminalId", nullable = false)
    private Terminal terminal;

In this section I test whether or not I can exclude, but it sucks because List of getPortarias nor was instantiated:

public boolean canDelete(Terminal t) throws BusinessException {
    if (!t.getPortarias().isEmpty()) {
        throw new BusinessException("Não é possível excluir porque existe portarias associadas a este terminal");
    }
    return true;
}

In this section I get the object by id to delete:

public T getById(int id) {
    return getDbManager().find(entityClass, id);
}

How do I make fetch of children work?

    
asked by anonymous 12.01.2015 / 20:38

1 answer

2

Summarizing the comments in an answer. You have fallen into a common problem with bidirectional relationships.

Following is a Wikibooks grafting

  

Object corruption, one side of the relationship is not updated after updating the other side

     

A common problem with bi-directional relationships is the application updates one side of the relationship, but the other side does not get updated, and becomes out of sync. In JPA, as in Java in general, it is the responsibility of the application or the object model to maintain relationships. If your application adds to one side of a relationship, then it should add to the other side.

Doing a free translation:

  

Object corruption, one side of the relationship is not updated after updating the other side

     

A common problem with bidirectional relationships is when the application updates one side of the relationship, but the other side is not updated and out of sync. In JPA, as in Java in general, it is the responsibility of the application or object model to maintain relationships (emphasis mine). If your application adds to one side of the relationship, then it should add to the other side.

In short, when you persist an object with your JPA provider it happens to exist in the context of persistence. Think of the persistence context as an area of memory with the objects you are manipulating; the persistence context is between the application and the database.

When you persist a Terminal it becomes part of your persistence context. When you persist the various Portarias with a reference to that Terminal they also become part of the persistence context.

Eventually your provider will write the state of the persistence context in the database, that is, execute operations of insert , delete , update , etc.

In your case, if you only associate Terminal with Portaria , on the side of the database everything will happen correctly. Since fk is in the portaria table, associating only the Terminal with the concierge is enough for the fk to be entered correctly.

The same does not occur with the Terminal object present in the persistence context. The Terminal object managed by it has no way of knowing that new Portarias related to it has been persisted unless you explicitly do so.

Solutions

1. Manipulate both sides of the relationship explicitly

Forget what you know about the database. If you were to update an object-oriented model without the help of tools you would end up having to update both sides of the relationship:

portaria.setTerminal(terminal);
terminal.getPortarias().add(portaria);

2. Let your model take care of it.

We can enrich the domain to handle bidirectional relationships:

class Terminal {

    // ...

    @OneToMany(mappedBy = "terminal")
    private List<Portaria> portarias;

    public void adicionarPortaria(Portaria p) {
        this.portariais.add(p);
        if (portaria.getTerminal() != this) {
            portaria.setTerminal(this);
        }
    }

    // ...
}

And in class Portaria :

class Portaria {

    // ...

    @ManyToOne
    @JoinColumn(name = "terminalId", nullable = false)
    private Terminal terminal;

    public void setTerminal(Terminal t) {
        this.terminal = t;
        if (!t.getPortarias().contains(this)) {
            t.getPortarias().add(this);
        }
    }

    // ...
}

3. Assume the persistence context has been corrupted, look up the bank information.

This is what we did with the refresh method:

entityManager.persist(portaria);
entityManager.refresh(terminal);

Basically what we are doing here is saying to the JPA provider: "Persist the new gateway (with fk to terminal)" and refresh the contents of the terminal according to what has been persisted in the bank. p>

This is apparently the simplest solution, but it is also the least recommendable due to a number of problems:

  • % unnecessary% are made.
  • The JPA provider is free to "reorder" operations in any way they wish, as well as delay writing at the bank. So we can not always count on the state of the bank.
  • In some environments there is a second cache level. In these environments you may end up recovering a corrupted object from the cache, having to unhitch ( selects ) from the cache before refreshing the state.
  • 4. Avoid bidirectional relationships

    This is a common maxim among more experienced developers. We do not always need bi-directional relationships. When possible it is worth simplifying the template. Do you really need to navigate from evict to Terminal to Portarias to Portaria ? Is the complexity of introducing a two-way relationship really worth it compared to the complexity of doing custom queries to get the other side? These are fair questions that must be asked before introducing a bidirectional relationship.

        
    13.01.2015 / 16:31