Recursive compare - Hibernate Audit object + Javers - hibernate-envers

I have following simple data model:
TestUser:
#Entity
#Table(name = "TEST_USER")
#Audited
public class TestUser implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME", nullable = false)
#NotEmpty
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "testUser", fetch = FetchType.LAZY)
#JsonIgnore
#OrderBy
private Set<TestUserAddress> addresses = new HashSet<TestUserAddress>();
// ...
}
TestUserAddress:
#Entity
#Table(name = "TEST_USER_ADDRESS")
#Audited
public class TestUserAddress implements Serializable {
private static final long serialVersionUID = -2071806522133475539L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME", nullable = false)
#NotEmpty
private String name;
#Column(name = "CITY", nullable = false)
#NotEmpty
private String city;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "TEST_USER_ID")
#JsonBackReference
private TestUser testUser;
// ...
}
I have performed one operation to update TestUserAddress object - for example I changed TestUserAddress name:
[TestUser]:
- id = 1
- name = "testName"
- [TestUserAddress]:
- name = "testAddressName" ===> changed to "testAddressName2"
- city = "testAddressCity"
I developed some code to compare TestUser objects, like below:
// testUserId - ID of the object that I would like to compare
// startRevision/endRevision - revisions to compare - stored in Hibernate Envers audit tables
AuditReader testUserReader = AuditReaderFactory.get(em);
TestUser testUserStart = testUserReader.find(TestUser.class, testUserId, startRevision.intValue());
TestUser testUserEnd = testUserReader.find(TestUser.class, testUserId, endRevision.intValue());
Javers javers = JaversBuilder.javers().build();
Diff diff = javers.compare(testUserStart, testUserEnd);
but above code is not showing me change done for TestUserAddress.
If I run below code:
// testUserId - ID of the object that I would like to compare
// startRevision/endRevision - revisions to compare - stored in Hibernate Envers audit tables
AuditReader testUserReader = AuditReaderFactory.get(em);
TestUser testUserStart = testUserReader.find(TestUser.class, testUserId, startRevision.intValue());
Set<TestUserAddress> testUserAddressStart = testUserStart.getAddresses();
TestUser testUserEnd = testUserReader.find(TestUser.class, testUserId, endRevision.intValue());
Set<TestUserAddress> testUserAddressEnd = testUserEnd.getAddresses();
Javers javers = JaversBuilder.javers().build();
Diff diff = javers.compare(testUserStart, testUserEnd);
Diff diff2 = javers.compare(testUserAddressStart, testUserAddressEnd);
diff2 variable is showing me all changes done for TestUserAddress, however I would like to perform compare operation on root object to get the full lists done for object field and all child objects.
Do you know how can I do it?
I will appreciate any help. Thank you.

Related

JPA Repository Query on additional table #ManytoMany

