Issue #2917: Inverse OneToMany Mapping and EmbeddedId

Type: Feature RequestVersion: 2.9.0_04Priority: NormalStatus: FixedReplies: 6
#1

hello support,

what is wrong?

---

package debug;

import static java.lang.System.out;
import static javax.persistence.GenerationType.IDENTITY;
import static javax.persistence.Persistence.createEntityManagerFactory;

import java.io.File;
import java.util.List;

import javax.persistence.Embeddable;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;

/**
 * @author Stanislav Jakuschev 14.08.2024
 * 
 *         objectdb-2.9.0_03
 * 
 *         issue: Inverse @OneToMany(mappedBy = "id.ab") into @EmbeddedId fails!
 * 
 *         question: Is it possible to map Entitys inverse OneToMany if this are
 *         a part of EmbeddedId?
 */
public class OdbInverseEmbIdMapping {

    public static void main(String[] args) {

        String dbName = OdbInverseEmbIdMapping.class.getSimpleName() + ".odb";

        new File(dbName).delete();
        new File(dbName + "$").delete();

        EntityManager em = createEntityManagerFactory(dbName).createEntityManager();

        em.getTransaction().begin();

        A a = new A();
        B b = new B();
        AB ab = new AB(a, b);
        em.persist(a);
        em.persist(b);
        em.persist(ab);

        em.getTransaction().commit();
        em.getTransaction().begin();

        em.persist(new C(new CId(ab, 1), 1));
        em.persist(new C(new CId(ab, 2), 1));

        em.getTransaction().commit();

        em.clear();

        out.println(em.createQuery("select a from A a", A.class).getResultList());
        out.println(em.createQuery("select b from B b", B.class).getResultList());
        List abs = em.createQuery("select ab from AB ab", AB.class).getResultList();
        out.println(abs);
        out.println(em.createQuery("select c from C c", C.class).getResultList());

        out.println(abs.get(0).cs);

    }

    @Entity
    public static class A {

        @Id
        @GeneratedValue(strategy = IDENTITY)
        public int id;

        @OneToMany(mappedBy = "a")
        public List abs;

        public A() {
        }

        @Override
        public String toString() {
            return "A [id=" + id + ", abs.size=" + abs.size() + "]";
        }

    }

    @Entity
    public static class B {

        @Id
        @GeneratedValue(strategy = IDENTITY)
        public int id;

        @OneToMany(mappedBy = "b")
        public List abs;

        public B() {
        }

        @Override
        public String toString() {
            return "B [id=" + id + ", abs.size=" + abs.size() + "]";
        }

    }

    @Entity
    public static class AB {

        @Id
        @GeneratedValue(strategy = IDENTITY)
        public int id;

        @ManyToOne()
        public A a;

        @ManyToOne()
        public B b;

        @OneToMany(mappedBy = "id.ab")
        public List cs;

        public AB() {
        }

        public AB(A a, B b) {
            this.a = a;
            this.b = b;
        }

        @Override
        public String toString() {
            return "AB [id=" + id + ", a=" + a + ", b=" + b + "]";
        }

    }

    @Entity
    public static class C {

        @EmbeddedId
        private CId id;

        public int y;

        public C() {
        }

        public C(CId id, int y) {
            this.id = id;
            this.y = y;
        }

        @Override
        public String toString() {
            return "C [id=" + id + ", y=" + y + "]";
        }

    }

    @Embeddable
    public static class CId {

        @ManyToOne()
        public AB ab;

        public int x;

        public CId() {
        }

        public CId(AB ab, int x) {
            this.ab = ab;
            this.x = x;
        }

        @Override
        public String toString() {
            return "CId [ab.id=" + ab.id + ", x=" + x + "]";
        }

    }

}
 

---

