Hibernate: orphanRemoval not working for cloned object

len :

I got problem to persist a cloned object using hibernate. When its nested child was removed the record was not deleted from database (I had put orphanRemoval = true).

As in the codes below, a clone is created with json. After the clone.removeItem(), hibernate should remove the orphan Item when session.update(clone). But it still exist when I query again. Whats wrong?

import static org.testng.Assert.assertEquals;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;


public class JacksonTest {

    SessionFactory factory;

    @BeforeClass
    public void setup() {

        StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
                .configure("hibernate.cfg.xml")
                .build();
        factory = new MetadataSources(registry)
                .addAnnotatedClass(PurchaseOrder.class)
                .addAnnotatedClass(Item.class)
                .buildMetadata()
                .buildSessionFactory();

        PurchaseOrder order = new PurchaseOrder(10,  LocalDate.now());
        order.addItem(new Item(1, "ABC", new BigDecimal("84.50")));

        try (Session session = factory.openSession()) {
            Transaction tx = session.beginTransaction();
            session.save(order);
            tx.commit();
        }
    }


    @AfterMethod
    public void shutdown() {
        try (Session session = factory.openSession()) {
            PurchaseOrder obj = this.findOrder(session, 10);
            Transaction tx = session.beginTransaction();
            session.delete(obj);
            tx.commit();
        }

        factory.close();
    }


    @Test
    public void testOrphanDelete() {

        PurchaseOrder clone;
        try (Session session = factory.openSession()) {
            PurchaseOrder obj = this.findOrder(session, 10);
            session.evict(obj);

            clone = this.jsonClone(obj);
            assertEquals(clone.getId(), 10); //passed
        }

        clone.removeItem(clone.getItems().get(0));

        try (Session session = factory.openSession()) {
            Transaction tx = session.beginTransaction();
            assertEquals(clone.getItems().size(), 0); //passed
            session.update(clone);
            tx.commit();
        }

        try (Session session = factory.openSession()) {
            PurchaseOrder obj = this.findOrder(session, 10);
            //AssertionError: expected [0] but found [1]
            assertEquals(obj.getItems().size(), 0);
        }

    }

    private PurchaseOrder findOrder(Session session, int id) {
        Query query = session.createQuery("from PurchaseOrder r "
                + "where r.id=:id");
        query.setParameter("id", id);
        return (PurchaseOrder) query.uniqueResult();
    }


    private PurchaseOrder jsonClone(PurchaseOrder order)  {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new JavaTimeModule());
        mapper.setVisibility(PropertyAccessor.ALL, Visibility.NONE);
        mapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);

        try {
            String s = mapper.writeValueAsString(order);
            return mapper.readValue(s, PurchaseOrder.class);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

    }

}

PurchaseOder.java:

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.*;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

@JsonIdentityInfo(
        generator=ObjectIdGenerators.PropertyGenerator.class,
        property="id")
@Entity
@Access(AccessType.FIELD)
public class PurchaseOrder {

    @Id
    private int id;
    private LocalDate issueDate;
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "order")
    private List<Item> items = new ArrayList<>();

    PurchaseOrder(){}

    public PurchaseOrder(int id, LocalDate issueDate) {
        this.id = id;
        this.issueDate = issueDate;
    }


    final int getId() {
        return id;
    }

    public final LocalDate getIssueDate() {
        return issueDate;
    }


    final List<Item> getItems() {
        return items;
    }

    public void addItem(Item item) {
        item.setOrder(this);
        items.add(item);
    }

    public void removeItem(Item item) {
        item.setOrder(null);
        items.remove(item);
    }
}

Item.java:

import java.math.BigDecimal;
import javax.persistence.*;

@Entity
@Access(AccessType.FIELD)
public class Item {

    @Id
    private int id;
    private String name;
    private BigDecimal price;

    @ManyToOne(fetch = FetchType.EAGER)
    private PurchaseOrder order;

    Item(){}

    Item(int id, String name, BigDecimal price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }


    final int getId() {
        return id;
    }

    final String getName() {
        return name;
    }

    final BigDecimal getPrice() {
        return price;
    }

    final PurchaseOrder getOrder() {
        return order;
    }

    final void setOrder(PurchaseOrder order) {
        this.order = order;
    }

}

hibernate.cfg.xml:

<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">org.h2.Driver</property>
        <property name="connection.url">jdbc:h2:D:/test</property>
        <property name="connection.username">sa</property>
        <property name="connection.password">sa</property>

        <property name="hibernate.default_schema">PUBLIC</property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">org.hibernate.dialect.H2Dialect</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>
        <property name="hibernate.current_session_context_class">org.hibernate.context.internal.ThreadLocalSessionContext</property>

        <!-- Drop and re-create the database schema on startup -->
         <property name="hbm2ddl.auto">update</property>

    </session-factory>

</hibernate-configuration>

Lastly, the version numbers of mine:
Java: 8 update181
Hibernate: 5.1.6
Jackson: 2.9.8
H2 database: 1.4.197

Update: The problem is only for orphan removal. The clone update or adding of Item child are working fine. As such, this test without failure:

    @Test
    public void testItemAdding() {

        PurchaseOrder clone;
        try (Session session = factory.openSession()) {
            PurchaseOrder obj = this.findOrder(session, 10);
            session.evict(obj);

            clone = this.jsonClone(obj);
        }

        clone.setIssueDate(LocalDate.of(1999, 9, 9));
        clone.addItem(new Item(2, "XYZ", new BigDecimal("182.50")));

        try (Session session = factory.openSession()) {
            Transaction tx = session.beginTransaction();
            session.update(clone);
            tx.commit();
        }

        try (Session session = factory.openSession()) {
            PurchaseOrder obj = this.findOrder(session, 10);
            assertEquals(obj.getIssueDate(), LocalDate.of(1999, 9, 9));
            assertEquals(obj.getItems().size(), 2);
            assertEquals(obj.getItems().get(1).getName(), "XYZ");
        }
   }  
Andronicus :

The object clone is not managed by jpa, because it's in detached state. You need to persist it first to see the changes done by mapping.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=160091&siteId=1