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.
Related
I am attempting to write to multiple databases using hibernate. I have encapsulated write and read/write sessions within a single session object. However, when I go to save I get a lot of errors that the objects are already associated with another session: "Illegal attempt to associate a collection with two open sessions"
Here is my code:
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
readWriteSession.saveOrUpdate(arg0);
writeOnlySession.saveOrUpdate(arg0);
}
}
I have tried evicting the object and flushing; however, that causes problems with "Row was updated or deleted by another transaction"... even though both sessions point to different databases.
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
readWriteSession.saveOrUpdate(arg0);
readWriteSession.flush();
readWriteSession.evict(arg0);
writeOnlySession.saveOrUpdate(arg0);
writeOnlySession.flush();
writeOnlySession.evict(arg0);
}
}
In addition to the above, I have also attempted using the replicate functionality of hibernate. This was also unsuccessful without errors.
Has anyone successfully saved an object to two databases that have the same schema?
The saveOrUpdate tries to reattach a given Entity to the current running Session, so Proxies (LAZY associations) are bound to the Hibernate Session. Try using merge instead of saveOrUpdate, because merge simply copies a detached entity state to a newly retrieved managed entity. This way, the supplied arguments never gets attached to a Session.
Another problem is Transaction Management. If you use Thread-bound Transaction, then you need two explicit transactions if you want to update two DataSources from the same Thread.
Try to set the transaction boundaries explicitly too:
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
Transaction readWriteSessionTx = null;
try {
readWriteSessionTx = readWriteSession.beginTransaction();
readWriteSession.merge(arg0);
readWriteSessionTx.commit();
} catch (RuntimeException e) {
if ( readWriteSessionTx != null && readWriteSessionTx.isActive() )
readWriteSessionTx.rollback();
throw e;
}
Transaction writeOnlySessionTx = null;
try {
writeOnlySessionTx = writeOnlySession.beginTransaction();
writeOnlySession.merge(arg0);
writeOnlySessionTx.commit();
} catch (RuntimeException e) {
if ( writeOnlySessionTx != null && writeOnlySessionTx.isActive() )
writeOnlySessionTx.rollback();
throw e;
}
}
}
As mentioned in other answers, if you are using Session then you probably need to separate the 2 updates and in two different transactions. The detached instance of entity (after evict) should be able to be reused in the second update operation.
Another approach is to use StatelessSession like this (I tried a simple program so had to handle the transactions. I assume you have to handle the transactions differently)
public static void main(final String[] args) throws Exception {
final StatelessSession session1 = HibernateUtil.getReadOnlySessionFactory().openStatelessSession();
final StatelessSession session2 = HibernateUtil.getReadWriteSessionFactory().openStatelessSession();
try {
Transaction transaction1 = session1.beginTransaction();
Transaction transaction2 = session2.beginTransaction();
ErrorLogEntity entity = (ErrorLogEntity) session1.get(ErrorLogEntity.class, 1);
entity.setArea("test");
session1.update(entity);
session2.update(entity);
transaction1.commit();
transaction2.commit();
System.out.println("Entry details: " + entity);
} finally {
session1.close();
session2.close();
HibernateUtil.getReadOnlySessionFactory().close();
HibernateUtil.getReadWriteSessionFactory().close();
}
}
The issue with StatelessSession is that it does not use any cache and does not support cascading of associated objects. You need to handle that manually.
Yeah,
The problem is exactly what it's telling you. The way to successfully achieve this is to treat it like 2 different things with 2 different commits.
Create a composite Dao. In it you have a
Collection<Dao>
Each of those Dao in the collection is just an instance of your existing code configured for 2 different data sources. Then, in your composite dao, when you call save, you actually independently save to both.
Out-of-band you said you it's best effort. So, that's easy enough. Use spring-retry to create a point cut around your individual dao save methods so that they try a few times. Eventually give up.
public interface Dao<T> {
void save(T type);
}
Create new instances of this using a applicationContext.xml where each instance points to a different database. While you're in there use spring-retry to play a retry point-cut around your save method. Go to the bottom for the application context example.
public class RealDao<T> implements Dao<T> {
#Autowired
private Session session;
#Override
public void save(T type) {
// save to DB here
}
}
The composite
public class CompositeDao<T> implements Dao<T> {
// these instances are actually of type RealDao<T>
private Set<Dao<T>> delegates;
public CompositeDao(Dao ... daos) {
this.delegates = new LinkedHashSet<>(Arrays.asList(daos));
}
#Override
public void save(T stuff) {
for (Dao<T> delegate : delegates) {
try {
delegate.save(stuff);
} catch (Exception e) {
// skip it. Best effort
}
}
}
}
Each 'stuff' is saved in it's own seperate session or not. As the session is on the 'RealDao' instances, then you know that, by the time the first completes it's totally saved or failed. Hibernate might want you to have a different ID for then so that hash/equals are different but I don't think so.
I have two entities mapped in my application in order to model a Team and its Members. A Team can have many Members and a Member can belong to no more than one Team. Everything is fine about handling this concepts. The problem comes when I try to move a Member from one existing Team to another.
The entities are presented below, in simplified form. The very last method, transfer(), is the one that should perform the removal of a certain Member from its Team and send it to another one.
#Entity
public class Member extends Person {
#ManyToOne
private Team team;
protected Member() {
super();
}
public Member(Team team, String name) {
super(name);
this.team = team;
}
// Trivial getters and setters...
public Team getTeam() {
return team;
}
protected void setTeam(Team team) {
this.team = team;
}
}
#Entity
public class Team {
#Id
private long id;
private String name;
#OneToMany(mappedBy="team", cascade=CascadeType.ALL)
private List<Member> members = new ArrayList<Member>();
protected Team() {
}
public Team(String name) {
this.name = name;
}
// trivial getters and setters...
public Member addMember(String name) {
Member member = new Member(this, name);
members.add(member);
return member;
}
protected void addMember(Member member) {
members.add(member);
member.setTeam(this);
}
public void removeMember(Member member) {
members.remove(member);
}
public Member memberByName(String memberName) {
for(Member member : members)
if(member.getName().equals(memberName))
return member;
return null;
}
public Collection<Members> getMembers() {
return Collections.unmodifiableCollection(members);
}
public void transfer(Member member, Team destination) {
members.remove(member);
destination.addMember(member);
}
}
I have this unit test code that is intended to validate the transfer service
Team teamA = teamRepository.teamById(idTeamA);
Team teamB = teamRepository.teamById(idTeamB);
Team teamC = teamRepository.teamById(idTeamC);
Member zaki = teamA.memberByName("Zaki");
Member denise = teamA.memberByName("Denise");
EntityTransaction t = teamRepository.transactionBegin();
teamA.transfer(zaki, teamB);
teamA.transferir(denise, teamC);
t.commit();
I have the following exception in the commit() line
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: application.domain.Member
Any ideas?
UPDATE 1:
I decided to perform a little test and changed the code of the transfer() method as follows
public void transfer(Member member, Team destination) {
member.setTeam(this);
}
The result was curious: no error, but also no update on the tables. Hibernate couldn't track the update and the transfer simply didn't happen.
UPDATE 2:
I decided to give it a try on the suggestion from Steve K and changed the transfer() method to the following:
public void transfer(Member member, Team destination) {
destination.addMember(member);
members.remove(member);
}
Looking the addMember() and removeMember() methods (below) we see that the Team is begin updated too.
protected void addMember(Member member) {
members.add(member);
member.setTeam(this);
}
public void removeMember(Member member) {
members.remove(member);
}
So, the member is being added to the destination collection, its Team is being set to the destination Team and then the member is being removed from the current collection (current Team).
The test case was changed to
EntityTransaction t = teamRepository.transactionBegin();
teamA.transfer(zaki, teamB);
teamA.getEntityManager().refresh(teamA); // I get an exception here!!!
t.commit();
In the refresh() line I have the following exception:
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: domain.Member
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1763)
// ... many calls
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: domain.Member
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:139)
// ... many calls
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.flush(AbstractEntityManagerImpl.java:1335)
... 28 more
It seems, after all, that transfering instances from one collection to another (that are implementing a simple aggregation) is not supported in Hibernate!
Your mappings and data access logic are just fine.
You need to move the transaction boundary at the beginning of the code:
EntityTransaction t = teamRepository.transactionBegin();
Team teamA = teamRepository.teamById(idTeamA);
...
teamA.transferir(denise, teamC);
t.commit();
But the exception you got it's not from the code you've shown us:
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: application.domain.Member
There might be a pending persist action being queued up and only translated during flush time (commit time in your case). So you need to activate the SQL logs being executed and try finding out the detached entity.
Moving Member from one list to other is not going to change the team value. Better idea would be to directly change team from A to B in a Member entity.
There are a couple of things that are not clear. First one is how do you handle hibernate session in this test? Is it active throughout the whole test? Second, does teamRepository.teamById(idTeamA) create also members? Because Member zaki = teamA.memberByName("Zaki") will return null if Zaki isn't created.
My setup would be something like this:
In #Before method, initial data is created (teamA (with Zaki and Denise), teamB and teamC)
In test method, begin transaction and get those entities by id
teamRepository.transactionBegin();
Team teamA = teamRepository.teamById(idTeamA); // does this create a team, or returns an existing one? here, it should only return existing team
... // get all teams
Member zaki = teamA.memberByName("Zaki");
... // get all members
Transfer members
teamA.transfer(zaki, teamB);
teamA.transfer(denise, teamC);
Commit transaction
t.commit();
The transfer() method is completely redundant. All you should need to do is call member.setTeam(destination) and Hibernate will update the collections accordingly.
Your Update's code:
member.setTeam(this);
does nothing. It should be
member.setTeam(destination);
You're seeing no updates because you're not changing the value. The member's already a member of this Team.
UPDATE:
Try just changing your transfer() method to this:
public void transfer(Member member, Team destination){
member.setTeam(destination);
}
After you've moved around all the team members you want to move, you can commit or flush the transaction before moving on to the next unit of work. Moving the items around in the local lists is not necessary unless your units of work are badly defined. Transfer your people, then commit. Then you're ready for the next bit of processing. If you need to do so in the middle of a transaction, then call entityManager.flush() which will execute the currently queued updates but still allow you to later commit or rollback. Flushing in this manner is an anti-pattern, though. If you need to know which team a member is a part of during your processing, ask the member, not the team (which is much more efficient anyway).
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.
I have some type an architect(?) question
I develop an application, based on Spring and Hibernate (annotated configuration)
For each table in my database I added 4 fields: createdBy and modifiedBy(String), created and modified (Datetime). Respectively each entity class also has this fields and getter/setter pairs. So I want to find best practice solution for filling this fields instead adding for each DAO extra code. Is it possible?
I'll be glad to any proposal
Certainly. Just add this code to a base class for all your persistent instances and enable annotation processing:
#PrePersist
public void prePersist()
{
if (created == null)
{
created = updated = createCurrentTimestamp();
createdBy = updatedBy = CurrentUser.get();
}
}
#PreUpdate
public void preUpdate()
{
updated = createCurrentTimestamp();
updatedBy = CurrentUser.get();
}
public static java.sql.Timestamp createCurrentTimestamp ()
{
final long now = System.currentTimeMillis();
final java.sql.Timestamp ts = new java.sql.Timestamp (now);
ts.setNanos(((int)(now % 1000)) * 1000000);
return ts;
}
CurrentUser is a ThreadLocal<String> which allows me to specify at the start of an operation which user started it. This way, any object that gets touched will contain the correct information.
Without annotation processing, activate the respective options in your HBM file.
Look at Spring AOP.
You can assign an "interceptor" for your DAO methods, so that the objects are first handled by the interceptor, and then the execution proceeds to the DAO methods.
In the interceptor you can fill the objects with the data you need.
One possibility would be to define a Hibernate EventListener which can fill in these fields just before each entity is flushed to the database
In my code, I did as follows:
queried for a course entity
populate it with the given course data.
courseDao.update(entity) which internally calls persist(entity) method.
Surprisingly, the data is got updated successfully.
I am confused with this behaviour of persist method.
Please help me out.
code is as below:
//My Service......
#Service("myService")
#Transactional
public class MyServiceImpl implements MyService {
#Transactional(rollbackFor = { Throwable.class })
public void updateCourse(final Course course) throws MyServiceException {
------
------
CourseEntity courseEntity = courseDao.findById(course.getId());
populateCourseEntity(courseEntity, course);
courseDao.update(courseEntity);
}
}
//CourseDao.....
public class CourseDaoImpl implements CourseDao {
--------
public void update(final T entity) throws MyDaoException {
if (entity != null) {
this.entityManager.persist(entity);
}
else {
String errMsg = "Object to be updated cannot be null.";
throw new MyDaoException(errMsg);
}
}
}
When an entity is currently managed (attached to a session), all updates to it are directly reflected to the underlying storage even without calling persist().
In your case, you load your entity, so it's in the session. Then even if you don't call persist() it will be updated in the database on transaction commit.
The persist() description from the javadoc:
Make an entity instance managed and persistent.
This means that the method doesn't do anything in your case, since your entity is both persistent and managed.
P.S. Where I say "session", understand "entity manager"
JPA tries very hard to be a helpful API, such that anything you get from it (or save to it) will subsequently be tracked by JPA. This means than any further changes will be automatically handled for you by JPA without any additional work on your part.