JaversException ENTITY_INSTANCE_WITH_NULL_ID for ignored id - javers

Using javers 5.11.2 I get the following exception although the id is set to be ignored. Why is that?
JaversException ENTITY_INSTANCE_WITH_NULL_ID: Found Entity instance 'my.package.javers.Leaf' with null Id-property 'id'
Update: I learned that
JaVers matches only objects with the same GlobalId
The id is specified using javax.persistence.Id. However, with each ORM it is possible to have an entity with a collection, then add a new element without id to that entity and then save it (CascadeType.Persist).
Is there any way to compare objects with javers in such a case?
Example (used lombok for boiler plate code).
The leaf:
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Data
#Entity
public class Leaf {
#DiffIgnore <============ id is ignored
#Id
private Long id;
private String color;
}
The tree:
#Builder
#Data
#Entity
public class Tree {
#Id
private Long id;
private String name;
#OneToMany
private Set<Leaf> leafs;
}
Test adds a leaf to the oakSecond without an id set. The diff cannot be made. An Exception is thrown.
#Test
public void testCompare_AddLeafToTree() {
Leaf leaf = Leaf.builder().id(1L).color("11").build();
Set<Leaf> leafsOfOakFirst = new HashSet<>();
leafsOfOakFirst.add(leaf);
Tree oakFirst = Tree.builder().id(1L).name("oakFirst").build();
oakFirst.setLeafs(leafsOfOakFirst);
Set<Leaf> leafsOfOakSecond = new HashSet<>();
leafsOfOakSecond.add(leaf);
leafsOfOakSecond.add(Leaf.builder().color("12").build());
Tree oakSecond = Tree.builder().id(1L).name("oakFirst").build();
oakSecond.setLeafs(leafsOfOakSecond);
Javers javers = JaversBuilder.javers().build();
Changes changes = javers.compare(oakFirst, oakSecond).getChanges();
assertThat(changes).isNotEmpty();
}
Same with the following definition of the Javers instance:
EntityDefinition leafEntityDefinition = EntityDefinitionBuilder.entityDefinition(Leaf.class).withIgnoredProperties("id").build();
Javers javers = JaversBuilder.javers().registerEntity(leafEntityDefinition).build();

You can't pass an Entity to Javers with null Id because it would be non-identifiable. If you use Hibernate to generate your Ids, make sure that you pass your object to javers.commit() after hibernate are done with its job. That's how the #JaversSpringDataAuditable aspect works.
Alternatively, you can model those objects with unstable IDs as Value Object in Javers.

Related

How to mock jpa repository save(without return object) and modify id of input object

I use Spring Data JPA in my project and my model code is here:
#Getter
#Setter
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#NoArgsConstructor
#AllArgsConstructor
#Builder
public class Activity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String number;
//...
}
My service code is here:
activityRepository.save(activity);//activity has no data in field id
activity.setNumber("D"+activity.getId()); //A
activityRepository.save(activity);
And my mock code is here:
when(activityRepository.save(activity)).thenReturn(tempActivity);
//invoke service method
verify(activityRepository).save(activity);
The question is that I have always been met with the Null Pointer Exception in code A.So how can I mock this repository save method?
Two points:
Use the returned object from activityRepository.save(activity)
activity = activityRepository.save(activity);//activity has no data in field id
activity.setNumber("D"+activity.getId()); //A
activity = activityRepository.save(activity);
Return a modified version, that is returned by the mock
tempActivity = activity.toBuilder().id(5).build();
when(activityRepository.save(activity)).thenReturn(tempActivity);
//invoke service method
verify(activityRepository).save(activity);

JPA Entity class which is not mapped to any table

