Envers, father not revisioned on delete/create of a child - spring-boot

I'm experiencing a problem with revisions on OneToMany relation tables
I have
#Audited
EntityA {
#Id
Integer id;
#OneToMany(mappedBy = "entityA", cascade = CascadeType.ALL)
private List<EntityB> entityBs;
}
and
#Audited
EntityB {
#Id
Integer id;
#Column(name = "ENTITY_A_ID", nullable = false)
private Long entityAId;
#ManyToOne
#JoinColumn(name = "ENTITY_A_ID", insertable = false, updatable = false)
private EntityA entityA;
}
I want that envers creates a new revision of EntityA everytime I delete or create EntityB.
Reading the docs I understand that org.hibernate.envers.revision_on_collection_change is the right configuration property and is by default set to true.
So I can't understand why I don't see any new revision on EntityA when related EntityB is created or deleted.
I tried to create EntityB stand alone and I tried to create via
entityA.getEntityB().add(entityB)
repository.save(entityA)
but no revision of EntityA was saved
Any suggestion?
Thanks in advance
My configuration
spring boot: 2.4.4
hibernate/envers: 5.4.29
jdk 11

Related

Spring hibernate envers is not saving the updated values

Got an issue with Hibernate Envers. I have an entity that is being created and followed by an update in the integration test and envers has created 2 audit entities corresponding to insert and the update but the update entry is same as insert. The updated value is not reflected while the original entity reflects the updated value. No help googling.
Added spring data envers in the build and added the Audited annotation as required
implementation('org.springframework.data:spring-data-envers')
#Audited
public class EmployeeEntity
#Id
#Column(name = "ID", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private final Long id;
#Column(name = "TYPE")
private String type;
#Column(name = "created_by", nullable = true, updatable = false)
#CreatedBy
private String createdBy;
.... few more fields. In the integration test the type filed is updated and the actual employee table has the updated value however the employee_AUD has the same value as that of insert audit entry

relationship row to be deleted on cascade delete

I have the following entities in a one-to-many relationship:
#Entry
class Parent {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Collection<Child> children;
...
}
and
#Entry
class Child {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private int id;
...
#Transient
private Parent parent;
...
}
So – out of these, there are the following 2 entity tables in the database
parent(id, ...)
child(id, ..)
and the relationship table between these two entities-- due to that #OneToMany relationship
parent_child(parent_id, child_id)
suppose parent_id=4 has the child_id=7 and thus parent_child table has the row (parent_id=4, child_id=7).
When i delete child id =7, isn’t the (parent_id=4, child_id=7) in parent_child table supposed to be deleted as part of it? I’m seeing that row in parent_child table even after the corresponding child is deleted from the child table.
I’m using the repository (implementing CrudRepository) for deleting that child.
//////////////
UPDATE:
by parent_child(parent_id, child_id), i'm referring to the relationship table that hibernate is generating behind the scenes to maintain the relationship between parent and child tables.
went into this table out of curiosity directly on SQL. and these are what i'm seeing. i expected (still do) the (parent_id=4, child_id=7) row would disappear now that child_id=7 fell off the face of the Earth. but didn't.
You have to mapped your entity class like this.
#ManyToOne
#JoinColumn(name = "parent_id", referencedColumnName = "id", nullable = false,cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Parent parent;
#OneToMany(mappedBy = "parent",cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Collection<Child> children;
Remove
#Transient
private Parent parent;
And do #ManyToOne mapping in Child Entity.

Cascade.Type = ALL does not delete all "child rows" before trying to delete its own row

I'm trying to delete a user but every row in the database that references the user does NOT get deleted before hibernate tries to remove the user.
I have the same structure for every other entity in the application and it works just fine, all the child rows get deleted first and then the row itself get deleted, but as you see below this is not the case when trying to delete a user. Hibernate goes to this statement :
Hibernate: delete from users where user_id=?
before all comment_votes are deleted. (Posts should also be deleted before as well but I guess the comment_votes error shows up first).
This sequence of sql statements are executed before the error according to the console:
Hibernate: delete from comment_vote where vote_id=?
Hibernate: delete from comment where comment_id=?
Hibernate: delete from comment_vote where vote_id=?
Hibernate: delete from comment where comment_id=?
Hibernate: delete from comment_vote where vote_id=?
Hibernate: delete from comment where comment_id=?
Hibernate: delete from users where user_id=?
This is the error I'm getting:
org.postgresql.util.PSQLException: ERROR: update or delete on table "users" violates foreign key constraint "fkjf73ixvt1jv3wdv4ah0hkpewf" on table "comment_vote"
Detail: Key (user_id)=(2) is still referenced from table "comment_vote".
User.java :
#Entity
#Table(name = "users") // because User is a keyword in some DBs
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id", columnDefinition = "serial")
private Long id;
#NotEmpty
#Column(unique = true)
private String username;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<Post> posts = new ArrayList<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<Comment> comments = new ArrayList<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<CommentVote> comment_votes = new ArrayList<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
#JsonIgnore
private List<PostVote> post_votes = new ArrayList<>();
// getters and setters
}
This is CommentVote.java :
#Entity
#Table(name = "comment_vote")
public class CommentVote {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "vote_id", columnDefinition = "serial")
private Long id;
#Min(value = -1, message = "A vote can not be less than -1")
#Max(value = 1, message = "A vote can not be greater than 1")
#Column(name = "actual_vote")
private int actualVote;
#ManyToOne()
#JoinColumn(name="user_id", nullable=false)
#JsonIgnore
private User user;
// getters and setters
}
I tried with orphanRemoval = true on every child field in User.java but that does not seem to change anything.
You can try to use #OnDelete. As it's stated in the documentation:
... the #OnDelete cascade is a DDL-level FK feature which allows you to remove a child record whenever the parent row is deleted.
So, when annotating the #ManyToOne association with #OnDelete(action = OnDeleteAction.CASCADE), the automatic schema generator will apply the ON DELETE CASCADE SQL directive to the Foreign Key declaration.
Taken this in mind, you can correct your mapping in the following way:
#Entity
#Table(name = "comment_vote")
public class CommentVote {
// ...
#ManyToOne()
#JoinColumn(name="user_id", nullable=false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
// ...
}