13:32:52.428 [main] DEBUG org.jboss.logging - Logging Provider: org.jboss.logging.Log4j2LoggerProvider
13:32:52.558 [main] INFO org.hibernate.jpa.boot.internal.PersistenceXmlParser - HHH000318: Could not find any META-INF/persistence.xml file in the classpath
13:32:52.560 [main] DEBUG org.hibernate.jpa.HibernatePersistenceProvider - Located and parsed 0 persistence units; checking each
13:32:52.562 [main] DEBUG org.hibernate.jpa.HibernatePersistenceProvider - Found no matching persistence units
[A [id=1, abs.size=1]]
[B [id=1, abs.size=1]]
[AB [id=1, a=A [id=1, abs.size=1], b=B [id=1, abs.size=1]]]
[C [id=CId [ab.id=1, x=1], y=1], C [id=CId [ab.id=1, x=2], y=1]]
Exception in thread "main" [ObjectDB 2.9.0_03] javax.persistence.PersistenceException
id.ab is not found in type C (mapped by [Single] field debug.OdbInverseEmbIdMapping$AB.cs null) (error 305)
    at java.lang.String.valueOf(String.java:2994)
    at java.io.PrintStream.println(PrintStream.java:821)
    at debug.OdbInverseEmbIdMapping.main(OdbInverseEmbIdMapping.java:67)
Caused by: com.objectdb.o.UserException: id.ab is not found in type C (mapped by [Single] field debug.OdbInverseEmbIdMapping$AB.cs null)
    at com.objectdb.o.MSG.a(MSG.java:64)
    at com.objectdb.o.IMQ.<init>(IMQ.java:78)
    at com.objectdb.o.UMR.A(UMR.java:511)
    at com.objectdb.o.ENT.loadInverse(ENT.java:1565)
    at com.objectdb.o.IVP.h(IVP.java:151)
    ... 5 more

---

Thank You!

#2

There are 2 issues in this test case.

1. To use mappedBy you have to provide the target type using a generic List parameter:

@OneToMany(mappedBy = "a")
public List abs;

or using a targetEntity attribute:

@OneToMany(targetEntity = AB.class, mappedBy = "a")
public List abs;

2. mappedBy expects a simple field name, mappedBy = "id.ab" was not supported.

 

Following your post, build 2.9.0_04 includes an attempt to support path expressions such as "id.ab". Please try it.

ObjectDB Support
#3

to 1:

Generics like <AB> was gone after paste java code into Reply-editor and subsequent editing. Original is attached. Sorry.

to 2:

Path expressions are recognized now, but entitys from EmbeddedId are not mapped.

thank you

#4

Please try build 2.9.0_05.

ObjectDB Support
#5

Now it works, thank you very much.

But this extension has a negative impact on the iteration performance via the primitive data types of an EmbeddedId.

 

benchmark (huge time scaled sensor data set):

v 2.9.0_04 (fast)

find_1_N_1Y() PT19.06S -0.81G/0.68G
find_1_N_1Y() PT24.17S -0.13G/0.73G
find_1_A_1Y() PT37.456S -0.62G/0.49G
find_1_A_1Y() PT46.46S +0.26G/0.90G
find_A_1_1D() PT0.392S +0.15G/1.04G
main PT2M8.525S -1.12G/1.06G

v 2.9.0_05 (slow)

find_1_N_1Y() PT20.812S -1.10G/0.46G
find_1_N_1Y() PT26.748S +0.29G/0.86G
find_1_A_1Y() PT1M44.504S -0.87G/0.21G
find_1_A_1Y() PT2M14.852S +0.91G/1.11G
find_A_1_1D() PT1.302S +0.14G/1.25G
main PT4M49.26S -0.58G/1.29G

 

EmbeddedId:

@Embeddable
public class ValueId {
    int platformSensor;
    short year;
    byte month;
    byte day;
    byte hour;
}


Query:

select ps.platform.id, ps.sensor.id, v.id.year, v.id.month, v.id.day, v.id.hour, v.value from PlatformSensor ps, Value v where ps.sensor.name like ?1 and v.id.platformSensor=ps.id and v.id.year=?2 and v.id.month=?3 and v.id.day=?4

 

The same query on the same data is executed significantly faster than in versions < 2.9.0_05.

Do you have any idea why this might be?

#6

The same query with the same data is executed significantly faster with versions < 2.9.0_05.

Sorry

#7

Can you share a sample database that demonstrates the performance gap in running this query?

ObjectDB Support

Reply