How to cascate relationship?

1

I have a class mapped with hibernate, however, when saving an object the whole object is cascaded to the child table (which is expected). The problem is that equal objects are saved instead of just relating the child object to the parent object.

@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@Fetch(value = FetchMode.SELECT)
private List<Atributo> atributos;

I thought putting equals would be enough for the API, but it did not work as expected.

Does anyone know how to do this so that I do not need to check if the object exists in the database every time the function is called?

In the example I made it looks like this:

@Entity
public class TestePai implements Serializable {

    @Id
    @GeneratedValue
    private Long ID;
    String name;
    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    TesteFilho testefilho;
}

@Entity
public class TesteFilho implements Serializable {

    @Id
    @GeneratedValue
    private Long ID;

    String name;
}

I used a task to execute.

@Component
public class TesteScheduller {

    private static final Logger log = LoggerFactory.getLogger(UserAdminConfig.class);

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");

    @Autowired
    TestePaiRepository testePaiRepository;

    @Autowired
    TesteFilhoRepository testeFilhoRepository;

    @Scheduled(initialDelay = 2000L, fixedRate = 24 * 60 * 60 * 1000L)
    public void teste() {
        log.trace("\n\n********************************************************************\n\n");
        log.trace("Start TesteScheduller in: {}", dateFormat.format(new Date()));
        TestePai pai = new TestePai();
        TesteFilho filho = new TesteFilho();
        if (this.testeFilhoRepository.findOneByName(filho.getName()) != null) {
            filho = this.testeFilhoRepository.findOneByName(filho.getName());
        }
        pai.setTestefilho(filho);
        log.trace("Pai 1:\n" + pai);
        testePaiRepository.saveAndFlush(pai);
        log.trace("\n\n********************************************************************\n\n");
        pai = new TestePai();
        pai.setName("Bom dia");
        filho = new TesteFilho();
        if (this.testeFilhoRepository.findOneByName(filho.getName()) != null) {
            filho = this.testeFilhoRepository.findOneByName(filho.getName());
        }
        pai.setTestefilho(filho);
        log.trace("Pai 1:\n" + pai);
        testePaiRepository.save(pai);
        testePaiRepository.saveAndFlush(pai);
    }
}

This returns me the following error:

org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.andersoney.teste.model.Teste.TesteFilho; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.andersoney.teste.model.Teste.TesteFilho
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:299)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:503)
    at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
    at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:209)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
    at com.sun.proxy.$Proxy95.saveAndFlush(Unknown Source)
    at com.andersoney.teste.scheduller.TesteScheduller.teste(TesteScheduller.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)

The main challenge in the real situation is that I will apply what is discovered here: the object will have many children, a list ManytoMany , and in this list there will be both objects that exist, and objects other than the database. I can do the check as above and add the object already with the% set% of the bank. But the foreign key must be persisted for those who have ID , and those that you do not have, the foreign key like Hibernate must already be saved and fetched.

    
asked by anonymous 18.12.2017 / 17:43

1 answer

1

You can use the <T> T merge(T entity) method instead of void persist(java.lang.Object entity) . It is used to bind entities that are in the detached state (entities that may or may not exist in the database and are not "known" by EntityManager ) to EntityManager .

It is important to note that this search for the database will only be performed if a @Id is informed.

Example:

Parent Class:

@Entity
public class Pai {

    @Id
    @GeneratedValue
    private long id;

    @Column
    private String nome;

    @ManyToMany(cascade = { 
        CascadeType.PERSIST, 
        CascadeType.MERGE
    })
    private List<Filho> filho = new ArrayList<>();

    //getters e setters omitidos

    public final void addFilho(Filho filho) {
        this.filho.add(filho);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Pai)) return false;
        Pai other = (Pai) o;
        return getId() == other.getId();
    }

    @Override
    public int hashCode() {
        return 31;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Pai [id=").append(id).append(", nome=").append(nome).append("]");
        return builder.toString();
    }
}

Son Class:

@Entity
public class Filho {

    @Id
    @GeneratedValue
    private long id;

    @Column
    private String nome;

    //getters e setters omitidos

    @Override
    public int hashCode() {
        return 31;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Filho)) return false;
        Filho other = (Filho) o;
        return getId() == other.getId();
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Filho [id=").append(id).append(", nome=").append(nome).append("]");
        return builder.toString();
    }
}

Test:

EntityManagerFactory emf = Persistence.createEntityManagerFactory("persistence-unit");
EntityManager em = emf.createEntityManager();

em.getTransaction().begin();

Filho f1 = new Filho(); //Entidade no estado new
f1.setNome("Filho 1");

Filho f2 = new Filho(); //Entidade no estado new
f2.setNome("Filho 2");

Pai p1 = new Pai();     //Entidade no estado new
p1.setNome("Pai 1");
p1.addFilho(f1);
p1.addFilho(f2);

//Persiste tanto pai quanto filhos
em.persist(p1);
em.getTransaction().commit();

em.clear();         //Remove todas as entidades do em deixando-as "detached"

em.getTransaction().begin();

Filho f3 = new Filho(); //Entidade no estado new
f3.setNome("Filho 3");

//Entidade no estado detached
//É a mesma entidade que o Filho 1, porém representado por outro
//objeto que o EntityManager ainda não conhece
Filho f4 = new Filho(); 
f4.setId(f1.getId());
f4.setNome("Filho 1");

Pai p2 = new Pai();
p2.setNome("Pai 2");
p2.addFilho(f2);    //Aqui ele dará um SELECT no BD durante o flush
p2.addFilho(f3);    //Add filho no estado new
p2.addFilho(f4);    //Aqui ele dará outro SELECT no BD durante o flush

em.merge(p2);

em.getTransaction().commit();

em.createQuery("SELECT p FROM Lifecycle$Pai p ", Pai.class)
    .getResultList()
    .forEach(System.out::println);

System.out.println("\n\n");

//Pode ver que não haverá filhos duplicados
em.createQuery("SELECT f FROM Lifecycle$Filho f ", Filho.class)
    .getResultList()
    .forEach(System.out::println);


em.close();
emf.close();
    
19.12.2017 / 22:12