I have an entity class that has a lazy field like this:
#Entity
public Movie implements Serializable {
...
#Basic(fetch = FetchType.LAZY)
private String story;
...
}
The story field should normally be loaded lazily because it's usually large. However sometimes, I need to load it eagerly, but I don't write something ugly like movie.getStory() to force the loading. For lazy relationship I know a fetch join can force a eager loading, but it doesn't work for lazy field. How do I write a query to eagerly load the story field?
I'd try Hibernate.initialize(movie). But calling the getter (and adding a comment that this forces initialization) is not that wrong.
The one possible solution is:
SELECT movie
FROM Movie movie LEFT JOIN FETCH movie.referencedEntities
WHERE...
Other could be to use #Transactional on method in ManagedBean or Stateless and try to access movie.getReferencedEntities().size() to load it but it will generate N+1 problem i.e. generating additional N queries for each relationship which isn't too efficient in many cases.
You can use the fetch all properties keywords in your query:
SELECT movie
FROM Movie movie FETCH ALL PROPERTIES
WHERE ...
To quote the JPA spec (2.0, 11.1.6):
The LAZY strategy is a hint to the persistence provider runtime that data should be fetched
lazily when it is first accessed. The implementation is permitted to eagerly fetch data for which the
LAZY strategy hint has been specified.
Hibernate only supports what you are trying if you use its bytecode enhancement features. There are a few ways to do that. First is to use the build-time enhancement tool. The second is to use (class-)load-time enhancement. In Java EE environments you can enable that on Hibernate JPA using the 'hibernate.ejb.use_class_enhancer' setting (set it to true, false is the default). In Java SE environments, you need to enhance the classes as they are loaded, either on your own or you can leverage org.hibernate.bytecode.spi.InstrumentedClassLoader
If you don't mind having a POJO as a query result you can use constructor query. This will require your object to have constructor with all needed parameters and a query like this:
select new Movie(m.id, m.story) from Movie m
I would suggest to traverse the objects using Java reflection calling all methods starting with "get" and repeat this for all the gotten object, if it has an #Entity annotation.
Not the most beautiful way, but it must be a robust workaround. Something like that (not tested yet):
public static <T> void deepDetach(EntityManager emanager, T entity) {
IdentityHashMap<Object, Object> detached = new IdentityHashMap<Object, Object>();
try {
deepDetach(emanager, entity, detached);
} catch (IllegalAccessException e) {
throw new RuntimeException("Error deep detaching entity [" + entity + "].", e);
} catch (InvocationTargetException e) {
throw new RuntimeException("Error deep detaching entity [" + entity + "].", e);
}
}
private static <T> void deepDetach(EntityManager emanager, T entity, IdentityHashMap<Object, Object> detached) throws IllegalAccessException, InvocationTargetException {
if (entity == null || detached.containsKey(entity)) {
return;
}
Class<?> clazz = entity.getClass();
Entity entityAnnotation = clazz.getAnnotation(Entity.class);
if (entityAnnotation == null) {
return; // Not an entity. No need to detach.
}
emanager.detach(entity);
detached.put(entity, null); // value doesn't matter. Using a map, because there is no IdentitySet.
Method[] methods = clazz.getMethods();
for (Method m : methods) {
String name = m.getName();
if (m.getParameterTypes().length == 0) {
if (name.length() > 3 && name.startsWith("get") && Character.isUpperCase(name.charAt(3))) {
Object res = m.invoke(entity, new Object[0]);
deepDetach(emanager, res, detached);
}
// It is actually not needed for searching for lazy instances, but it will load
// this instance, if it was represented by a proxy
if (name.length() > 2 && name.startsWith("is") && Character.isUpperCase(name.charAt(2))) {
Object res = m.invoke(entity, new Object[0]);
deepDetach(emanager, res, detached);
}
}
}
}
Related
I have a rest application where one of the resources can be updated. Below are two methods responsible for achieving this task:
updateWithRelatedEntities(String, Store): receives id and new object Store which was constructed by deserializing PUT request entity, sets the version (used for optimistic locking) on new object and calls update in a transaction.
public Store updateWithRelatedEntities(String id, Store newStore) {
Store existingStore = this.get(id);
newStore.setVersion(existingStore.getVersion());
em.getTransaction().begin();
newStore = super.update(id, newStore);
em.getTransaction().commit();
return newStore;
}
update(String, T): a generic method for making an update. Checks that ids match and performs merge operation.
public T update(String id, T newObj) {
if (newObj == null) {
throw new EmptyPayloadException(type.getSimpleName());
}
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class) {
superclass = ((Class) superclass).getGenericSuperclass();
}
Class<T> type = (Class<T>) (((ParameterizedType) superclass).getActualTypeArguments()[0]);
T obj = em.find(type, id);
if (!newObj.getId().equals(obj.getId())) {
throw new IdMismatchException(id, newObj.getId());
}
return em.merge(newObj);
}
The problem is that this call: T obj = em.find(type, id); triggers an update of store object in the database which means that we get OptimisticLockException when triggering merge (because versions are now different).
Why is this happening? What would be the correct way to achieve this?
I kind of don't want to copy properties from newStore to existingStore and use existingStore for merge - which would, I think, solve the optimistic lock problem.
This code is not running on an application server and I am not using JTA.
EDIT:
If I detach existingStore before calling update, T obj = em.find(type, id); doesn't trigger an update of store object so this solves the problem. The question still remains though - why does it trigger it when entity is not detached?
I can't see your entity from code which you added but I believe that you missing some key point with optimistic locking -> #Version annotation on version field.
If you have this field on your entity then container should be able to do merge procedure without problems. Please take a look to
Optimistic Locking also good article don't break optimistic locking
I know there are many similar questions about this trouble but nothing works for me.
I have #ManyToOne relationship between Aim and User.
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false, updatable = false)
private User user;
and
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Collection<Aim> userAims;
respectively.
#Override
#Transactional(propagation = Propagation.REQUIRED)
#PreAuthorize("isAuthenticated() and principal.user.isEnabled() == true")
public Aim findById(String aimId) throws NumberFormatException, EntityNotFoundException {
Aim aim = null;
try {
aim = aimRepository.findOne(Long.parseLong(aimId));
} catch (NumberFormatException e) {
throw new InvalidDataAccessApiUsageException(e.getMessage(), e);
}
if (aim == null) throw new EntityNotFoundException("Aim with id: " + aimId + " not found!");
return aim;
}
#OneToMany associations work fine with lazy fetching. Method isn't nested to another #Transactional method so #Transactional works fine.
So the record exists.
Classes User and Aim aren't final and implement
Serializable
Some sources advice to put annotations on getters. It also doesn't
work.
#Fetch(FetchMode.SELECT) the same situation =\
Query via Hibernate results the same, but HQL query with left join
fetch works fine
My FK is ON UPDATE CASCADE ON INSERT CASCADE
optional = false also tried...
Pay attention that I haven't the LazyInitException
Thanks in advance!
I'm guessing from the code in your findById method, and by the reference to "lazy initialization not working" in the title, that you are wanting to find an Aim object by it's numeric Id, along with the associated User object.
In order to do this with lazy-loading, you need to 'get' the associated object, and (most importantly) you need to 'get' one of the associated entity's fields.
So the code inside the try block should be:
aim = aimRepository.findOne(Long.parseLong(aimId));
if (aim != null && aim.getUser() != null) {
aim.getUser().getUserId(); // doesn't need to be assigned to anything
}
Alternatively, if you have a logger available you can use the userId in a debug or trace log message:
if (aim != null && aim.getUser() != null) {
logger.debug("Lazy-loaded User " + aim.getUser().getUserId());
}
This has the added benefit that you can debug how things are lazy-loaded.
By the way, we found out the hard way that making a find routine throw an Exception when it doesn't find something is a bad idea. This is because you might want to use the find routine to find out if an Entity does NOT exist. If that is happening within a transaction, your exception may trigger an unwanted rollback (unless you specifically ignore it). Better to return null and check for that instead of using a try ... catch.
I'm trying to save with GreenDAO an entity called hotel. Each hotel has a relation one-to-many with some agreements and each agreement has got... well, a picture is worth a thousand words.
Now, what I do is the following:
daoSession.runInTx(new Runnable() {
#Override
public void run() {
ArrayList<Hotel> listOfHotels = getData().getAvailability();
for(Hotel h : listOfHotels)
{
List<HotelAgreement> hotelAgreements = h.getAgreements();
for(HotelAgreement ha : hotelAgreements) {
ha.setHotel_id(h.getHotel_id());
HotelAgreementDeadline hotelAgreementDeadline = ha.getDeadline();
List<HotelRemark> hr = hotelAgreementDeadline.getRemarks();
List<HotelAgreementDeadlinePolicies> hadp = hotelAgreementDeadline.getPolicies();
daoSession.getHotelReportDao().insertOrReplaceInTx( h.getReports() );
daoSession.getHotelPictureDao().insertOrReplaceInTx( h.getPictures() );
daoSession.getHotelRemarkDao().insertOrReplaceInTx(hr);
daoSession.getHotelAgreementDeadlinePoliciesDao().insertOrReplaceInTx(hadp);
daoSession.getHotelAgreementDeadlineDao().insertOrReplace(hotelAgreementDeadline);
daoSession.getHotelAgreementDao().insertOrReplace(ha);
}
// daoSession.getHotelReportsDao().insertOrReplace( getData().getReports() );
}
daoSession.getHotelDao().insertOrReplaceInTx(listOfHotels);
}
});
This, of course, does not work. I get a "Entity is detached from DAO context" error on the following line:
HotelAgreementDeadline hotelAgreementDeadline = ha.getDeadline();
I understand this is because I try to get the Agreements from a Hotel entity which does not come from the database, but from another source (a web service, in this case). But why does this happen with ha.getDeadline() and not with h.getAgreements()?
Now, I have the Hotel object and it does include pretty much all data: agreements, deadline, policies, remarks, pictures, report. I'd just like to tell GreenDAO: save it! And if I can't and I have to cycle through the tree - which is what I'm trying to do with the code above - how am I supposed to do it?
Here I read that I have to "store/load the object first using a Dao". Pretty awesome, but... how does it work? I read the greenDAO documentation about relations but couldn't find anything.
Thank you to everybody who's willing to help :-)
At some point, when you get the response from the webservice, you are creating new entity objects and filling them with the info. Try inserting each new object in the DB just after that.
If you want, you can insert, for example, all n Agreement for an Hotel using insertOrReplaceInTx, but you shouldn't use any relation before all the involved objects are in the DB.
I think that greendao team have to add the following control in the method
getToOneField() like in the getToManyList()
if(property == null){
code already generated by greendao plugin
}
return property;
so in your case in HotelAgreements class
#Keep
public DeadLine getDeadLine {
if(deadLine == null) {
long __key = this.deadLineId;
if (deadLine__resolvedKey == null || !deadLine__resolvedKey.equals(__key)) {
final DaoSession daoSession = this.daoSession;
if (daoSession == null) {
throw new DaoException("Entity is detached from DAO context");
}
DeadLineDao targetDao = daoSession.getDeadLineDao();
DeadLine deadLineNew = targetDao.load(__key);
synchronized (this) {
deadLine = deadLineNew;
deadLine__resolvedKey = __key;
}
}
}
return deadLine;
}
adding the control
if(deadLine == null) {
...
}
so if you receive data from rest json
the object is populated and getProperty() method return property field from object not from database just like it does with Lists
then you can insert or replace it
Then, when you load or load deeply object from db the property is null and greendao take it from DB
I'm trying to persist 3 entities (exp,def,meng) in a transaction, and then persist another 2 (def', meng'), where meng' is related to exp.
However, as I attempt to persist meng' eclipselink/jpa2 is being told to:
Call: INSERT INTO EXPRESSION (EXPRESSION, GENDER) VALUES (?, ?)
bind => [exp, null]
which will throw an expession since it's been already inserted and it's a key.
So apparently persisting the entity meng' which includes updating exp itself would somehow make eclipselink think I asked to persist a new exp.
Here is the test:
#Test
public void testInsertWords() throws MultipleMengsException, Exception{
final List<String[]> mengsWithSharedExp = new LinkedList<String[]>();
mengsWithSharedExp.add(mengsList.get(3));
mengsWithSharedExp.add(mengsList.get(4));
insertWords(mengsWithSharedExp, null, mengsDB);
}
Here is the problematic code:
public void insertWords(EnumMap<Input, MemoEntity> input) throws MultipleMengsException {
Expression def = (Expression) input.get(Input.definition);
Expression exp = (Expression) input.get(Input.expression);
beginTransaction();
persistIfNew(def);
persistIfNew(exp);
persistNewMeng(null, exp, def);
commitTransaction();
}
private void persistNewMeng(final MUser usr, Expression exp, final Expression def) throws RuntimeException {
final Meaning meng = new Meaning(usr, exp, def);
if (!persistIfNew(meng)) {
throw new RuntimeException("Meng ." + meng.toString() + " was expected to be new.");
}
if (usr != null) {
usr.addMeng(meng);
}
}
public <Entity> boolean persistIfNew(final Entity entity) {
final Object key = emf.getPersistenceUnitUtil().getIdentifier(entity);
if (em.find(entity.getClass(), emf.getPersistenceUnitUtil().getIdentifier(entity)) != null) {
return false;
}
em.persist(entity);
return true;
}
You can checkout the Maven source code (to test) from here.
Is this expected behavior? If so, why? And most importantly, how to solve?
It looks as if
#ManyToMany(cascade=CascadeType.ALL)
private Set<Expression> exps;
in Meaning is the culprit, although I don't understand why it should. The documentation says:
If the entity is already managed, the persist operation is ignored, although the persist operation will cascade to related entities that have the cascade element set to PERSIST or ALL in the relationship annotation.
Frank is correct. You are not reading in the Expression, so when you call persist on Meaning, when it is referencing existing Expressions they are detached, which cause the transaction to fail. Calling merge will work, or you can remove the cascade persist on the exps relationship since you seem to persist new Expressions directly anyway its not needed.
Mostly likely you run into:
[...] If the entity is detached [...] the transaction commit will fail.
(same source that you are citing)
If you persist a new entity that references an already persistent entity, you must use "merge" instead of "persist". "merge" will persist new entities and update existing entities.
Also beware of the fact that the merge operation will return an attached data graph, that must be used for further operations within the same persistence context.
I am learning GAE and am getting a bit stuck. If I use the following, with a finally to make sure the persistence manager is closed, I get an exception when trying to actually read the Note objects:
public class Notes {
public List<Note> getAll() {
PersistenceManager pm = PMF.instance().getPersistenceManager();
try {
Query query = pm.newQuery("select from com.uptecs.google1.model.Note order by subject");
return (List<Note>) query.execute();
} finally {
pm.close();
}
}
}
The exception I get is this:
Object Manager has been closed
org.datanucleus.exceptions.NucleusUserException: Object Manager has been closed
at org.datanucleus.ObjectManagerImpl.assertIsOpen(ObjectManagerImpl.java:3876)
at org.datanucleus.ObjectManagerImpl.getFetchPlan(ObjectManagerImpl.java:376)
at org.datanucleus.store.query.Query.getFetchPlan(Query.java:497)
Try detaching the object from the graph with detachable="true":
#PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
public class Note {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Long key;
...
}
Note: I totally understand the need for this, sometimes you need to retrieve the objects and lists in a controller, close the PM in the controller, then pass the models to the views. Until better solutions are known to me, this is what I am doing this on JDO/GAE with no problems so far.
List:
It seems to me that you will have to detach all the items in the list if you want to be able to use them after the PM is closed. I'd use this to get specific lists of items. A full getAll() can be very big in size.
public List<Note> getList(){
List<Note> detachedList=null, list=null;
try {
String query = "select from " + Note.class.getName();
pm = PMF.get().getPersistenceManager();
list = (List<Note>)pm.newQuery(query).execute();
detachedList = new ArrayList<Note>();
for(Note obj : list){
detachedList.add(pm.detachCopy(obj));
}
} finally {
pm.close();
}
return detachedList;
}
By Key:
public Note findByKey(Long key) {
Note detachedCopy=null, object=null;
try{
pm= PMF.get().getPersistenceManager();
object = pm.getObjectById(Note.class,key);
detachedCopy = pm.detachCopy(object);
}catch (JDOObjectNotFoundException e) {
return null; // or whatever
}
finally {
pm.close(); // close here
}
return detachedCopy;
}
Afer the close, you have a detached copy, with which you can work.
Reference: http://www.datanucleus.org/products/accessplatform_1_1/jdo/attach_detach.html
When result is returned in the list - objects are retrieved lazily (only when you ask for them). Since your persistence manager is closed you get an exception. By "detaching" the objects your are effectively telling the persistence manager to retrieve them eagerly.
In addition to the answer from bakkal, I would say that you absolutely need the detachable="true" annotation parameter, otherwise you will never get it to work.
To detach a list of objects, you can also use pm.detachCopyAll(your_query_result_list), wich will be a bit faster than your implementation of the iteration to detach, and will let you spare a few lines of code. Thanks JDO ! ;-) But be aware, this method requires explicit cast of its results.
Here's a working example I currently use in my last App (the Key used in the query is an Encoded String) :
pm = PMF.get().getPersistenceManager();
Query query = pm.newQuery(TandemSubscription.class);
query.setFilter("groupSubscriptionKey==groupSubscriptionKeyParam");
query.setOrdering("dateRDV desc");
query.declareParameters("String groupSubscriptionKeyParam");
// Get Data
#SuppressWarnings("unchecked")
List<TandemSubscription> savedSubscriptions =
(List<TandemSubscription>) query.execute(Key);
// Detach all objects in the list
savedSubscriptions =
(List<TandemSubscription>) pm.detachCopyAll(savedSubscriptions);
pm.close();
// Now you can use the list and its content.
I Hope this helps a bit.