(This is a simplification of the real problem)
Let's start with the following little class:
#Entity
class Test {
Test(int id, String name) {
this.id = id;
this.name = name;
}
#Id
private int id;
#Column
private String name;
#Override
public int hashCode() {
return id;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof Test) {
return id == ((Test) obj).id;
}
return false;
}
}
If we execute the following, no exception occurs:
EntityManagerFactory factory = Persistence.createEntityManagerFactory("local_h2_persistence");
EntityManager theManager = factory.createEntityManager();
EntityTransaction t = theManager.getTransaction();
Test obj1 = new Test(1, "uno");
tA.begin();
AtheManager.persist(obj1);
AtheManager.persist(obj1); // <-- No exception
tA.commit();
I guess the second call is ignored, or maybe the object is saved to the DB again. The thing is there is no problem in saving the same entity twice. Now let's try the following:
EntityManagerFactory factory = Persistence.createEntityManagerFactory("local_h2_persistence");
EntityManager theManager = factory.createEntityManager();
EntityTransaction t = theManager.getTransaction();
Test obj1 = new Test(1, "uno");
Test obj1_ = new Test(1, "uno");
tA.begin();
AtheManager.persist(obj1);
AtheManager.persist(obj1_); // <-- javax.persistence.EntityExistsException: a different object with the same identifier value was already associated with the session
tA.commit();
What? How could it possibly be relevant that the object is in a different memory location? Somehow it is and the code throws an exception.
How can I make the second example work just like the first?
I am just rewriting what #jb-nizet wrote in the comments, which feels like the answer to me:
Hibernate doesn't use ==. It simply does what you're telling it to do.
persist's contract is: associate this object with the session. If it's
already associated to the session, it's a noop. If it isn't, it is
associated to the session to be inserted in the database later. If
what yo want to do is make sure the state of this object is copied to
a persistent entity, and give me back that persistent entity, then
you're looking for merge().
So the solution was to just use
AtheManager.merge(obj1);
instead of
AtheManager.persist(obj1);
In first case, you save the same object twice, which is allowed.
But in second case, you save two different object to database, but both has the same primary key. It is database constraint violation.
In the first example you pass a reference to an object to save it and in the second call you pass exactly the same reference; they both point to the same object in memory.
However, in the second example you allocated two objects with two new calls which creates the objects at two different memory addresses; they are two different objects. The first reference points to some other memory address then the second object's reference. If you tried this in the second example it would return false: obj1 == obj1_
Related
I am conducting some Neo4J tests and running into the following peculiar problem. I created a small model which I'm intending to use with OGM. The model has a superclass Entity and a child class Child. They're both in package persistence.model. Entity has the required Long id; with matching getId() getter.
public abstract class Entity {
private Long id;
public Long getId() {
return id;
}
}
#NodeEntity
Child extends Entity {
String name;
public Child() {
}
}
Creating Child objects and persisting them through OGM works fine. I'm basing myself on the examples found in the documentation and using a Neo4jSessionFactory object, which initialises the SessionFactory with the package persistence.model. The resulting database contains objects with proper ID's filled in.
The problem arises when I try to fetch a Child for a given ID. I'm trying it with three methods, using two connection systems (bolt and ogm):
boltSession.run("MATCH (a:Child) WHERE id(a) = {id} RETURN a", parameters("id", childId));
ogmSession.query("MATCH (a:Child) WHERE id(a) = $id RETURN a", params);
ogmSession.load(Child.class, childId, 1);
The first two methods actually return the correct data. The last one returns a null value. The last one, using OGM, has some obvious benefits, and I'd love to be able to use it properly. Can anyone point me in the right direction?
In your test code you are doing a lookup by id of type int.
private int someIdInYourDatabase = 34617;
The internal ids in Neo4j are of type Long.
If you change the type of the id to long or Long then it will work.
private long someIdInYourDatabase = 34617;
I'm using Hibernate with the JPA API in Java and I'm running the following test:
public void test1()
{
A a = new A();
a.setValue(1);
getDao().store(a);
a.setValue(2);
A loaded = getDao().load(a.getId());
assertEquals(1, loaded.getValue());
}
I am expecting the test to pass because the second part of the code with the load call may be called by a different method that loads the object for a different purpose. I am expecting the state of the object to reflect the state of the database and this is not the case here!
So I found out that the reason this happens is that the persistence context is caching the object unless we detach it. So the way I implemented the store method is:
void store(T object)
{
EntityManager em = getEntityManager();
em.getTransaction().begin();
if (object.getId() == 0)
{
em.persist(object);
}
else
{
em.merge(object);
}
em.getTransaction.commit();
em.detach(object); // Detaches this very object from 1st level cache so that it always reflects DB state
}
public T load(long id)
{
return getEntityManager().find(getPersistentClass(), id);
}
The detach call made the test pass but since then I have a different problem: When I'm storing an entity that contains a reference to a different object and a getter which is using that referenced object is annotated to be validated the validation fails because the persistence context (or the Hibernate session) is passing a different object to the validator (a copy which is not having the same state). Let me give you an example:
class B
{
private int value;
#Transient
private C c = new C(); // Shall not be persisted
#Size(min = 1)
public String getName()
{
return c.getName();
}
public void setName(String name)
{
c.setName(name);
}
}
public void test2()
{
B b = new B();
b.setName("name");
getDao().store(b); // Includes detach, as above
// Validation has passed. c.name was "name".
getDao().store(b);
// Validation has failed. c.name was empty.
}
So c.name was empty in the second attempt although I haven't changed the object. The only thing inbetween is the detach() call. If I remove it it test2 will pass but test1 will fail again.
What kind of magic is going on here? How do I fix this?
A simple class with an integer field:
#Entity
#Cacheable
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#Table(name = "myObject")
public class MyObject
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(columnDefinition = "int default 0")
#Index(name = "refCount")
private int refCount;
public int getRefCount(){ return refCount; }
}
Objects are fetched from the database using a simple Utility method:
Session session = SessionFactoryUtil.getCurrentSession();
Transaction tx = session.beginTransaction();
criteria.setFetchSize(1);
T object = (T) criteria.uniqueResult();
// I tried to add this line, but it made no difference
Hibernate.initialize(object);
tx.commit();
return object;
The problem is the following:
Shortly after fetching this object, I am calling the getRefCount method. At that point I encounter the following exception:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:164)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:285)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185)
at mypackage.MyObject_$$_javassist_1.getRefCount(MyObject_$$_javassist_1.java)
My hibernate configuration file (i.e. hibernate.cfg.xml) contains the following property:
<property name="hibernate.current_session_context_class">thread</property>
What I don't understand:
If this would happen to a collection, then I would just add the fetch = FetchType.LAZY annotation. But this simple int field is not a join. Why would an int ever be wrapped inside a Proxy in the first place ?
I tried to add the Hibernate.initialize(object); line, but it made no difference at all.
I also experimented with the hibernate.current_session_context_class="managed" setting. After which I had to start and stop all sessions manually. I opened it at every fetch and closed it in a finally block. But that also made no difference.
This is one of my first Hibernate projects. I'm starting to wonder if I should open a transaction before calling getters on hibernate objects.
I'm not using Spring, just Hibernate.
EDIT: actually there is a 2nd object
Actually there is a parent object (which I initially thought was not important). This Parent object contains a link to the MyObject
#Entity
#Cacheable
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#Table(name = "parentObject")
public class ParentObject
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// link
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#ElementCollection(targetClass = MyObject.class)
#JoinColumn(name = "myObjectId")
private MyObject myObject;
public MyObject getMyObject(){ return myObject; }
}
What happens is:
a Parent object gets fetched
parent.getMyObject() is called to get a MyObject instance
This MyObject instance is in fact a proxy without any fields on it.
As soon as I call a method on this MyObject instance, I get the LazyInitializationException
When I fetch my objects I make sure a session exists and a transaction is created. But after the fetching I immediately close the transaction.
I am not creating a transaction when I'm calling the getMyObject() or when calling the getters. I guess that's the problem. I'll test if that makes a difference.
EDIT 2:
It turns out that I indeed need to call the getters inside a transaction. But that in itself is not enough.
A second problem is that the Parent object was fetched in a transaction that was already committed. As a result, the proxy object is no longer bound to an event. I guess that's what they call a "detached object". (lol, I'm just learning as we go here.)
I had to "reattach" this object by calling the Session#update(proxy) method. Now finally I can call the getter without exceptions.
// uses a transaction internally
Parent parent = MyHibernateUtil.fetch(Parent.class, ...);
MyObject object = parent.getMyObject();
...
// create a new transaction
Session session = SessionFactoryUtil.getCurrentSession();
Transaction tx = session.beginTransaction();
// reattach the object
SessionFactory.getCurrentSession().update(myObject);
int count = myObject.getRefCount();
tx.commit();
But what I learned from this issue is that I probably use transactions the wrong way. I guess I should make longer transactions that contain both the fetches and the calls to the getters. Right ?
I suppose whole object(MyObject in your case) is proxied. Could you call getId instead of getRefCount() ?
Try calling getId function before closing the transaction. Don't know what will happen but just a suggestion.
Can somebody explain this behaviour?
Given an Entity MyEntity below, the following code
EntityManagerFactory emf = Persistence.createEntityManagerFactory("greetingPU");
EntityManager em = emf.createEntityManager();
MyEntity e = new MyEntity();
e.setMessage1("hello"); e.setMessage2("world");
em.getTransaction().begin();
em.persist(e);
System.out.println("-- Before commit --");
em.getTransaction().commit();
System.out.println("-- After commit --");
results in an output indicating multiple calls to the "setter" methods of MyEntity by the EclipseLinks EntityManager or its associates. Is this behaviour to be expected? Possibly for some internal performance or structural reasons? Do other JPA implementations show the same behaviour?
-- Before commit --
setId
setId
setMessage1
setMessage2
setId
setMessage1
setMessage2
-- After commit --
There seem to be two different kinds of reassignments. First, an initial set of the Id. Second, two consecutive settings of the whole Entity.
Debugging shows that all calls of a given "setter" have the same object as their parameter.
#Entity
public class MyEntity {
private Long id;
private String message1;
private String message2;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
public Long getId(){ return id; }
public void setId(Long i) {
System.out.println("setId");
id = i;
}
public String getMessage1() { return message1; }
public void setMessage1(String m) {
message1 = m;
System.out.println("setMessage1");
}
public String getMessage2() { return message2; }
public void setMessage2(String m) {
message2 = m;
System.out.println("setMessage2");
}
}
Are you using weaving? http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Performance/Weaving
EclipseLink must call setId once to set the generated ID in the managed entity instance. It will also create an instance and set its values for the shared cache, explaining another setId and set values calls. If you are not using weaving, because the EntityManager still exists, EclipseLink will also create a backup instance to use to compare for future changes - any changes to the managed entity after the transaction commits still need tracked.
If this isn't desirable, weaving allows attribute change tracking to be used instead so that backup copies aren't needed to track changes. You can also turn off the shared cache, but unless you are running into performance or stale data issues, this is not recommended.
I wonder if anyone has come across this error and can explain what's happening:
<openjpa-2.1.1-SNAPSHOT-r422266:1087028 nonfatal user error>
org.apache.openjpa.persistence.InvalidStateException:
Primary key field com.qbe.config.bean.QBEPropertyHistory.id of com.qbe.config.bean.QBEPropertyHistory#1c710ab has non-default value.
The instance life cycle is in PNewProvisionalState state and hence an
existing non-default value for the identity field is not permitted.
You either need to remove the #GeneratedValue annotation or modify the
code to remove the initializer processing.
I have two objects, Property and PropertyHistory. Property has OneToMany List of PropertyHistory:
#OneToMany(fetch = FetchType.LAZY, cascade=CascadeType.MERGE, orphanRemoval=false)
#JoinColumn(name="PROPERTY_NAME")
#OrderBy("updatedTime DESC")
private List<QBEPropertyHistory> history = new ArrayList<QBEPropertyHistory>();
And Property object is loaded and saved like this:
public T find(Object id) {
T t = null;
synchronized(this) {
EntityManager em = getEm();
t = em.find(type, id);
//em.close(); //If this is uncommented, fetch=LAZY doesn't work. And fetch=EAGER is too slow.
}
return t;
}
public T update(T t) {
synchronized(this) {
EntityManager em = getEm();
em.getTransaction().begin();
t = em.merge(t);
em.getTransaction().commit();
em.close();
return t;
}
}
In the service layer I load a property using find(id) method, instantiate a new PropertyHistory, add it into property prop.getHistory().add(propHist) then call update(prop) and get the above error.
The error disappears if I close EntityManager in find() but that breaks lazy loading and prop.getHistory() always returns null. If I set fetch=EAGER it becomes unacceptably slow as there are 10s of 1000s of records and I need to select thousands of property objects at a time and history is not needed 99.99% of the time.
I can't remove the #GeneratedValue as the error text suggests because it is generated (DB2, autoincrement). Now I wonder how would i "modify the code to remove the initializer processing" ?
Thanks!
The problem is that you are trying to share an Entity across persistence contexts(EntityManager). You could change your methods to take an EntityManager instance and use the same EM for the find and update operations.