EntityManager JPA or JDO impl and different behavior

#1

Hi,
I have an issue with the EntityManager. My persistance XML looks like:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

<persistence-unit name="xPU" transaction-type="RESOURCE_LOCAL">
   <provider>com.objectdb.jpa.Provider</provider>
   <properties>
     <property name="javax.persistence.jdbc.url" value="objectdb/db/x.odb"/>
     <property name="javax.persistence.jdbc.user" value="admin"/>
     <property name="javax.persistence.jdbc.password" value="admin"/>
   </properties>
</persistence-unit>

</persistence>

Now in our project we use spring to start everything. If I do:

System.out.println("EntityManager Name: " + em.getClass().getName());

The result is: com.objectdb.jpa.EMImpl
However if I create a basic test project, NO spring involved. Same
persistance xml in my META-INF, startup using:

emf = Persistence.createEntityManagerFactory("xPU");
em = emf.createEntityManager();

and run the same command, I get: com.objectdb.jdo.PMImpl
Whats going on here? Is the entityManager not controlled by the
persistance.xml or is something in the spring is doing something I
don't understand? Or have I missed something?

The reason I ask this is, I have an entity class that contains another
entity, and that entity holds a Map. If I retrieve the initial object
using "select t from someobject t" then everything comes back
correctly. However if I use em.find(SomeObject.class, id) the map is
NOT populated. I created a simple test to demonstrate this, however in
the simple test it all works fine, and the only difference I can find
is the entityManager thats being used. BUT I can't seem to force the
unit test to use the JPA entity manager which I believe it should be
using.

Thanks

#2

It is strange. But actually the test behavior is expected.

com.objectdb.jdo.PMImpl extends com.objectdb.jpa.EMImpl and EMImpl is never instantiated directly, PMImpl is used for both JPA and JDO.

Are you sure that in Spring you really see an EMImpl instance (which is not PMImpl that extends EMImpl)?

 

ObjectDB Support
#3

Well, Not exactly. It's hard to say what the EntityManager is because it's proxied by spring but from digging into the objects in debug I can get see that somewhere inside  nativeEntityManagerFactory=com.objectdb.jpa.EMF@5b224686 and I assumed that if the EMF is coming from the jpa package the EM would too, but may have been wrong.

That still doesn't change the fact that in my project the em.find(type, id) doesn't populate the nested map and in the test project it does.

If I use a query instead of find it populates the map in both.

Because my project uses Spring to create the entity manager:

<bean id="coreEntityManagerFactory"
                class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitManager" ref="pum" />
    <property name="persistenceUnitName" value="xPU" />
 </bean>

and in the test I load it directly I'm assuming that it's not the same entity manager implementation that is being used.

Thanks,

Eitan

#4

ObjectDB uses com.objectdb.jpa.EMF for JPA and com.objectdb.jdo.PMF for JDO, but com.objectdb.jdo.PMImpl for both. Anyway, this is probably not related to the the difference between your Spring application and the test.

More information is needed, and particularly how exactly that map is defined and with which fetch type.

If the problem can only be demonstrated in Spring you may try generating a simple test case by modifying the Spring GuestBook tutorial application.

 

ObjectDB Support
#5

Hi,

 

Attached is a simple project showing the error I've been getting. The unit test shows how when a query is performed on the holder, the attributeName display values map is populated, whereas, when doing a find call the attributeName is found but no values are returned in the display values map.

 

Thanks,

Eitan

#6

Thank you for the test project. Similar code works well in the following console test application:

import java.util.*;

import javax.persistence.*;

@Entity
public class T592 {
   
    public static void main(String[] args) {
       
        EntityManagerFactory emf =
            Persistence.createEntityManagerFactory(
                "objectdb:$objectdb/db/test.tmp;drop");

        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        Holder holder = new Holder();
        holder.listOfNames.add(new AttributeName("hello"));
        em.persist(holder);
        em.getTransaction().commit();
        em.close();
       
        em = emf.createEntityManager();
        holder = em.createQuery("SELECT h FROM Holder h",
            Holder.class).getSingleResult();
        em.close();
        System.out.println(holder.getListOfNames().get(0).getDisplayValues());

        em = emf.createEntityManager();
        holder = em.find(Holder.class, 1);
        em.close();
        System.out.println(holder.getListOfNames().get(0).getDisplayValues());

        emf.close();
    }
   
   
    @Entity
    static class Holder {
        long id = 1;
        List<AttributeName> listOfNames = new ArrayList<AttributeName>();

        @Id
        public long getId() {
            return id;
        }
       
        public void setId(long id) {
            this.id = id;
        }
       
        @OneToMany(cascade=CascadeType.PERSIST, fetch=FetchType.EAGER)
        public List<AttributeName> getListOfNames() {
            return listOfNames;
        }
       
        public void setListOfNames(List<AttributeName> listOfNames) {
            this.listOfNames = listOfNames;
        }
    }

    @Entity
    static class AttributeName {
        String name = null;
        Map<String, String> displayValues = new HashMap<String, String>();

        AttributeName() {
        }
       
        AttributeName(String name) {
            this.name = name;
            displayValues.put("en", name);
        }

        @OneToMany(cascade=CascadeType.ALL,
                   fetch=FetchType.EAGER, orphanRemoval=true)
        //@ElementCollection(fetch=FetchType.EAGER)
        public Map<String, String> getDisplayValues() {
            return displayValues;
        }
       
        public void setDisplayValues(Map<String, String> displayValues) {
            this.displayValues = displayValues;
        }
    }
}

