PreUpdate not firing when adding to a collection [duplicate] - java

This question already has answers here:
JPA EventListener method not called on change to many-to-many collection?
(2 answers)
Closed 3 years ago.
I have a JPA annotated class which contains a collection like so:
#Entity
public class Employee {
#Id
private int id;
#Basic
private String name;
#OneToMany
#JoinTable(name = "ORG", joinColumns = #JoinColumn(name="MINION"),
inverseJoinColumns = #JoinColumn(name="EMP"))
private List<Employee> minions = new ArrayList<Employee>();
#PreUpdate
public void preUpdate(){ ... }
}
What I'm seeing is that if I have a managed Employee entity and I add to it's collection of minions the preUpdate method is not getting invoked. A new row is added to the mapping table in the DB so I know the update is going through. If I change a property directly on the Employee, like name, then preUpdate fires as expected when the transaction is committed.
Is there a way to get PreUpdate to fire when a mapped collection is modified? Or is there some other technique or Hibernate specific annotation for detecting when this happens?

#PreUpdate event is triggered just before database UPDATE operation is executed for the entity in question.
If you're not updating direct properties of Employee, there's no UPDATE to execute for its table and thus #PreUpdate listener is never called. You should have better luck using #PrePersist event which is triggered by "flush" rather than "update".

Maybe a this custom workaround works:
Create a subclass of ArrayList which identifies changes through ActionListener pattern
public class Employee {
....
private List<Employee> minions = createChangeNotifierList();
private List<Employee> createChangeNotifierList() {
ChangeNotifierList<Employee> l = new ChangeNotifierList<Employee>();
l.setActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
preUpdate();
}
});
return l;
}
public void setMinions(List<Employee> l) {
if (!(l instanceof ChangeNotifierList)) {
l = createChangeNotifierList();
preUpdate();
}
this.minions = l;
}
public void preUpdate(){ ... }
}
public class ChangeNotifierList<T> extends ArrayList<T> {
private ActionListener actionListener;
public ChangeNotifierList() {
}
public ChangeNotifierList(List<T> list) {
super.addAll(list);
}
public void setActionListener(ActionListener actionListener) {
this.actionListener = actionListener;
}
public boolean add(T e) {
boolean b = super.add(e);
if (b) {
notifyChange();
}
return b;
}
private void notifyChange() {
actionListener.actionPerformed(null);
}
.....
}

Here is my implementation for Hibernate provider:
http://pastebin.com/8cPB96bZ
Generally you just mark methods that should be called in the case of a dirty collection with #PreCollectionChange annotation.

Related

How can I delete an AggregateMember on Axon?

I have an aggregate Organization which can have several addresses. So we have modeled this OrganizationDeliveryAddress as an Aggregate Member. On the OrganizationDeliveryAddress we command and event sourcing handlers for the entity itself.
Here's my current implementation:
#Aggregate
public class Organization {
private #AggregateIdentifier
#NonNull UUID organizationId;
#AggregateMember
private final List<OrganizationDeliveryAddress> deliveryAddresses = new ArrayList<>();
#CommandHandler
public UUID on(AddOrganizationDeliveryAddressCommand command) {
val addressId = UUID.randomUUID();
val event = new OrganizationDeliveryAddressAddedEvent(command.getOrganizationId(), addressId, command.getAddress());
AggregateLifecycle.apply(event);
return addressId;
}
#EventSourcingHandler
public void on(OrganizationDeliveryAddressAddedEvent event) {
val address = new OrganizationDeliveryAddress(event.getOrganizationDeliveryAddressId(), false);
deliveryAddresses.add(address);
}
}
public class OrganizationDeliveryAddress {
private #EntityId
#NonNull UUID organizationDeliveryAddressId;
#CommandHandler
public void on(RemoveOrganizationDeliveryAddressCommand command) {
AggregateLifecycle.apply(new OrganizationDeliveryAddressRemovedEvent(command.getOrganizationId(),
command.getOrganizationDeliveryAddressId()));
}
#EventSourcingHandler
public void on(#SuppressWarnings("unused") OrganizationDeliveryAddressRemovedEvent event) {
if (organizationDeliveryAddressId.equals(event.getOrganizationDeliveryAddressId())) {
AggregateLifecycle.markDeleted();
}
}
}
We want to remove one of the addresses, but it looks like not just the address, but the entire aggregate is deleted.
So here's my question: How can I instruct Axon Framework to remove the OrganizationDeliveryAddress Aggregate Member?
The AggregateMember is not an Aggregate per se but just a member of another Aggregate. This is why if you call AggregateLifecycle.markDeleted(); it will mark the Aggregate itself as deleted.
To 'delete' an AggregateMember you should do the opposite as adding it, which means you can have an #EventSourcingHandler method on your Aggregate listening to the OrganizationDeliveryAddressRemovedEvent. This method would be responsible to find the right DeliveryAddress on your AggregateMember (deliveryAddresses) or even better a Map as you will see below and simply remove it from that. A pseudo-code could be something like this:
// Organization.java
...
#AggregateMember
private final Map<UUID, OrganizationDeliveryAddress> deliveryAddressItToDeliveryAddress = new HashMap<>();
...
#EventSourcingHandler
public void on(#SuppressWarnings("unused") OrganizationDeliveryAddressRemovedEvent event) {
Assert.isTrue(deliveryAddressItToDeliveryAddress.containsKey(event.getOrganizationDeliveryAddressId()), "We do not know about this address");
deliveryAddressItToDeliveryAddress.remove(event.getOrganizationDeliveryAddressId());
}

