I'm trying to track changes in JPA OneToMany associations in order to notify subscribers of events that a region (one of these associations) of an object have been changed. I first tried to have a specialization of List which is aware of changes.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "col_name")
private List<SomeType> oneOfManyLists = new CAArrayList<SomeType>();
My decorator looks like this:
public class CAArrayList<T> extends ArrayList<T> {
private boolean changed = false;
public CAArrayList() {
super();
}
public boolean isChanged() {
return changed;
}
// Mutable delegates
#Override
public boolean add( T e) {
changed = true;
return super.add( e );
}
....
}
In the DAO I want to check which of the OneToMany associations have been changed,
Unfortunatly the following line leads to an ClassCastException since Hibernate wraps my CAArrayList this with PersistentBag:
((CAArrayList) obj.getSomeList()).isDirty()
The next one works but is not portable between different implementations of JPA.
((PersistentBag) obj.getSomeList()).isDirty()
How should this be done in a portable way?
I think you're trying to do it at the wrong level (too low).
Rather than trying to track all internal changes to the list of SomeType, encapsulate the functional changes to this list in a specific service, to which all the other services (and the presentation layer) would delegate when this list must be modified. In this centralized service, notify the subscribers when the service changes the list of SomeType.
Related
I am having trouble publishing events from an aggregate-root in a Spring Boot application. What I basically want is to publish an "Update" event every time some information about a person is changed.
The code for this is pretty straightforward:
#Entity
public class Person {
#Transient
private final Collection<AbstractPersonRelatedEvent> events = new ArrayList<>();
Person(Person other) {
// copy other fields
other.events.foreach(events::add);
}
// other stuff
public Person updateInformation(...) {
Person updated = new Person(this);
// setting new data on the updated person
if (!hasUpdateEventRegistered()) {
updated.registerEvent(PersonDataUpdatedEvent.forPerson(updated));
}
return updated;
}
void registerEvent(AbstractPersonRelatedEvent event) {
events.add(event);
}
#DomainEvents
Collection<AbstractPersonRelatedEvent> getModificationEvents() {
return Collections.unmodifiableCollection(events);
}
#AfterDomainEventPublication
void clearEvents() {
events.clear();
}
}
I am managing Person instances through a manager:
#Service
#Transactional
class PersistentPersonManager implements PersonManager {
// other methods are omitted
#Override
public Person save(Person person) {
return personRepository.save(person);
}
}
However when I call the manager (manager.save(person.updateInformation(...)) the events seem to go "missing":
upon calling the save() method all events are still present but when Spring invokes getModificationEvents() the collection is empty. The events seem to have vanished somewhere in between (with only Spring-code being executed).
As this is pretty basic, I must be missing something essential but got stuck in a rut.
So how do I get back on track here?
I assume you are using JPA here.
For JPA the save operation actually does a merge on the JPA EnityManager.
For a detached entity merge loads/finds the entity with the same id from the database or the current session and copies all the (changed) fields over. This does ignore transient fields like the events.
You are dealing with detached entities because you are creating a new entity every time you call updateInformation.
So here is what is happening:
You load an entity (e1) from the database. It does not have any events registered.
By calling updateInformation you create a new detached entity (e2). You also register events with e2.
When calling save JPA finds the matching e1 and copies all changes from e2 into it, except the events. So e1 still has no events registered.
Events get triggered, but there aren't any because only e1 is used.
In order to fix this: Do not create new instances of the entity in updateInformation.
The default entities loading in hibernate is set to lazy. I would like to turn on the eager initalization in Hibernate for just while method calling and then still using lazy loading:
For example i have entity like:
public class Applications implements java.io.Serializable {
private int id;
private String name;
private Set viewses = new HashSet(0); // here entities views
private Set routeses = new HashSet(0); // here antoher entities routes
public Set getViewses() {
return this.viewses;
}
public void setViewses(Set viewses) {
this.viewses = viewses;
}
public Set getRouteses() {
return this.routeses;
}
public void setRouteses(Set routeses) {
this.routeses = routeses;
}
// .... other things
}
I want avoid to iterate all inners objects for completly get all details about object Applications For example:
begin();
List apps = getSession().createQuery("from Applications").list(); // here lazy initalisation not all inner elements are filled from database
commit();
Above code resulting that inner entities like (routes,views) are empty.
When i call application.getRouteses() nothing happens beacuse i have set lazy to true and i call getRouteses() after session close (commit) Only way to set all elementy in entitiy Application throught one transaction and return it completly from method is use iteration and setters or eager initialisation:
begin();
List apps = getSession().createQuery("from Applications").list();
Iterator iter = apps.iterator();
while(iter.hasNext()){
Applications application = (Applications) iter.next();
application.setRouteses(application.getRouteses()); // here i set all routes beacuse i call getRoutes and hibernate will load from db
}
commit();
Now eager inistalistation, below code load all details about object with inner routes and views entities too:
begin();
List apps = getSession().createQuery("from Applications").list(); // here eager all inner object are filled from databasse
commit();
only problem is change lazy to eager but not in hibernate.xml configuration. I would like to know it is possible to trun on eager for some time and then turn on lazy (programatical). Fo example:
// HERE LAZY INITIALISATION
turnOnEager(); // some method ???
// HERE EAGER INITIALISATION
begin();
List apps = getSession().createQuery("from Applications").list(); // here eager all inner object are filled from databasse
commit();
// AND LAZY AGAIN
turnOnLazy(); // some method ???
You may not turn on and off the lazy at runtime. But, you can try to change the hql to fetch the child entities like below using fetch keyword.
HQL
from Applications a join fetch a.someProperty s join fetch s.anotherProperty
From docs:
A "fetch" join allows associations or collections of values to be
initialized along with their parent objects using a single select.
This is particularly useful in the case of a collection. It
effectively overrides the outer join and lazy declarations of the
mapping file for associations and collections.
Ref: http://docs.jboss.org/hibernate/orm/3.3/reference/en-US/html/queryhql.html
Can any one say to me that can I return Hibernate Entities as return value in JAXWS web service methods!?
Indeed I have some Entities like these:
#Entity
public class Parent {
...
private Childone childoneByChildoneid;
#ManyToOne
public
#javax.persistence.JoinColumn(name="ChildOneId",referencedColumnName="Id")
Childone getChildoneByChildoneid() {
return childoneByChildoneid;
}
public void setChildoneByChildoneid(Childone childoneByChildoneid) {
this.childoneByChildoneid = childoneByChildoneid;
}
...
}
#Entity
public class Childone {
...
private Collection<Parent> parentsById;
#OneToMany(mappedBy = "childoneByChildoneid")
public Collection<Parent> getParentsById() {
return parentsById;
}
public void setParentsById(Collection<Parent> parentsById) {
this.parentsById = parentsById;
}
...
}
And have a service like this:
#Stateless
#WebService()
public class MasterDataService {
#EJB
private MasterDataManager manager;
#WebMethod
public Parent getParent(int parentId) {
return manager.getParent(parentId);
}
}
#Stateless
public class MasterDataManager {
#PersistenceContext
EntityManager em;
public Parent getParent(int parentId) {
Parent parent = (Parent) em.createQuery(
"select p from Parent p where p.id=:parentId")
.setParameter("parentId", parentId).getSingleResult();
return parent;
}
}
When I call this web method from client I get LazyInitializationException exception :(
I test Serializable and Cloneable interfaces and override clone method but unfortunately it doesn't work, I use em.detach(parent) in manager but it doesn't work still.
Can any one help me?
tnax
It is debatable. Generally, you have two options:
return the entities, but make sure they are initialized. Either mark the #*ToMany with fetch=FetchType.EAGER or use Hibernate.initialize(..). The reason for the exception is that by default all collections in entities are not fetched from the database until requested. But when you request them from the jax-ws serializer, the hibernate session is already closed. Technically, you can have some OpenSessionInViewIntercepetor but I don't think there's something ready-to-use with JAX-WS, and it might be a problem to write one. If you don't want to transfer these collections, you can annotate them with #XmlTransient (or #JsonIgnore, depending on the serialization technique). It makes the entity somewhat of a mess, but I still prefer it to code duplication.
Use DTOs (data transfer objects) - transfer all data from the entity to a new object with a similar structure, that will be exposed by the web service. Again you'd have to make sure you are populating the DTO when the hibernate session is active
I prefer the first option, because it requires less biolerplate code, but I agree one should be very careful with entity state management when using it.
I'm working on GAE-based applications, which uses JDO to access datastore. I need to implement polymorphic relationship between persisted objects.
There's abstract parent class:
#PersistenceCapable
#Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE)
public abstract class Parent {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
#Extension(vendorName = "datanucleus", key = "gae.encoded-pk", value = "true")
String id;
// ....
And several child classes:
#PersistenceCapable (identityType = IdentityType.APPLICATION)
public class Child extends Parent {
// ....
Also, there's one more class, which should have reference to one of child classes. According to "Polymorphic Relationships" section of "Entity Relationships in JDO" article, the best way to implement such relationship is to store key of an object, so this class looks in the following way:
#PersistenceCapable (identityType = IdentityType.APPLICATION)
public class OtherClass {
#Persistent
private String reference;
// ....
I retrieve string key of referenced object from instance of OtherClass. Then I would like to obtain referenced object itself: it's an instance of one of Parent subclasses. BUT:
If I do it with pm.getObjectById(oid) method:
Object object = pm.getObjectById(reference);
JDOObjectNotFoundException exception is thrown (javax.jdo.JDOObjectNotFoundException: No such object FailedObject:...).
If I do it with getObjectById(class, key) method:
Parent object = pm.getObjectById(Parent.class, reference);
FatalNucleusUserException exception is thrown (org.datanucleus.store.appengine.FatalNucleusUserException: Received a request to find an object of kind Parent but the provided identifier is the String representation of a Key for kind Child)
What is correct way to retrieve instance of one of subclasses referenced in another entity?
UPDATE: I found this thread in GAE google group, but frankly it did not help me a lot.
I found the same problem with JDO and App Engine, so I started a project that implements a workaround for this. https://code.google.com/p/datanucleus-appengine-patch/
My first test with the code I have now looks okay, feel free to try it out at give me some feedback.
Actually my workaround may solve your problem 2 ways.
I implemented a getObjectById(class, id) that also looks for kinds that are instances of the provided class.
I implemented a getObjectById(oid) that does some special handling of lookup if oid is of type com.google.appengine.api.datastore.Key, then it will figure out the correct class to return.
I added a new annotation #PolymorphicRelationship that will make is easy to handle to workaround that App Engine describes, with storing the keys. Sample shown below:
#Persist
public Collection<Key> myChildKeys;
#NotPersistent
#PolymorphicRelationship(keyField ="myChildKeys")
public Collection<TestChild> myChildren;
I'm using this rather cancerous and smelly anti-pattern to get around this limitation of JDO/App Engine.
#JsonIgnore
#Persistent(mappedBy="account")
private List<XProvider> xProviders;
#JsonIgnore
#Persistent(mappedBy="account")
private List<YProvider> yProviders;
// TODO: add extra providers here and in getProviders() below...
And then to get the collection:
public List<XProvider> getXProviders() {
if (xProviders == null) {
xProviders = new ArrayList<XProvider>();
}
return xProviders;
}
//etc with other getters and setters for each collection.
public List<Provider> getProviders() {
List<Provider> allProviders = new ArrayList<Provider>();
// TODO: add extra providers here...
allProviders.addAll(getXProviders());
allProviders.addAll(getYProviders());
return allProviders;
}
It's a bad solution, but any port in a storm...
(Also relates a little to this bug, using interfaces as the collection type http://code.google.com/p/datanucleus-appengine/issues/detail?id=207)
App Engine's JDO layer doesn't currently support polymorphism. In fact, I'm not sure if JDO supports it in general or not.
I have a table with a generated id, but in some cases I would like to set it on my own. Can I, somehow, force Hibernate to ignore the #GeneratedValue?
It may be an overkill but have you thought about writing your own CustomIDGenerator which probably subclasses say the AutoGenerator of hibernate and exposes a couple of methods where you can set the id of the next class object to be generated so for example
class MyGenerator extends .... {
public void setIdForObject(Class clazz, Long id) {
//once you use this API, the next time an object of
//type clazz is saved the id is used
}
public void setIdForObject(Class clazz, Long id, Matcher matcher) {
//once you use this API, the next time an object of
//type clazz is saved and the matcher matches yes the id will be
//assigned. Your matcher can match properties like name, age etc
//to say the matched object
}
}
This could get complicated but at the least is possible as per hibernate doco
create your own identifiergenerator/sequencegenerator
public class FilterIdentifierGenerator extends IdentityGenerator implements IdentifierGenerator{
#Override
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
// TODO Auto-generated method stub
Serializable id = session.getEntityPersister(null, object)
.getClassMetadata().getIdentifier(object, session);
return id != null ? id : super.generate(session, object);
}
}
modify your entity as:
#Id
#GeneratedValue(generator="myGenerator")
#GenericGenerator(name="myGenerator", strategy="package.FilterIdentifierGenerator")
#Column(unique=true, nullable=false)
private int id;
...
and while saving instead of using persist() use merge() or update()
Although this question was asked quite a while ago, I found the perfect answer for it in this post by #lOranger, and wanted to share it.
This proposal checks if the object's current id is set to something other than null, and if so, it uses it, otherwise, it generates it using the default (or configured) generation strategy.
It's simple, straight forward, and addresses the issue brought up by #Jens, of one not being able to retrieve the object's current id.
I just implemented it (by extending the UUIDGenerator), and it works like a charm :-D
For you use case, you can manually add this no user.
One way to do it is to put the insert operation on a file named "./import.sql" (in your classpath).
Hibernate will go execute these statements when the SessionFactory is started.