The problem could be some sort of integration problem of Spring, genericdao and ObjectDB, but if it is a pure ObjectDB problem maybe you can show it by modifying the above test.

ObjectDB Support
#7

If you can covert your sample project from a Maven project into a simple Eclipse project that doesn't use Maven it could be very helpful, since it will enable getting into ObjectDB with the debugger (by replacing the objectdb jar with dependency on the ObjectDB development project, which doesn't seem to work with a Maven project).

ObjectDB Support
#8

Hi,

Sorry for the late response.

And don't see why the current project wouldn't work in eclipse together with the Objectdb source project.

Did you do the following:

1. run "mvn eclipse:eclipse" on the test project

2. import the test project to eclipse

3. In eclipse, change your java build path to remove the M2_REPO dependency for objectdb jar and instead add a dependency to the objectdb project in eclipse.

This should allow you to debug the test project with objectdb.

 

In addition yesterday I had another case where I had an object holding another object which was holding a map of <string, someObject> with eager fetch annotation and the map values were still lazy loaded(using the latest objectdb 2.3.4_03). I had to load them manually in the transaction due to lack of time but would like to get to the bottom of LAZY/EAGER loading of maps.

 

In addition, non related. Since upgrading to 2.3.4_03 I can no longer run objectdb server nor the explorer on Windows 7 64bit but it does work on linux.

 

I'm getting this error:

Caused by: com.objectdb.o._PersistenceException: Failed to write to file 'd:\temp\com\contextspace\datamodel\party\IDHolder.class'

 

and it should never try and write on d drive as everything is running on c drive.

 

Thanks,

Eitan

#9

Build 2.3.4_04 should fix the new "Failed to write to file" error.

Thank you for the "mvn eclipse:eclipse" tip. Previously I imported the project using m2eclipse and then replacing the jar with dependency on the ObjectDB project didn't work.

ObjectDB Support
#10

Please try build 2.3.4_05.

ObjectDB Support
#11

Thank you very much. This version works :-)

#12

Hi,

Extending the datamodel a bit more exposed another problem with lazy loading. In the attached project it's demonstrated how, by having a Map that it's key is an Entity and the values are a List of another entity, the list is lazy loaded even though it is annotated as EAGER.

 

test2 in ExampleTest prints the Before and After reloading from the DB.

Thanks,

Eitan

#13

Your new test uses lists as direct values of maps.

This is supported by ObjectDB as an extension of JPA but rarely used.

Please try build 2.3.4_07 that should enable eager fetch in this case.

ObjectDB Support
#14

Hi,

Thanks for the quick investigation. That has indeed fixed that issue. However there appear to be two more.

First, as we are dealing with maps, the "equals" and "hashcode" methods on the key object have to be overridden, so that "containsKey" and "get" methods work correctly. The issue with this is, when "find" is called, the object is being lazy loaded. So if the "equals" or "hashcode" method use more than the database id for generation or comparison, it all fails horribly. Have a look at the output from the second junit test, you will see heaps of calls like:

AttributeName EQUALS method called for <attributename name="null" values="" id="11"/> against: <attributename name="null" values="" id="10"/>

where the equals method has been called, but the attributename appears to have been lazy loaded. 

For the purposes of continuing, I have set my "equals" method in attribute name to look only at the database id.

 

Second issue. The docs at: http://www.objectdb.com/api/java/jpa/OneToMany state: "When the collection is a java.util.Map, the cascade element and the orphanRemoval element apply to the map value." This appears to mean that if I have cascade=CascadeType.ALL on a OneToMany map, then the cascade ONLY applies to the values, NOT to the key. This means all keys MUST be created prior to trying to persist the map.

So I set the annotation on Holder.java at line 92 to have CascadeType.ALL. The test created the AttributeNames via the dao prior to trying to persist. The list of OtherObject is NOT persisted prior to the call. When I call persist, it fails with exception about trying to reuse a primary key for AttributeName. So I removed the CascadeType.All from Holder and persisted the OtherObjects prior to calling update on Holder and then it all works. However that then means any sub parts of OtherObjects (ie the Map<string,String>) don't get updated when they are changed (this is what the unit test is setup testing at the mo).

To reproduce the error, try replacing the annotation at line 92 in Holder.java, and removing the persisting of OtherObject in the unit test (lines 136-139 for ExampleTest.java).

I have attached the updated Maven project. Please let me know if I have missed something, or miss read the docs

Thanks

Paul.

#15

Please create a new forum thread for each one of these new problems.

Each problem should be demonstrated by a separate test case. The test case should be as simple as possible (e.g. one entity with a map, another entity for the key - if it is sufficient to demonstrate the problem, no DAO, Spring, etc.). Please follow the format of the sample test in the posting instructions. Static inner classes should be used for the entity classes with simple names (e.g. RootEntity and KeyEntity).

It may also help if you describe your expectations more clearly. In the second problem - do you expect disabling cascading through keys (which indeed may be the right thing) to solve the problem? What do you expect in the first problem?

Following the posting instructions may help in providing support and fixing issues more quickly.

Thank you for your cooperation.

ObjectDB Support
#16

As far as I'm concerned this is still part of the same problem.  It still relates to apparent lazy loading issues. But if you really want a new forum issue, I'll create a new one.

#17

From your point of view everything that is related to ObjectDB may be seen as the same issue.

But your last post discusses 2-3 new issues (that may need 2-3 new threads) after 2 other issues that have been reported earlier in this thread and have already been fixed.

"Loading issues" is far too general since ObjectDB does mainly 2 things persisting and loading.

Actually this thread had to be closed after post #4 because all the posts since #5 are unrelated to the original subject.

ObjectDB Support

Reply