JPA persisting does not happen with relation when VARCHARS turn to CLOBs

I do have two entities that relate each other via a OneToMany-Relation.
Entity 1 is named "Change" and looks like the following
public class Change {
String attribute1;
#Column(name="\"ATTRIBUTE1\"")
public void getAttribute1() {
return this.attribute1;
}
public void setAttribute1(String attribute1) {
this.attribute1 = attribute1;
}
// and 7 more of these....
List<ChangeTask> relatedChangeTasks = new ArrayList<ChangeTask>();
#OneToMany(cascade={PERSIST, MERGE, REFRESH}
#JoinTable(name="CHANGE_CHANGETASK", joinColumns={#JoinColumn(name="CHANGE_ID")}, inverseJoinColumns={#JoinColumn(name="CHANGETASK_ID")})
#JoinColumn(name="\"relatedChangeTask_ID\"" )
public List<ChangeTask> getRelatedChangeTasks() {
return this.relatedChangeTasks;
}
public void setRelatedChangeTasks(List<ChangeTask> relatedChangeTasks) {
this.relatedChangeTasks = relatedChangeTasks;
}
}
Entity 2 is named ChangeTask and extends Change.
public class ChangeTask extends Change {
// some additional attributes...
}
Persisting a new or existing Change record with one ChangeTask added to the "relatedChangeTask" list works just perfect.
Now I have to change the annotation of the 8 attributes from Default to #Lob, so Change now looks like this:
public class Change {
String attribute1;
#Lob
#Column(name="\"ATTRIBUTE1\"")
#Basic(fetch=EAGER)
public String getAttribute1() {
if(fieldHandler != null) {
return (java.lang.String) fieldHandler.readObject(this, "attribute1", attribute1);
}
return attribute1;
}
public void setAttribute1(String attribute1) {
if(fieldHandler != null) {
this.attribute1= (java.lang.String) fieldHandler.writeObject(this, "attribute1", this.attribute1, attribute1);
return;
}
this.attribute1= attribute1;
}
// and 7 more of these....
List<ChangeTask> relatedChangeTasks = new ArrayList<ChangeTask>();
#OneToMany(cascade={PERSIST, MERGE, REFRESH}
#JoinTable(name="CHANGE_CHANGETASK", joinColumns={#JoinColumn(name="CHANGE_ID")}, inverseJoinColumns={#JoinColumn(name="CHANGETASK_ID")})
#JoinColumn(name="\"relatedChangeTask_ID\"" )
public List<ChangeTask> getRelatedChangeTasks() {
return this.relatedChangeTasks;
}
public void setRelatedChangeTasks(List<ChangeTask> relatedChangeTasks) {
this.relatedChangeTasks = relatedChangeTasks;
}
}
Now, when I try to add a given ChangeTask to a Change the persist operation does not fail. But at the end of the Transaction the relation has not been persisted, meaning the relation-table "CHANGE_CHANGETASK" remains empty. When I debug through the whole process, I can see that the list contains one entry before "entityManager.merge()" operation and it still contains one entry after the merge. But it never arrives at the database.
Does anybody have an idea what I'm doing wrong here? As strange as it may sound, it must be something related with the #Lob annotations. As soon as I remove those again from the entity everything works fine.
Thanks in advance.
You wrote
public void getAttribute1() {
That can't be right. I think you mean
public String getAttribute1() {
Additionally you have annotated the setter:
#Column(name="\"ATTRIBUTE1\"")
public void setAttribute1(String attribute1) {
this.attribute1 = attribute1;
}
You have to annotage either the field or the getter.

Hibernate/JPA: only one entry can have specific field value

I need something that seems not so specific but anyway I was unable to come up with nice and sophisticated solution.
Say I have very simple hibernate/jpa entity:
#Entity(name="entity")
public class Type {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#Column(unique = true, nullable = false)
private String name;
#Column(unique = false, nullable = false)
private boolean defaultType;
}
What i need is to somehow annotate defaultType field so only (and exactly) one persisted entity have this value as true. When new entity get persisted with this defaultType as true, the old one (with defaultType=true) entity has to be altered and its defaultType value changed to false. Also if any entity get changed (its defaultType got changed to true), same rule should apply.
As far I know this can be achieved inside business logic (e.g. in DAO layer), with DB trigger or with hibernates interceptor or event (If there is another way, please let me know). I tried with DAO solution but it's kind of bad solution because it can be bypassed and it is really clumsy for such simple operation. DB triggers can not be added with hibernate/jpa annotations (if I am not mistaken) and i am not sure how to make this functionality with hibernate interceptors/events.
So, what is best solution for this problem?
You need use Callback method in JPA, for example PreUpdate or PostUpdate, for instance:
#Entity
#EntityListeners(com.acme.AlertMonitor.class) // set callback method in another class
public class Account {
Long accountId;
Integer balance;
boolean preferred;
#Id
public Long getAccountId() { ... }
...
public Integer getBalance() { ... }
...
#Transient
public boolean isPreferred() { ... }
...
public void deposit(Integer amount) { ... }
public Integer withdraw(Integer amount) throws NSFException {... }
#PreUpdate // callback method in some class
protected void validateCreate() {
if (getBalance() < MIN_REQUIRED_BALANCE)
throw new AccountException("Insufficient balance to open an
account");
}
#PostUpdate // callback method in some class
protected void adjustPreferredStatus() {
preferred =
(getBalance() >= AccountManager.getPreferredStatusLevel());
}
}
// callback method in another class
public class AlertMonitor {
#PreUpdate // callback method in another class
public void updateAccountAlert(Account acct) {
Alerts.sendMarketingInfo(acct.getAccountId(), acct.getBalance());
}
}
Update: About your question, If I undestand what you want, this code may help you:
#Entity(name="entity")
#EntityListeners(com.yourpackage.TypeListner.class)
public class Type {
...
#Column(unique = false, nullable = false)
private boolean defaultType;
}
public class TypeListner {
pivate static Type objectWithTrue = null;
public void init() { // call this method when application is started
List<Type> results = entityManager
.createQuery("from Type", Type.class)
.getResultList();
for(Type type: results) {
if(type.getDefaultType()) {
objectWithTrue = type;
}
}
}
private void changeDefaultType(Type changed) {
if(changed.getDefaultType()) {
if(changed != objectWithTrue && objectWithTrue != null) {
objectWithTrue.setDefaultType(false);
}
objectWithTrue = changed;
}
}
#PostPresist
public void newType(Type changed) {
changeDefaultType(changed);
}
#PostUpdate
public void updateType(Type changed) {
changeDefaultType(changed);
}
#PreRemove
public void removeType(Type changed) {
if(changed.getDefaultType() && objectWithTrue == changed) {
objectWithTrue = null;
}
}
OR
You can use listner #PreUpdate and #PrePresist and every times overwrite all Type objects without store any variable (it isn't so good for perfomance then first example, but more reliable):
#PreUpdate
void updateType(Type changed) {
if(changed.getDefaultType()
List<Type> results = entityManager
.createQuery("from Type", Type.class)
.getResultList();
for(Type type: results) {
if(changed != type && type.getDefaultType()) {
type.setDefaultType(false);
}
}
}
}

OptimisticLockException caused by #Version annotated int not incrementing

I started getting an OptimisticLockException thrown from my previously working Play application when I made the following modifications:
I added a new field, hasStarted, to an entity:
#Entity
public class ExperimentInstance extends Model {
#Version
public int version;
#Id
public Long id;
public boolean hasStarted;
...
defined in the database by:
alter table experiment_instances add column has_started bit default 0;
I update the new field inside of a TimerTask like so:
final ExperimentInstance runningInstance = ExperimentInstance.findById(experimentInstanceId);
new Timer().schedule(new TimerTask() {
#Override
public void run() {
if (runningInstance != null) {
runningInstance.hasStarted = true;
runningInstance.save();
}
}
}, lifetimeInMs);
I enabled transaction logs for my database and see the following two sql statements:
update experiment_instances set has_started=true, version=3 where id=3721 and version=2
and much later:
update experiment_instances set status='FINISHED', version=3 where id=3721 and version=2
It is clear the issue is that the #Version annotated field is not incrementing from 2 to 3 when the table is updated earlier; however, I have no idea where to start debugging this issue.
Any help would be greatly appreciated.
I solved this issue by adding a "hasStarted()" method to a helper class in the private scope of the containing class like so:
public class HelperClass {
private ExperimentInstance experimentInstance;
public HelperClass(ExperimentInstance experimentInstance) {
this.experimentInstance = experimentInstance;
}
public void hasStarted() {
experimentInstance.hasStarted = Boolean.TRUE;
experimentInstance.update();
}
}
public class ContainingClass extends UntypedActor {
...
private HelperClass helperClass = new HelperClass(experimentInstance);
...
new Timer().schedule(new TimerTask() {
#Override
public void run() {
helperClass.hasStarted();
}
}, lifetimeInMs);
...
I believe the issue was caused by the "final" keyword.

JPA deleting bidirectional association from inverse side

In my domain model there are a lot of bidirectional associations (both OneToMany and ManyToMany)
I've read this article and made all my associations on the basis of the sample pattern. (the ManyToMany associations has a two-sided addXY methods, following the pattern)
Using the pattern in this article the question is, what about deleting from the inverse side?
Example:
public class Customer implements Serializable {
...
#ManyToOne()
private CustomerStatus customerStatus;
#PreRemove
public void preRemove(){
setCustomerStatus(null);
}
public void setCustomerStatus(CustomerStatus customerStatus) {
if(this.customerStatus != null) { this.customerStatus.internalRemoveCustomer(this); }
this.customerStatus = customerStatus;
if(customerStatus != null) { customerStatus.internalAddCustomer(this); }
}
On the other side:
public class CustomerStatus implements Serializable {
private static final long serialVersionUID = 1L;
#OneToMany(mappedBy="customerStatus")
private List<Customer> customers;
#PreRemove
public void preRemove(){
for(Customer c : customers){
c.setCustomerStatus(null); // this causes ConcurrentException
}
}
public List<Customer> getCustomers() {
return Collections.unmodifiableList(this.customers);
}
public void addCustomer(Customer c){
c.setCustomerStatus(this);
}
public void removeCustomer(Customer c){
c.setCustomerStatus(null);
}
void internalAddCustomer(Customer c){
this.customers.add(c);
}
void internalRemoveCustomer(Customer c){
this.customers.remove(c);
}
The problem is, that the preRemove method causes ConcurrentException. How to handle this?
The goal is, to delete the CustomerStatus, and set NULL all the Customers, where there was that status.
UPDATE
Without the preRemove method, I've got MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails
You cannot call this.customers.remove(c) while you are iterating over the customer collection. This question has come up before so you may find other solutions as in here:
How to avoid ConcurrentModificationException when iterating over a map and changing values?
but a simple solution is to just create a new list from the old to iterate over on preRemove:
public void preRemove(){
List<Customer> tempList = new ArrayList(customers);
for(Customer c : tempList){
c.setCustomerStatus(null);
}
}

Categories

Resources