openjpa, update, error 'PK has non-default value' - java

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.

Related

Unexpected duplicate key exception

I'm seeing a weird behavior with Spring Boot 2.0.4 + Hibernate.
I have an entity including a randomly generated code. If the generated code is already set for another entity, a DataIntegrityViolationException is thrown as expected. This way the loop can try again with a new code which hopefully is not used. When this happens, the loop continues, a new code is generated and the call to saveAndFlush() throws the same exception again saying that the original code that caused the problem (previous iteration) is already used (duplicate). However, I'm setting a new code now, not the one the exception mentions.
The only thing I can think of is that Hibernate doesn't remove the operation from the "queue" so when the second call to saveAndFlush() happens, it still tries to perform the first save and then the new one. Obviously, the first save fails as during the first iteration. Maybe I'm wrong, but then what is going on here?
#Entity
public class Entity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private int code;
public void setCode(int code) {
this.code = code;
}
//Other properties
}
#Transactional
public void myFunction() {
boolean saved = false;
do {
int code = /* Randomly generated code */;
if(entity == null) {
entity = new Entity(code, /* other properties */);
} else {
entity.setCode(code);
}
try {
entity = myRepository.saveAndFlush(entity);
saved = true;
} catch (DataIntegrityViolationException e) {
/* Ignore so that we can try again */
}
} while(!saved);
}
EDIT:
If I replace saveAndFlush() by save(), the issue disappears. I saw somewhere that doing a save after a previous save that failed may be problematic if flush() is also called. This is exactly my case. However, I don't understand why it is a problem. The only reason I call saveAndFlush() instead of save() is to catch the duplicate key exception. Using save(), if Hibernate doesn't perform the INSERT or UPDATE directly, the exception is thrown during the flush occurring just before the transaction is committed which is not really what I want.
You will need to restart your transaction in such a scenario, as the error is already bound to the transaction context/session.
So instead, put the retry logic outside of the transaction boundary, or if you need to maintain integrity (all or nothing saved), check first if it is present to avoid the exception being thrown.
If you debug the code and see what's the state of persistence context you may get your answer. You are right hibernate maintains a queue of sorts i.e. all the queries that are made during a transaction will run on commit/flush.
Please post the value of persistence context you get while debugging.

GAE Objectify load object created in transaction within same transaction

I try to use objectify transaction, but I have some issues when I need to reload an object created in the same transaction.
Take this sample code
#Entity
public class MyObject
{
#Parent
Key<ParentClass> parent;
#Index
String foo;
}
ofy().transact(new VoidWork()
{
#Override
public void vrun()
{
ParentClass parent = load();// load the parent
String fooValue = "bar";
Key<ParentClass> parentKey = Key.create(ParentClass.class, parent.getId())
MyObject myObject = new MyObject(parentKey);
myObject.setFoo(fooValue);
ofy().save().entity(myObject).now();
MyObject reloaded = ofy().load().type(MyObject.class).ancestor(parentKey).filter("foo", fooValue).first().now();
if(reloaded == null)
{
throw new RuntimeException("error");
}
}
});
My object reloaded is always null, maybe I miss something, but logically within a transaction I can query an object which was created in the same transaction?
Thanks
Cloud Datastore differs from relational databases in this particular case. The documentation states that -
Unlike with most databases, queries and gets inside a Cloud Datastore
transaction do not see the results of previous writes inside that
transaction. Specifically, if an entity is modified or deleted within
a transaction, a query or lookup returns the original version of the
entity as of the beginning of the transaction, or nothing if the
entity did not exist then.
https://cloud.google.com/datastore/docs/concepts/transactions#isolation_and_consistency

What Dropwizard-Hibernate doc is trying to say?

