Envers reflection error when updating an ElementCollection with EmbeddedId - java

I have a model that has a composite key id and a map of strings that is mapped as an element collection.
#Entity
#Audited
#NoArgsConstructor
#AllArgsConstructor
#Data
public class DemoModel {
#EmbeddedId
private DemoCompositeKey id;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name="demo_model_collection", joinColumns = {
#JoinColumn(name="demo_one", referencedColumnName = "one"),
#JoinColumn(name="demo_two", referencedColumnName = "two")
})
#MapKeyColumn(name = "key_thing")
#Column(name = "value_thing")
#EqualsAndHashCode.Exclude()
private Map<String, String> someCollection = new HashMap<>();
}
The component key is simple in itself as you can already see from the collection table mapping
#Embeddable
#AllArgsConstructor
#NoArgsConstructor
public class DemoCompositeKey implements Serializable {
Integer one;
Integer two;
}
And a simple repo.
#Repository
public interface DemoRepository extends JpaRepository<DemoModel, DemoCompositeKey> {}
This setup has been working without any issues for quite a while, but recently I decided I want to audit this entity and added the #Audited tag and added Hibernate Envers to the project.
Inserting the entity initially does not cause any issues and works as expected. But once I try to update one of the elements of the collection the application errors out with the following message (line breaks added by me for readability:
org.springframework.orm.jpa.JpaSystemException: Error accessing field
[java.lang.Integer com.example.demo.DemoCompositeKey.one] by reflection
for persistent property [com.example.demo.DemoCompositeKey#one] : A;
nested exception is org.hibernate.property.access.spi.PropertyAccessException:
Error accessing field [java.lang.Integer com.example.demo.DemoCompositeKey.one]
by reflection for persistent property [com.example.demo.DemoCompositeKey#one] : A
So as you can see from the error, it appears that it tries to compare the Map key string against the composite key and of course, fails.
The original production code for DemoModel has some additional attributes and it made sense from a modeling perspective for it to have a composite key.
Repo with issue: https://github.com/lances101/spring-boot-envers-issue
It seems like Envers is really having trouble with this. Am I defining the Map improperly? Is it a problem because I'm using a composite key?
Update 2021/03/17: swapping for a non-composite primary key does work, but then that is a design change and this is most likely a bug / lacking support of within Envers itself.
Ticket created: https://hibernate.atlassian.net/browse/HHH-14480

Related

can't figure out how to audit for null value in not owned entity using hibernate envers

What my project have:
rsqlParser in order to parse complicated queries
Hibernate envers for audit purposes
Pretty stupid middle developer who don't know how to implement isNull rsql query
I have two Object with strict one-to-one relationship: object A which contains object B, and object B, which contains object A.
In RDS it's looks like object B has an object_a_id field
Object_A entity class
#Entity
#Getter
#Setter
#Audited
#NoArgsConstructor
public class Object_A {
#OneToOne(mappedBy = "object_a")
private Object_B object_b;
}
Object_B entity
#Entity
#Getter
#Setter
#Audited
#NoArgsConstructor
public class Object_B {
#OneToOne
#JoinColumn(
name = "object_a_id",
referencedColumnName = "id",
foreignKey = #ForeignKey(name = "object_b_object_a_fk")
)
private Object_A object_a;
Clearly you see that Object_B OWNS Object_A and when I try to perfom something simple like
return auditProperty.isNull();
I get
This type of relation (object_b) isn't supported and can't be used in queries
I guess I need somehow to make custom query where I add some object_b subselect beforehand but can't figure out how to write it.
You should probably create an issue in the issue tracker(https://hibernate.atlassian.net) with a test case(https://github.com/hibernate/hibernate-test-case-templates/blob/master/orm/hibernate-orm-5/src/test/java/org/hibernate/bugs/JPAUnitTestCase.java) that shows this limitation and ask for an improvement for this.
Usually, in ORM this is handled on the SQL level by introducing an exists subquery like:
where not exists (select 1 from object_b b where b.object_a_id = rootAlias.id)
Not sure how that works exactly in your case, but you could try to do something similar in your query.

How to audit class that has OneToMany unidirectional relationship?

I'm using Spring Data JPA for Auditing. There's a unidirectional relationship between classes Article and File. The Article class looks like this:
#Getter
#Entity
#SuperBuilder(toBuilder = true)
#Table(name = "article")
public class Article extends AuditEntity {
...
#Builder.Default
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true)
#JoinTable(name = "article_additional_file",
joinColumns = #JoinColumn(name = "article_id"),
inverseJoinColumns = #JoinColumn(name = "additional_file_id"))
private List<File> additionalFiles = new ArrayList<>();
...
}
The problem is, when changes occur in the file list (owned files get deleted or added), the modifiedDate field (which is in AuditEntity class and it's annotated with #LastModifiedDate annotation) is not updated (it works with all other fields). And I cannot make it a bidirectional relationship since other classes own the File class as well. So my question is, how to trigger the update of field modifiedDate when changes occur in the file list?
EDIT
I'd prefer not to use Enver, if that's possible. I need to use as little additional libraries as possible
Instead of using #JoinTable use #AuditJoinTable
Info from the hibernate documentation:
When a collection is mapped using these two annotations (#OneToMany + #JoinColumn), 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.

jpa - Multiple #ElementCollection in two different #Embeddable classes

I have an entity with two Embedded classes of the same type and which one has an ElementCollection of the same type two. The business logic is apparently correct, but I am experiencing some problems with lack of knowledge in JPA, I guess.
Let's check my classes:
#Entity
public class Etapa extends EntidadeBase {
#Embedded
private CronogramaDeDesembolso cronogramaDeReceita;
#Embedded
private CronogramaDeDesembolso cronogramaDeDespesa;
}
#Embeddable
public class CronogramaDeDesembolso {
#ElementCollection
private List<Parcela> parcelas;
}
I am receiving the following error log.
Caused by: org.hibernate.HibernateException: Found shared references
to a collection:
nexxus.convenioestadual.dominio.planodetrabalho.etapa.Etapa.cronogramaDeReceita.parcelas
Do you guys have any clue of what is wrong and how can I fix it?
EDIT:
Due comments I did this edit and it do not worked too
#Entity
public class Etapa extends EntidadeBase {
#Embedded
#AttributeOverride(name = "parcelas", column = #Column(name = "parcelasReceita"))
private CronogramaDeDesembolso cronogramaDeReceita;
#Embedded
#AttributeOverride(name = "parcelas", column = #Column(name = "parcelasDespesa"))
private CronogramaDeDesembolso cronogramaDeDespesa;
}
Is there any reason why you have decided to use this structure ? Typically when converting an object to an RDBMS you would need to model the relationships. When you use an embeddable it will add the column (or columns) associated with it to the table. So when you do this normally (not collections) it is fine.
When you do a collection it runs into issues. Mainly there is no way to represent a collection in a single row (since this is an entity you could have many of them so effectively for each object you only have one row) & one column. So when you represent a collection you actually have to have a second table with a column referencing it back to the first. It's really the opposite thinking of a normal object. The collection entries need to know what collection they were associated with instead of the collection being knowledgeable of its entries.
So in some POJO you could have and these....
MyListObject {
//Some implementation of things you want to collect
}
MyClass {
List<MyListObject> myListObject;
}
But to model this in JPA you would need to have these represented by two tables.
Your object that will be in the list.
#Entity
MyListObject {
#ManyToOne
#JoinColumn(name = "MY_CLASS_KEY")
private MyClass myClass;
}
Your object/entity that will have the list.
#Entity
MyClass {
#Id
#Column(name = "MY_CLASS_KEY")
private Long myClassKey;
#OneToMany(mappedBy = "myClass")
private List<MyListObject> myString;
}
I hope this helps.
A quick search on Google turned up this in StackOverflow:
JPA Multiple Embedded fields
It would seem as though you have to do some explicit annotation overriding over the fields within the embeddable class. There are some code examples in the linked answer as well that should give you a good idea of where to go.
Cheers,

Hibernate Inheritance.JOINED generated FK name

I am currently trying to use inheritance within Hibernate and came across InheritanceType.JOINED. I like the idea of concentrating all data in one table and sharing IDs rather than having duplicate columns in all the sub type tables (#MappedSuperClass). But Hibernate automatically generates indexes on my sub class tables on the id column like FK_idx3wiwdm8yp2qkkddi726n8o everytime I initialize my Hibernate singleton. I noticed that by hitting the 64 key limit on my MySQL Table as the names are generated differently on every startup.
What is the proper way to handle this? Can this be fixed by annotations? What else could I try?
I know that there are countless similar Questions on SO but haven't been able to identify one solving my specific problem.
I am not going to disable hbm2ddl.auto during dev mode.
I am using MyISAM. There are no actual Foreign Keys. This is why Hibernate generates default indexes, I think. Anyway, the problem would be identical with InnoDB and real Foreign Keys as the names would still be quite random. Or maybe Hibernate would actually check for existence in this case. I don't really see, why it does not do this on MyISAM tables.
As I hit similar problems before, the solution could also be to specify a name for that single-column index. But how?
Super Class: FolderItem
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class FolderItem implements Comparable<FolderItem>
{
#Id
#GeneratedValue
protected int id;
protected String name;
#OneToOne
#ForeignKey(name = "fkParent")
protected Folder parent;
...
}
Sub Class: Folder
#Entity
public class Folder extends FolderItem
{
#OneToMany(mappedBy = "parent")
#OrderBy(value = "sortOrder")
private List<FolderItem> children;
...
}
What I tried
add #Index to FolderItem.id - this created an index on the FolderItem table as one would expect, but didn't affect the Folder table
copy protected int id; to Folder and tried to add an #Index to it, which resulted in an Exception similar to "duplicate definition of ID"
add #Table(appliesTo = "Folder", indexes = { #Index(name = "fkId", columnNames = { "id" }) }) to Folder class, which actually created my specified index as expected, but still created it's own FK_9xcia6idnwqdi9xx8ytea40h3 which is identical to mine, except for the name
Try #PrimaryKeyJoinColumn(name = "foler_item_id") annotation for Folder class.

Serializing List of POJOs using Hibernate

I have a Java class that is mapped to a database table using JPA. Inside this class I have a List<Code> that I need to store in database.
So the Code class is not mapped into Hibernate. Is there a way to Serialize the List<Code> without mapping the Code class into Hibernate? Thanks in advance.
Error Message:
org.hibernate.exception.SQLGrammarException: could not insert collection [com.app.Account.codes#2]
Problem is that I'm getting error when Hibernate attempts to Serialize my List.
#Entity
#Table (name="account", catalog="database1")
public class Account{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column (name = "id")
private String id
#Column (name = "type")
private String type;
#CollectionOfElements
#Column (name = "codes")
List<Code> codes;
...
...
}
public class Code implements Serializable{
//This is a basic POJO that is not mapped into Hibernate. I just want this object
//to be stored in database.
}
You need to annotate the codes field with #Lob, not with #CollectionOfElements.
See http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html_single/#d0e6099 and the following paragraph.
This is a very fragile way of persisting a list of objects, though, because it uses binary serialization, which might quickly become non-backward compatible if the list or the Code class changes. It's also impossible to query or inspect using database tools.
I would rather map the Code class as an entity or as an embeddable.

Categories

Resources