I am using a entity class for mixing two/three table columns in one entity to hold an outcome of SYS_REFCURSOR in oracle
This allows me to have single class which is not mapped to any table but it still is an Entity
#Data
#Entity
#NoArgsConstructor
class EmployeeDetails {
#Id
#Column("emp_id")
String empId;
#Column("job_name")
String jobName;
#Column("dept_name")
String deptName;
//Future requirement
//String updatedBy
}
Now I have an additional requirement, to add who last modified the employee table, I don't want modify the procedure now, the procedure is being re-used in another background procedure and batch jobs.
My question is, is it possible to use #ManyToOne on this class which is obviously not mapped to any table
If not how do avoid manually looping a child array list, is there a ready made option in JPA or spring boot to achieve that.
Or what will be the smartest/recommended way to bring the below Entity into this class
#Data
#Entity
#NoArgsConstructor
#Table(name="app_users")
class AppUsers {
#Id
#Column(name="user_id")
String userId;
#Column
String userName;
}
#Transient, check how this annotation works it will resolve the issue, you need to understand working of #Transient
My spring boot 2.6.2 EntityManager code is as follows
q = em.createStoredProcedureQuery("MY_PROC",EmployeeDetails.class);
q.registerStoredProcedureParameter("OUT_REFC", void.class, ParameterMode.REF_CURSOR);
q.execute();
q.getResultList()
I have modified my class EmployeeDetails as below
#Data
#Entity
#NoArgsConstructor
class EmployeeDetails {
#Id
#Column("emp_id")
String empId;
#Column("job_name")
String jobName;
#Column("dept_name")
String deptName;
#OneToOne
#JoinColumn(
name="user_id",
referencedColumnName="emp_id",
insertable=false,
updatable=false,
nullable=true
)
AppUsers updatedBy;
}
The log prints Hibernate two times one after one as below, first it calls the proc and then it calls the select query, so, I did not wrote that SQL myself, the JPA layer is taking care of it
Hibernate:
{call MY_PROC(?)}
Hibernate:
select
...
...
from app_users
where user_id=?
so, my expectation achieved and I am getting the values

Hibernate5Module doesn't fetch OneToMany collection

First of all I have had two related entities mapped with bidirectional OneToMany:
#Data
#Entity
#Table(name = "tbl_parent")
public class Parent {
#Id
#Column(name = "parent_id")
private Long id;
...
#OneToMany(mappedBy = "parent")
private List<Child> children = new ArrayList<>();
}
and
#Data
#Entity
#Table(name = "tbl_child")
#ToString(exclude = "parent")
public class Child {
#Id
#Column(name = "child_id")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private Parent parent;
}
However, I had StackOverFlow for method findAll() for Parent entity as far as infinite loop occurred during serialization.
Then I added property in application.yml:
spring:jsp:open-in-view: false
And had another problem with serialization. To solve it I added dependency in build.gradle:
implementation "com.fasterxml.jackson.datatype:jackson-datatype-hibernate5"
And configuration class:
#Configuration
public class JacksonConfiguration {
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
#Bean
public Jdk8Module jdk8TimeModule() {
return new Jdk8Module();
}
#Bean
public Hibernate5Module hibernate5Module() {
return new Hibernate5Module();
}
}
Now I have no error but list in parent entity is always null.
What I'm doing wrong? How can I fetch all related children entities without StackOverflow?
#Data annotation from Lombok can cause stackoverflows due to circular dependencies between classes if both classes have #Data and collections/toStrings mentioning each other.
See Lombok.hashCode issue with "java.lang.StackOverflowError: null"
The issue is quite simple: you cannot hope to serialize both sides of a bidirectional association. If you try to do that, you'll get a JSON representation of Parent with a children property, and each object in children will have a parent property, with each parent containing a children property, and so on, ad nauseam.
From what you describe, I'm assuming you want to opt out of serializing the parent property, in which case you should put #JsonIgnore on Child.parent.
Also, since you enabled the HibernateModule, which by default ignores lazy properties, you should either disable it, enable its FORCE_LAZY_LOADING feature, or configure Parent.children to be eagerly fetched. The third option also eliminates the need for #JsonIgnore.
(all three options have potentially far-reaching consequences, so you need to decide which one works best for your use case; also note that having to use open-jpa-in-view is a code smell, because forcing the lazy load happens outside of any transactional context)