OneToMany does not return values saved from other entity

I have entity structure:
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = { CascadeType.ALL })
List<UserAgreement> userAgreements= new ArrayList<>();
}
#Entity
#Table(name = "user_agreements")
public class UserAgreement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.REFRESH)
#JoinColumn(name = "agreement_id")
private Agreement agreement;
}
#Entity
#Table(name = "agreements")
public class Agreement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "agreement", cascade = { CascadeType.ALL })
List<UserAgreement> userAgreements = new ArrayList<>();
}
I am using Spring Boot with JpaRepository. When I use AgreementRepository extends JpaRepository<Agreement, Long> to save Agreement and related UserAgreement, it works well and cascades necessary fields to DB:
agreement.getUserAgreements().add(new UserAgreement(user, agreement, status));
agreementRepository.save(agreement);
However, after save, if try to retrieve user.getActiveUserAgreements(), I get empty list. It does not refresh.
How to force User entity to get List<UserAgreement> which was saved from other side?
From the Wikibooks: OneToMany
The relationship is bi-directional so, as the application updates one
side of the relationship, the other side should also get updated, and
be in sync. In JPA, as in Java in general it is the responsibility of
the application, or the object model to maintain relationships. If
your application adds to one side of a relationship, then it must add
to the other side.
That means you need to assign the UserAgreement to the User when you create the relation.
It looks like many-to-many association. You might probably drop UserAgreement class. Anyway, to support it you have to write helper methods addAgreement(), removeAgreement() etc. See more details here https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/

Hibernate insert causes update of another table

I have a model that looks like this:
#Entity
#Table(name = "A")
class A {
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "A_categories", joinColumns = {
#JoinColumn(name = "A_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "category_id",
nullable = false, updatable = false) })
private List<Category> categories;
}
#Entity
#Table(name = "category")
class Category {
#Id
#Column(name="id")
#GeneratedValue
private Integer id;
#Column(name = "category_name")
private String categoryName;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "categories")
private List<A> a;
}
So there is a many-to-many relationship between A and Category. Now categories are static, and cannot be changed by a user. From the UI, the user will try to save an entity A, and each can have one or more categories. So the JSON that comes back looks a little like this:
{A: {categories: [{id: 1}, {id: 2}, {id: 3}]}}
Now when I try to save this A object (after jackson has unmarshalled to java), I just want entries to be made in the join table, A_categories, for each category the new entity has.
However, the Category entity itself also gets updated. So if you notice, the JSON does not have any category_name, and so the database entry for each Category will also get updated to a null entry for the name.
How can I prevent this from happening?
Two different approaches:
1) Set managed categories before merging.
a.setCategories(readAllByIds(a.getCategories()))
private Collection<Category> readAllByIds(Collection<Category> categories) {
Collection<Category> result = new ArrayList();
for (Category category : categories) {
result.add(entityManager.getReference(Category.class, category.getId()));
}
return result;
}
EntityManager.getReference returns proxy, so the additional benefit is that no database round trips are executed for reading the associated categories.
With this solution you are not merging the deserialized categories into the persistence context, thus Hibernate will not synchronize their state with the database.
2) Do not cascade any operations from A to categories (remove cascade attribute).
This way, neither PERSIST nor MERGE will be cascaded and Hibernate will just use ids of the detached Category instances to store the data into the relationship table.
Sidenote: Generally, cascading REMOVE or ALL in a many-to-many association makes no sense (if you remove an A you probably don't want to remove all the categories it belongs to).
#Column has the attributes insertable and updatable. You can set them to false:
#Entity
#Table(name = "category")
class Category {
#Id
#Column(name="id", insertable=false, updatable=false)
#GeneratedValue
private Integer id;
#Column(name = "category_name", insertable=false, updatable=false)
private String categoryName;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "categories")
private List<A> a;
}
You can try adding this
#Entity
#Table(name = "category")
class Category {
#Id
#Column(name="id")
#GeneratedValue
private Integer id;
#Column(name = "category_name")
private String categoryName;
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "categories", cascade=CascadeType.DETACH)
private List<A> a;
}
with the cascade.DETACH should not save changes when you save A entity, but let me know if is not working to make an example modifying the ManyToMany relationship with this DETACH action

Resources