I have run into LazyInitializationException and then I ran into the following paragraph from the official doc. Unfortunately, it makes absolutely no sense to me. Please help.
(The code block above the paragraph in the doc.)
#GET
#Timed
#UnitOfWork
public Person findPerson(#PathParam("id") LongParam id) {
return dao.findById(id.get());
}
Important
The Hibernate session is closed before your resource method’s return
value (e.g., the Person from the database), which means your resource
method (or DAO) is responsible for initializing all lazily-loaded
collections, etc., before returning. Otherwise, you’ll get a
LazyInitializationException thrown in your template (or null values
produced by Jackson).
First The Hibernate session is closed before your resource method’s return value. How is this possible? This would have been possible had there been a try-finally block around my resource's return statement, but that is not the case here.
My resource should have been invoked by another method, which I am guessing would open the Hibernate session before my resource method is invoked and would then close the session after my resource method returns. How can it close it before my method returns. I don't get it.
The most important part - which means your resource method (or DAO) is responsible for initializing all lazily-loaded collections, etc., before returning. I have no Hibernate experience. I am using it for the first time now. How do I initialize, or rather what is exactly is meant by "initialize" in context of Hibernate? A code example will help a lot.
PS: This question might look odd, and someone at a cursory glance might even suggest to move it to "English Language and Usage", but please read it carefully. This is a technical question, not paragraph dissection.
Edit:
Added the code block from the doc else it won't make sense anyone. Also I removed one paragraph from my question, which became clear to me, immediately after posting the question.
First The Hibernate session is closed before your resource method’s
return value. How is this possible? This would have been possible had
there been a try-finally block around my resource's return statement,
but that is not the case here.
I know nothing about Dropwizard. So let's see the source (I change it a bit).
From UnitOfWorkAwareProxyFactory
class UnitOfWorkAwareProxyFactory {
public <T> T create(Class<T> clazz) {
final ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(clazz);
final Proxy proxy = (Proxy) factory.createClass().newInstance();
proxy.setHandler(new MethodHandler() {
#Override
public Object invoke(Object self, Method overridden,
Method proceed, Object[] args) {
final UnitOfWork unitOfWork = overridden.getAnnotation(UnitOfWork.class);
final UnitOfWorkAspect unitOfWorkAspect = new UnitOfWorkAspect(sessionFactories);
try {
unitOfWorkAspect.beforeStart(unitOfWork);
Object result = proceed.invoke(self, args);
unitOfWorkAspect.afterEnd();
return result;
} catch (Exception e) {
unitOfWorkAspect.onError();
throw e;
}
}
});
return (T) proxy;
}
}
if you have a class
class PersonDao {
#UnitOfWork
public Person findPerson(LongParam id) {
return dao.findById(id.get());
}
}
You can do something like this
UnitOfWorkAwareProxyFactory factory = new UnitOfWorkAwareProxyFactory();
PersonDao proxy = factory.create(PersonDao.class);
when you do
Person person = proxy.findPerson(1L);
that line becomes
unitOfWorkAspect.beforeStart(unitOfWork);
Object result = findPerson.invoke(proxy, 1L);
unitOfWorkAspect.afterEnd();
return result;
Methods unitOfWorkAspect.beforeStart(unitOfWork) and unitOfWorkAspect.afterEnd() from the source UnitOfWorkAspect
class UnitOfWorkAspect {
public void beforeStart(UnitOfWork unitOfWork) {
session = sessionFactory.openSession();
configureSession();
beginTransaction();
}
public void afterEnd() {
try {
commitTransaction();
} catch (Exception e) {
rollbackTransaction();
throw e;
} finally {
session.close();
}
}
}
The most important part - which means your resource method (or DAO) is responsible for initializing all lazily-loaded collections, etc., before returning. I have no Hibernate experience. I am using it for the first time now. How do I initialize, or rather what is exactly is meant by "initialize" in context of Hibernate?
Initialize in this context means the collection data should be loaded from a database. Some methods of an initialization
1.Use an eager loading, for an example
class User {
#ManyToMany(fetch = FetchType.EAGER)
private List<Role> roles;
}
Hibernate will load roles via joins or subselects, when you get a User entity.
Use Hibernate.initialize(user.getRoles())
Use join fetch in HQL — from User user left join fetch user.roles
Use Criteria with setFetchMode()
Use fetch profiles, entity graphs. Don't know can entity graphs be used with a session, it is a JPA feature: http://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/fetching/Fetching.html
If you don't need to fetch collections, you can use a partial objects loading with transforming to the root entity: How to transform a flat result set using Hibernate

Entity must be managed to call remove?

public Person deletePerson(Person entity) {
EntityManager ems = emf.createEntityManager();
try {
ems.getTransaction().begin();
ems.merge(entity);
ems.remove(entity);
ems.getTransaction().commit();
} finally {
ems.close();
}
return entity;
}
it doesnt work I don't know why? Gives me java.lang.IllegalArgumentException
It doesn't work because remove operation requires managed entity to be passed to it. You could modify your code like this to make it work:
entity = ems.merge(entity);
ems.remove(entity);
Because merge returns managed entity instance, you can call remove with the object it returns, because it is managed by JPA (the object you pass to merge is not affected, which is why your code fails).

LazyInitializationException, for an int

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.

Categories