I want to do select like this in my jpa spring repository
SELECT sicknes_id, count(symptomp_id) as ilosc FROM symptomp_sicknes where symptomp_id IN (1,2) group by sicknes_id Order by ilosc DESC;
My enitity
#Entity
#Table(name = "symptomp")
public class Symptomp {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "symptomp_id")
private Long symptomp_id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#ManyToMany(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}, fetch = FetchType.LAZY)
#JoinTable(name = "symptomp_sicknes",joinColumns = #JoinColumn(name = "symptomp_id"),inverseJoinColumns = #JoinColumn(name = "sicknes_id"))
private Set<Sicknes> sicknes = new HashSet<>();
#Entity
#Table(name = "sicknes")
public class Sicknes {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "sicknes_id")
private Long sicknes_id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#ManyToOne(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH}, fetch = FetchType.LAZY)
#JoinColumn(name = "speciesId")
private Species species;
My Symptomp repository:
public interface SymptompRepository extends JpaRepository<Symptomp, Long> {
#Query("select p from Symptomp p where name like ?1%")
public List<Symptomp> findAllBySymptompName(String symptomp);
public Symptomp findByName(String symptomp);
public List<Symptomp> findByNameIn(List<String> list);
Integer countDistinctSymptompByName(String id);
}
How I can create this select in my JPA repository?
I try get value like in select but i got error mapping bean.
You can get query result as List<Object[]> using nativeQuery=true parameter
#Query("SELECT sicknes_id, count(symptomp_id) as ilosc FROM symptomp_sicknes where symptomp_id IN (1,2) group by sicknes_id Order by ilosc DESC", nativeQuery=true)
List<Object[]> getQueryResult();
Other option is to create dto class with appropriate constructor
public class QueryResultDto {
Long sicknesId;
Long count;
public QueryResultDto(Long sicknesId, Long count) {
this.sicknesId = sicknesId;
this.count = count;
}
}
Then using JPQL
#Query("select new yourproject.dtopath.QueryResultDto(...")
List<QueryResultDto> getQueryResult(#Param("symptompIds") List<Long> symptompIds);
If you want to avoid a native Query the best way is to create an Entity for that JoinTable. Then you can query it easily. Additional benefit if this is that if in future a requirement will pop up that you have to store additional attributes in that relation you will have the Entity already there to do that easily.

JPARepository - sometimes create duplicate records

I have the following entity class.
#Data
#EqualsAndHashCode(callSuper=false)
#ToString(callSuper=true)
#Entity
#Table(name = "storeitem")
public class StoreItem extends SellableStoreItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToOne
#JoinColumn(name = "store_id")
private Store store;
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToOne
#JoinColumn(name = "storeitemcategory_id", nullable = true)
private StoreItemCategory storeItemCategory;
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "storeItem")
private List<StoreItemTranslation> storeItemTranslationList = new ArrayList<>();
#EqualsAndHashCode.Exclude
#OneToMany(mappedBy = "storeItem",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<StoreItemOptionCollectionSelection> storeItemOptionCollectionSelections = new ArrayList<>();
#EqualsAndHashCode.Exclude
#Column(name = "uid")
private UUID uid = UUID.randomUUID();
#EqualsAndHashCode.Exclude
#CreationTimestamp
#Column(name = "createddate", nullable = false)
private LocalDateTime createdDate;
#EqualsAndHashCode.Exclude
#Column(name = "iscurrent", nullable = false)
private boolean isCurrent = true;
And in my service layer, I do the following.
private StoreItemResponse setStoreItemCreate(StoreItemDTO storeItemDTO, Store store, StoreItemCategory storeItemCategory) {
StoreItem storeItem = new StoreItem(storeItemDTO, store, storeItemCategory);
if(storeItemDTO.getUid() != null){
storeItem.setUid(storeItemDTO.getUid());
}
storeItem = storeItemRepository.save(storeItem);
// Create Translations for store Item
for (TranslationDTO translationDTO : storeItemDTO.getTranslationDTOs()) {
StoreItemTranslation translation = new StoreItemTranslation(translationDTO, storeItem);
storeItemTranslationRepository.save(translation);
}
return new StoreItemResponse(storeItem.getId(), DtoResponseStatus.OK);
}
However, when testing the code, I notice that there are times (not often but some cases) I see duplicate records (with different id) are being saved to database. And the duplicates are saved 2ms apart so I suspect storeItem = storeItemRepository.save(storeItem); created the duplicate records.
Why would this happen?

JPA cascade - deletion is not working when id is different in join table

I am using spring JPA to insert and delete the records in these tables. I can able to insert the record, but deletion is not happening due to different id's in emp_workstation table. If both employee_id and workstation_id were same then delete is working fine, but if both the id's were not same then delete not happening in employee table and emp_workstation table (cascade has set to 'ALL'). Does anybody face this problem before or know how to solve this issue?
Employee.java
#Setter
#Getter
#Builder(toBuilder = true)
#AllArgsConstructor(access = AccessLevel.PUBLIC)
#NoArgsConstructor(access = AccessLevel.PUBLIC)
#Entity
#Table(name = "employee")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_employee_id")
#SequenceGenerator(name = "sq_employee_id", sequenceName = "sq_employee_id", allocationSize = 1)
private Long id;
#Column(name = "employee_name", length = 50)
private String name;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "emp_workstation", joinColumns = {
#JoinColumn(name = "employee_id") }, inverseJoinColumns = { #JoinColumn(name = "workstation_id") })
private Workstation workstation;
}
Workstation.java
#Setter
#Getter
#Builder(toBuilder = true)
#AllArgsConstructor(access = AccessLevel.PUBLIC)
#NoArgsConstructor(access = AccessLevel.PUBLIC)
#Entity
#Table(name = "workstation")
public class Workstation {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sq_workstation_id")
#SequenceGenerator(name = "sq_workstation_id", sequenceName = "sq_workstation_id", allocationSize = 1)
private Long id;
#Column(name = "workstation_name", nullable = false, length = 100)
private String name;
#Column(name = "workstation_area", nullable = false, length = 100)
private String name;
#OneToOne(mappedBy = "workStation")
private Employee employee;
}
Example: In EMP_WORKSTATION table, if EMPLOYEE_ID and WORKSTATION_ID were same then deletion is working fine. but if both id were different then deletion is not working.
Thanks

