How to cascade relationship?

Asked

Viewed 470 times

1

I have a class mapped with Hibernate, however, when saving an object is cascateado the whole object to the child table (what 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 believed that putting equals would be enough for the API but did not work as expected.

Does anyone know how to do it in such a way that they don’t need to check if the object exists in the bank every time the function is called?

In the example I made was 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 perform.

@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 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, as well as objects other than on the bench. I can do the check as above and add the object already with the ID bank defined. But the foreign key you have must persist ID, and those who do not, must be saved and obtained the foreign key as the Hibernate already does.

  • Attributes of child/parent objects are changed, or zeroed, if they are new? For that is the only way they can be inserted again, in my opinion.

  • Gustavo, what I hope is the behavior contrary to this. I want the data to be checked if it exists in the database and then save.

1 answer

1


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

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

Example:

Pai 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();
    }
}

Classe Filho:

@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();
    }
}

Testing:

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();
  • Felipe I am not using Entity manager Factory, so as good as I have what you gave me doesn’t work. I need something using JPA that uses Jparepository.

  • @Andersoneyrodrigues O JpaRepository uses a EntityManager. You can catch it through a custom implementation of JpaRepository. Take a look in that and in that link to know how.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.