spring jpa projection nested bean

is it possible to have a projection with nested collection with Spring JPA?
I have the following 2 simple entity (to explain the problem)
#Entity
#Table(name = "person")
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList = new ArrayList<>();
}
#Entity
#Table(name = "address")
public class Address implements Serializable {
private Integer id;
private String city;
private String street;
}
Is it possible to have a projection of Person with following attributes filled in ? {person.name, address.city}
I might be wrong in semantics of word Projection. but the problem is what i need to achieve. Maybe it is not possible with Projection, but is there another way to achieve the end goal? Named Entity graph perhaps ?
P.S. please suggest a solution for Spring JPA not Spring Jpa REST
thanks in advance
You're right, Entity Graphs serve this exact purpose - control field loading.
Create entity graphs dynamically from the code or annotate target entities with Named Entity Graphs and then just use their name.
Here is how to modify your Person class to use Named Entity Graphs:
#Entity
#Table(name = "person")
#NamedEntityGraph(name = "persion.name.with.city",
attributeNodes = #NamedAttributeNode(value = "addressList", subgraph = "addresses.city"),
subgraphs = #NamedSubgraph(name = "addresses.city", attributeNodes = #NamedAttributeNode("city")))
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList;
}
And then when loading your person:
EntityGraph graph = em.getEntityGraph("person.name.with.city");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
return em.find(Person.class, personId, hints);
The same applies for queries, not only em.find method.
Look this tutorial for more details.
I think that that's not usual scenario of Data JPA usage. But you can achieve your goal with pure JPQL:
SELECT a.street, a.person.name FROM Address a WHERE …
This solution has 2 drawbacks:
It forces you to have bidirectional relationship Address ←→ Person
It returns List
Another solution (and that's preferred JPA way) is to create DTO like this:
class MyPersonDTO {
private String personName;
private List<String> cities;
public MyPersonDTO(String personName, List<Address> adresses) {
this.personName = personName;
cities = adresses
.stream()
.map(Address::getCity)
.collect(Collectors.toList());
}
}
And the execute JPQL query like this:
SELECT NEW package.MyPersonDTO(p.name, p.addressList) FROM Person p WHERE …
Return type will be List<MyPersonDTO> in that case.
Of course you can use any of this solutions inside #Query annotation and it should work.

Why is JPA query so slow?

I am implementing queries in my web application with JPA repositories. The two main tables I am querying from are FmReportTb and SpecimenTb.
Here are the two entity classes (only important attributes are listed).
//FmReportTb.java
#Entity
#Table(name="FM_REPORT_TB")
public class FmReportTb implements Serializable {
#Column(name="ROW_ID")
private long rowId;
#Column(name="FR_BLOCK_ID")
private String frBlockId;
#Column(name="FR_FULL_NAME")
private String frFullName;
#OneToOne
#JoinColumn(name="SPECIMEN_ID")
private SpecimenTb specimenTb;
FmReportTb has OneToOne relationship with SpecimenTb.
#Entity
#Table(name="SPECIMEN_TB")
public class SpecimenTb implements Serializable {
private String mrn;
#OneToOne(mappedBy="specimenTb", cascade=CascadeType.ALL)
private FmReportTb fmReportTb;
The query I am working on is to find all records in FmReportTb and show a few attributes from FmReportTb plus mrn from SpecimenTb.
Here is my JPA repository for FmReportTb:
#Repository
public interface FmReportRepository extends JpaRepository<FmReportTb, Long> {
#Query("select f from FmReportTb f where f.deleteTs is not null")
public List<FmReportTb> findAllFmReports();
Since, I am only showing part of the attributes from FmReportTb and one attribute from SpecimenTb, I decided to create a Value Object for FmReportTb. The constructor of the VO class assigns attributes from FmReportTb and grabs mrn attribute from SpecimenTb based on the OneToOne relationship. Another reason for using VO is because table FmReportTb has a lot of OneToMany children entities. For this particular query, I don't need any of them.
public class FmReportVO {
private String frBlockId;
private Date frCollectionDate;
private String frCopiedPhysician;
private String frDiagnosis;
private String frFacilityName;
private String frFullName;
private String frReportId;
private String filepath;
private String mrn;
public FmReportVO(FmReportTb fmReport) {
this.frBlockId = fmReport.getFrBlockId();
this.frCollectionDate = fmReport.getFrCollectionDate();
this.frCopiedPhysician = fmReport.getFrCopiedPhysician();
this.frDiagnosis = fmReport.getFrDiagnosis();
this.frFacilityName = fmReport.getFrFacilityName();
this.frFullName = fmReport.getFrFullName();
this.frReportId = fmReport.getFrReportId();
this.mrn = fmReport.getSpecimenTb().getMrn();
}
I implemented findall method in servicebean class to return a list of FmReportTb VOs.
//FmReportServiceBean.java
#Override
public List<FmReportVO> findAllFmReports() {
List<FmReportTb> reports = fmReportRepository.findAllFmReports();
if (reports == null) {
return null;
}
List<FmReportVO> fmReports = new ArrayList<FmReportVO>();
for (FmReportTb report : reports) {
FmReportVO reportVo = new FmReportVO(report);
String filepath = fileLoadRepository.findUriByFileLoadId(report.getFileLoadId().longValue());
reportVo.setFilepath(filepath);
fmReports.add(reportVo);
}
return fmReports;
}
Lastly, my controller looks like this:
#RequestMapping(
value = "/ristore/foundation/",
method = RequestMethod.GET,
produces = "application/json")
public ResponseEntity<List<FmReportVO>> getAllFmReports() {
List<FmReportVO> reports = ristoreService.findAllFmReports();
if (reports == null) {
return new ResponseEntity<List<FmReportVO>>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<List<FmReportVO>>(reports, HttpStatus.OK);
}
There are about 200 records in the database. Surprisingly, it took almost 2 full seconds to retrieve all the records in JSON. Even though I did not index all the tables, this is way too slow. Similar query takes about probably a few ms on the database directly. Is it because I am using Value Objects or JPA query tends to be this slow?
EDIT 1
This may have to do with the fact that FmReportTb has almost 20 OneToMany entities. Although the fetchmode of these child entities are set to LAZY, JPA Data repository tends to ignore the fetchmode. So I ended up using NamedEntityGraph to specify the attributes EAGER. This next section is added to the head of my FmReportTb entity class.
#Entity
#NamedEntityGraph(
name = "FmReportGraph",
attributeNodes = {
#NamedAttributeNode("fileLoadId"),
#NamedAttributeNode("frBlockId"),
#NamedAttributeNode("frCollectionDate"),
#NamedAttributeNode("frDiagnosis"),
#NamedAttributeNode("frFullName"),
#NamedAttributeNode("frReportId"),
#NamedAttributeNode("specimenTb")})
#Table(name="FM_REPORT_TB")
And then #EntityGraph("FmReportGraph") was added before the JPA repository query to find all records. After doing that, the performance is improved a little bit. Now fetching 1500 records only takes about 10 seconds. However, it still seems too slow given each json object is fairly small.
Answering for the benefit of others with slow JPA queries...
As #Ken Bekov hints in the comments, foreign keys can help a lot with JPA.
I had a couple of tables with a many to one relationship - a query of 100,000 records was taking hours to perform. Without any code changes I reduced this to seconds just by adding a foreign key.
In phpMyAdmin you do this by creating a Relationship from the "many" table to the "one" table. For a detailed explanation see this question: Setting up foreign keys in phpMyAdmin?
and the answer by #Devsi Odedra

Resources