I am working on auditing the changes of an entity, say PersonEntity.
Existing codes support creating or editing a person's profile and persisting the corresponding PersonEntity. If editing, it will update the PersonEntity in database. It also supports get query of the persons based on a few properties like age. Furthermore, it also provides API to set the "created by", "updated by" and "updated time" - generally it is quite auditable.
My work is to make it even more auditable by persisting all the old versions of a PersonEntity. I think I have two options:
Persist a new PersonEntity even if editing. In that way, all versions of PersonEntity are in the database. I should also, 1) add a property in the PersonEntity to indicate whether it is the up-to-date one; 2) when creating or updating PersonEntity, I need to set that flag; 3) change all the query methods to only get PersonEntity that is up-to-date.
In my view that is not a good option since 3) will cause a bunch of code change, and will slow down the query as well.
Creating a new class with the same property as PersonEntity, say PersonAuditEntity. When creating or updating PersonEntity, always persist one new PersonAuditEntity.
But in that way, two similar classes are existing in the same time. My initial solution is to simply wrap a PersonEntity into PersonAuditEntity, but it seems that JPA does not support embed an entity in another entity. Is that right?
Any other suggestions will be highly appreciated.
Did you take a look at Hibernate ORM Envers? This supports auditing the entities like for eg:
#Audited
private String name;
Will record changes to name atribute for the entity.
Another option.
Add a new column which gets set to inactive when the row is replaced with a new one. Only add new rows and never update a row.
All selects need to use an additional where clause "and inactive = false"
Related
I'm using hibernate with jpa and i have implemented audit with postgres trigger.
In my source code i have used à listenner on preInsert, preUpdate and preDelete to create a revision. This revision is added only if the saved entity is annoted by custom #AuditedEntity
This revision line contain transactionId and all the modification in same transaction is logged into audit_revision_details with postgres triggers and with this mecanism i can retrieve all modifications done on an entity.
I have à User entity with #AuditedEntity annotation and in the user entity i have a RoleUser Set. When i'm change lastname of the user, all work fine, i have a revision line created in my audit_revision table and in audit_revision_details i can see the change.
But when i'm saving only element inside RoleUser (i'm saving a user and RoleUser is save with CascadeType.ALL), revision is not created in audit_revision because when i'm entering in preInsert/preDelete or preUpdate listener, the saved entity is RoleUser and RoleUser is not annoted by #AuditedEntity because it's User that have this annotation.
One solution is to add à field in User entity that i always update, with this, i always save User entity and i'have my revision but it's not very clean.
Another one is to check if the entity is annoted by #AuditedEntity or entity_name is 'RoleUser' and i always create revision...but if i change my lastname at the same time i change a role, i risk to have 2 revision in bdd so i need to check if one exist before...
I don't want to use hibernate-envers ( and envers have the same problem...)
How can i tell hibernate to force the revision creation when i only change entity inside another one?
Does anyone have implement same things and can help me?
Thanks
One solution is to add à field in User entity that i always update, with this, i always save User entity and i'have my revision but it's not very clean.
I think that is going to be your only viable option, unless you can make all the *ToMany associations owned i.e. remove the mappedBy part.
By making the many side the owning side, Hibernate will treat an entity as dirty when the collection changes somehow, though I don't think that changes to the entities within the collection will cause the same. If you want that changes to the elements also cause the entity to be treated as dirty, you will have to use an #ElementCollection.
Overall, introducing a field on User like modificationTimestamp which you update is probably the easiest way to achieve this.
I'll simplify the example to get the idea across. My model looks like the following :
Projects have a ManyToMany relationship with Jobs. In order to create the join table, I created a ProjectJob entity that has an added column, "job_order". Project references an OneToMany link to a list of ProjectJob, with CascadeType.ALL.
I have a UniqueConstraint on ProjectJob that looks like this :
#UniqueConstraint(columnNames = {"project_id", "job_order"}
Everything works like a charm unless I want to reorder the jobs in the project. Basically, I flip the orders around in my service and made sure that every ProjectJob was a unique job_order in the list, and then save the Project (since the ProjectJobs will be cascaded). Problems arise when JPA/Hibernate tries to flush the transaction since it tries to update "row by row", and obviously the UniqueConstraint is violated after the first update (even though the whole batch of updates would give a coherent state).
Weirdly enough, the service call happens inside a transaction, so I'm not sure why I get this error.
Is there any way to update the child members of a collection in a unique statement so that the UniqueConstraint gets checked after all the children have been updated, instead of getting triggered after the first one? If not, what solution is available for my use case?
You could try to use deferred constraints. Or you use #ManyToMany #OrderColumn List<Job> jobs in Project instead which handles all of this automatically for you.
I have an entity "Event" that has a ManyToOne relationship with the entity "Organization". So an Organization can have multiple events.
What I originally wanted to do was to filter the entity Event using a property of the Organization entity. So basically when I fetch events, only return the events that have an Organization.code= :codeParam.
To accomplish that I implemented a hibernate filter with :
#FilterDef(name="codeFilter", parameters=#ParamDef( name="codeParam", type="string" ) )
...
#ManyToOne
#JoinColumn(name="Organization_Id")
#Filter(name="codeFilter", condition=" code = :codeParam")
private Organization organization;
...
Filter hibernateFilter = sess.enableFilter("codeFilter");
hibernateFilter.setParameter("codeParam", "hola");
Unfortunately according to a post from the Hibernate Team on the hibernate forums, this is not possible :
A Hibernate data filter does not change the multiplicity of an association. By definition it therefore does not filter many-to-one, one-to-one, or any load() or get() operation.
What is it supposed to do, return NULL instead of an instance? NULL does not mean FILTERED, it means NULL. You guys are using filters wrong.
So my question is : is there any way to filter the base entity ("Event") with a condition on the entity from a manyToOne relationship (Organization.code= :codeParam)?
I need this to be enforced every time there is a fetch of events, so a solution using the already existing hibernate filters or something similar would be greatly appreciated.
EDIT1: The question is a simple example of what needs to be done on a significantly bigger scale. Basically, we want to add security to all our Entities and their own nested Entities through the use of a globally defined filter on a Unix permissions row that all our tables have.
WARNING: Do not do this, it is dependent on Hibernate internals and prone to breaking on schema changes, and possibly on variations in individual query setup.
Set Hibernate to show its generated sql, run the query you want to filter (in this case, loading some Event objects), and check what name it assigns to the join used for fetching the related Organization. For example, the generated sql might include inner join Organization someNameHere on this_.Organization_Id = someNameHere.OrganizationId. Then apply the filter, not to the association, but to the Event class, with condition "someNameHere.code = :codeParam".
This is, unfortunately, the only way I've been able to find to filter one class by the properties of an associated class.
I'm trying to make a more robust solution, but it's a complex issue and I'm still in the research stage for that. I expect it will use code generation (through an annotation processor) and programmatic modification of Hibernate's mapping information on startup, but I'm not sure what else yet.
I am working with Java EE 7 on a Wildfly server. I have a strange scenario, where the client has two tables - "employees" and "employees_modified". The second table has the exactly same structure as the first one and servers as a modification storage. So if an employee changes his name from "john" to "john-1", we will write to employees_modified
insert into employees_modified(first_name) values("john")
Please note that the other fields in the table "employees_modified" are empty.
The question is: is there a way to somehow map the two tables and overwrite the values from employees by those in employees_modified where they are present.
I looked at #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) and #AttributeOverrides but those solutions don't seem to fit to my scenario.
Look at hibernate envers, it solves your problem simply. Attach envers to your project. Place the first table under audit with annotation #Audited and #AuditTable(value = "employees_modified"). But as pointed by #Predrag Maric it is important to leave other fields of the second table empty, you can use #PostPersist (or listener in pure hibernate) method in entity. In this method you can describe additional logic employees_modified entity creation and persisting.
You can use #SQLUpdate and #SQLDelete to customize the CRUD statements to be redirected to a different table:
#Entity
#SQLUpdate( sql="UPDATE employees_modified SET name = ? WHERE id = ?")
#SQLDelete( sql="DELETE FROM employees_modified WHERE id = ?")
public class Employees {
...
}
If it's only for auditing, I agree with the answers before me (triggers, events etc).
If you actually want to access that "employees_modified" table (e.g. run complex Hibernate queries) than you can use a second persistent unit. The following post: https://developer.jboss.org/thread/237078 seems to indicate that's a recommendation from hibernate. Obviously the 2nd unit will need xml configuration rather than annotation - at least it can't rely on the same #Table annotation.
BTW there's also some documentation about a #SecondaryTable annotation, but it's my understanding that it doesn't match your case (because your business probably needs to treat those tables differently - sometimes you want to view just the history, sometimes just the live data)/
I am thinking about switching from a self-implemented versioning-solution to Hibernate Envers, but I am not quite sure yet. I have read a lot about it, but I am concerned about schema changes and how Envers deals with them after having historized data according to an older schema.
What is your experience with Envers in this regard? How do you deal with schema changes and existing data with Envers?
Update 1:
It is not just about adding removing simple columns from a table, but e.g. when changing a simple Forein-Key-Relationship into a separate entity with two 1:n-relationships (M2M with attributed columns. This is a "logical" change in your data model. How do you deal with that when using Envers, when there is already historized data according to the old model? Is there an alternative to manually write sql-scripts and transfering them into the new representation?
In my experience, Envers simply copies every field from your entity table to its audit tables. The copied fields in the audit tables have no constraints on them, including nullability and foreign key constraints, so there's no problem with adding or removing such constraints on the real tables. Any kind of relationships you add to your entities will just be new audit columns and/or tables added under Envers, and it's up to you to correctly interpret them in their historical context.
For your example, if I understand correctly, of switching from a join-column-based relationship to a join-table-based one, you'd simply have the old join column coexisting with the join table, and at the point of the cutover, the former will cease being populated in favor of the latter. Your history will be completely preserved, including the fact that you made this switch. If you want all the old data to fit into the new model in the audit tables, it's up to you to do the migration.
There shouldn't be problems with modifying the existing schema as Envers relies on your #Entities to create the audit tables. So if you add or remove a column from an existing table, as long as this change is reflected in your #Entity / #Audited JavaBean, it should be ok.
The foreign key refactoring should be fine with Envers. As Envers creates a join table even for one-to-many relationship, it should be straight to change it to become many-to-many relationship. I extracted one paragraph from official document:
9.3. #OneToMany+#JoinColumn
When a collection is mapped using these two annotations, Hibernate
doesn't generate a join table. Envers, however, has to do this, so
that when you read the revisions in which the related entity has
changed, you don't get false results.
To be able to name the additional join table, there is a special
annotation: #AuditJoinTable, which has similar semantics to JPA's
#JoinTable.
One special case are relations mapped with #OneToMany+#JoinColumn on
the one side, and #ManyToOne+#JoinColumn(insertable=false,
updatable=false) on the many side. Such relations are in fact
bidirectional, but the owning side is the collection (see alse here).
To properly audit such relations with Envers, you can use the
#AuditMappedBy annotation. It enables you to specify the reverse
property (using the mappedBy element). In case of indexed collections,
the index column must also be mapped in the referenced entity (using
#Column(insertable=false, updatable=false), and specified using
positionMappedBy. This annotation will affect only the way Envers
works. Please note that the annotation is experimental and may change
in the future.