spring data - how to filter users of a particular userProfile by userProfileType?

My User entity follows:
#Entity
#Table
public class User {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
// non relevant attributes
#ManyToMany(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "user2userProfile",
joinColumns = #JoinColumn(name = "userId"),
inverseJoinColumns = #JoinColumn(name = "userProfileId"))
private Set<UserProfile> userProfileSet;
// getters and setters
}
The UserProfile entity follows:
#Entity
#Table(name = "userProfile")
public class UserProfile {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#Column(name = "type", length = 15, unique = true, nullable = false)
private String type = UserProfileType.USER.getUserProfileType();
// constructors, getter and setter
}
The UserProfileType enum is
public enum UserProfileType {
USER("USER"),
READER("READER"),
WRITER("WRITER"),
ADMIN("ADMIN");
// constructor and getter
}
My UserJpaRepository is:
public interface UserJpaRepository extends JpaRepository<User, String> {
// non relevant code
List<User> findAllByUserProfileType(UserProfileType userProfileType);
}
The way it stands now, I get the following error message on the console:
org.springframework.data.mapping.PropertyReferenceException: No property userProfileType found for type User!
What is the correct declaration on UserJpaRepository to get a list of users that have a specific UserProfileType (i.e. a list of all users that have a UserProfile of type READER)?
I don't really understand why you need to have a many to many relationship from your user to your user profile.
So if we would correct that to a many to one relationship like:
in User:
#OneToMany(mappedBy = "user")
private Set<UserProfile> profiles;
in UserProfile:
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
you could just setup a search by String type in your UserProfileRepository:
List<UserProfile> findAllByType(String type);
If you now iterate the List you get, you can get all users with a certain UserProfileType:
List<User> users = userProfileRepository.findAllByType(UserProfileType.USER.toString()).stream().map(profile -> profile.getUser()).collect(Collectors.toList());

Exception while persisting JPA object in DB having one to many relation

hi have two tables in picture table a and table b as follows :
#Entity
#Table(name = "A")
public class A implements Serializable {
#Id
#SequenceGenerator(name = "JOURNAL_CATEGORY_ID_GENERATOR", allocationSize = 1, sequenceName = "clm_jounal_category_config_seq")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "JOURNAL_CATEGORY_ID_GENERATOR")
#Column(name = "CLAIM_ID")
private String claimId;
#Column(name = "name")
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "country")
private List<ClaimDTLS> claimDetails;
}
B Primary Key:
#Embeddable
public class BPK implements Serializable {
#Column(name = "code")
private String code;
#Column(name = "CLAIM_ID")
private String claimId;
}
B Entity:
#Entity
#Table(name = "B")
public class B implements Serializable {
#EmbeddedId
protected BPK bpk;
#Column(name = "name")
private String name;
#MapsId("country_code")
#JoinColumn(name = "claimId", referencedColumnName = "claimId", insertable = false, updatable = false)
#ManyToOne
private A a;
}
when i try to persist object of A type in Db the value of table b claim id is not set and is intialized with zero.
Also primary key of table A is generated with a oracle sequence.
any help will be welcomed.
thanks in advance
Sequence values are numbers and when JPA use them as a generator it call the setter method of the entity PK. Now, you defined your PK as a string while you use a sequence and so no matching setter can be found. Change the type of you PK to be Long and things shall work