Spring Data Neo4j MappingException: Field with primary id is null for entity - spring-boot

I have a node entity class that is extending an abstract class which is in turn extending another abstract class as follows:
public abstract class AbstractModel{
#Id
#GeneratedValue(strategy = UuidStrategy.class)
#Convert(UuidStringConverter.class)
protected UUID uuid;
}
public abstract class AbstractBayModel extends AbstractModel{
.....//more protected fields
}
public class Person extends AbstractBayModel{
private String name;
.//more code
.
.
}
#Service
//Has a reference to a repository
public class PersonService{
public void add(Person person){
repository.insert(person);
}
}
now when I invoke the service class's add() method, I am getting the following error.
org.neo4j.ogm.exception.core.MappingException: Field with primary id is null for entity model.Person#41a45331
I have tried to alter the service method as follows and still get the same error:
public void add(Person person){
person.setUuid(UUID.randomUUID();
repository.insert(person);
}
I am using the following spring dependencies
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
</dependency>
Would be mighty obliged if anyone can assist.
regards

I had a similar problem when I wanted to persist an entity with custom-object-properties.
Neo4j-OGM only supports
any primitive, boxed primitive, String or arrays thereof,
essentially anything that naturally fits into a Neo4j node property.
I replaced all properties with the supported analogs leaving id as the only non-naturally fitting field:
#Id #Convert(UuidStringConverter.class)
#GeneratedValue(strategy = UuidStrategy.class)
private UUID uuid;
It worked for me, I hope it helps someone else.

Related

Create SQL view with panache

I'm using
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive-panache</artifactId>
</dependency>
and I would like to generate an SQL view in the same way we generate tables, specifically as follows:
#Entity
public class Invoice extends PanacheEntityBase {
...
}
I've tried that way but with no really success, I can't found anything on this in quarkus' documentation:
#Entity
#Subselect("SELECT i.id as invoiceId, i.appointmentIds FROM invoice i")
public class BilledAppointments extends PanacheEntityBase {
#Column
public Long invoiceId;
#Column
public String appointmentsIds;
}
I figured out that views are not managed by frameworks but only by databases. So it is not really possible to map a view to a "classic" entity, since Panache will interprate this as a table and create one. Moreover, if you create a view manually in your database that references one of your entities, it may be deleted if you adopt the drop-and-create strategy.
A solution is:
#Entity
public class MyTable extends PanacheEntityBase {}
#Entity
#Subselect("SELECT * FROM MyTable")
#Synchronize("MyTable")
public class MyView extends PanacheEntityBase {}
Helful answer
Hibernate documentation

How to list animals by customer id using springboot

I have two entities Customer and Animal, and I need to list the animals of a given customer, the animal entity receives the id of the customer I would like to list all the animals that have this id.
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "customerId", nullable = false)
private Customer customer;
You can use Spring Data JPA dependency which will make the task easy for you.
Add this dependency in your pom file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Assuming your Animal class looks like this:
#Entity
public class Animal {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToOne(cascade=CascadeType.MERGE)
private Customer customer;
//setters and getters
}
your repository class will look like this:
public interface AnimalRepository extends CrudRepository<Animal, Long> {
List<Animal> findByCustomer(Customer customer);
}
In your controller you should have an endpoint that receives the customerId.
That controller will call a service method, that receives the customerId and with that info call a repository method named findByCustomer (or something similar), which is the one that queries the database for the info.
The select will be something like:
select * from animals a where a.customerId = customerId
I recommend you to read about Spring Data JPA which is the tool that use Spring to make that db queries and repository implementation easier.

javax-validation annotation is not working for member variables that is declared as another ObjectType

I have added annotations in the parent class.
It is working fine.
But it is not working in the member variables that is declared as another Object type. It is validating:
orderId from base class
referenceNumber from MarchantApplicationRequest
#NotEmpty annotation at customerRequests field in MerchantApplicationRequest.
But it is not validating customerRoleType in CustomerRequest.
Also, I would like to add #NotBlank annotation in customerRequests. But it is not taking this, though it is taking #NotEmpty annotation.
Class MerchantApplicationRequest
#JsonIngnoreProperties(ignoreUnknown=false)
public class MerchantApplicationRequest extends IomBaseDTO {
#NotEmpty(message="customerRequests is mandatory")
private List<CustomerRequest> customerRequests;
#NotBlank(message="referenceNumber is mandatory")
private String referenceNumber ;
}
Class CustomerRequest
public class CustomerRequest {
#NotBlank(message="customerRoleType is mandatory")
private String customerRoleType ;
}
Controller class
Method where to apply validation:
#PostMapping("/orderDetail")
public void orderDetail(#Valid #RequestBody MerchantApplicationRequest request) {
try {
iOrderService.updateProductDetail(request);
} catch (Exception e) {
// ...
}
}
Here is my JSON payload:
{
"orderId" : 101,
"referenceNumber" : "123",
"customerRequests" : [ {
"customerRoleType" : null
}]
}
I am using in pom.xml of Spring Boot application:
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
</dependency>
If you want to cascade the validation you have to add the #Valid annotation:
#Valid
#NotEmpty(message="customerRequests is mandatory")
private List<CustomerRequest> customerRequests;
Please read more about cascading in the Hibernate Validation documentation: Example 2.11: Cascaded validation
Using bean-validation (javax.validation), you can add validation to elements of collections.
Using Bean-Validation 1.0
#JsonIngnoreProperties(ignoreUnknown=false)
public class MerchantApplicationRequest extends IomBaseDTO {
#NotEmpty(message="customerRequests is mandatory")
#Valid
private List<CustomerRequest> customerRequests;
#NotBlank(message="referenceNumber is mandatory")
private String referenceNumber ;
}
See also:
JSR 303: How to Validate a Collection of annotated objects?
LogicBig Tutorial: Collection Validation
Alternative since Bean-Validation 2.0
In Java 8 generic types can also be validated by prepending the annotation before the type inside the diamond-operator, e.g. <#Valid CustomerRequest>. This is a more concise way of defining per-element validation. It has the same effect like the traditional way, validates every given element as defined in the class (CustomerRequest).
See also:
java/beans validation - collection/map does not contain nulls
Baeldung Tutorial: Validating Container Elements with Bean Validation 2.0

JaversException PROPERTY_NOT_FOUND: Property in derived class not found in abstract class

I have a model looking something like this:
#Data
public abstract class InputFormGroup
{
String id;
String name;
String text;
String type;
}
#Data
public class SimpleInputFormGroup extends InputFormGroup
{
InputControl inputControl;
InputFormAnswerRow answerRow;
}
#Data
public class InputFormPage
{
String id;
String name;
String title;
List<InputFormGroup> inputFormGroups = new LinkedList<>();
}
In effect I have a larger structure that in it has InputFormPages that has one or more InputFormGroups that can be of type SimpleInputFormGroup (and also other types that I have not included in this example).
Im using MongoRepository to persist them and everything looks very nice and I can query the changes on the Entity object and it all looks good.
The only time I have an issue is when I try to query with a path. In this example "..../inputFormPages/0/inputFormGroups/0/answerRow/answers/0".
Then I get the exception because the framework finds a list of InputFormGroup but then when it looks for answerRow in the abstract InputFormGroup base class it does naturally not find it. Is there some way to configure the model so the framework also looks in the derived classes for, in this case, the answerRow parameter? Of is it as simple as the framework doesn't support polymorphism in this way?
The error I got:
"JaversException PROPERTY_NOT_FOUND: Property 'answerRow' not found in class 'com.replior.ebrmockupbackend.model.InputFormGroup'. If the name is correct - check annotations. Properties with #DiffIgnore or #Transient are not visible for JaVers."
And the Query:
JqlQuery query = QueryBuilder.byValueObjectId(batch1.getId(),Batch.class,"inputForm/inputFormSteps/2/inputFormPages/0/inputFormGroups/0/answerRow/answers/0").withChangedProperty("value").build();
List<Change> changes = javers.findChanges(query);
And the version:
<dependency>
<groupId>org.javers</groupId>
<artifactId>javers-spring-boot-starter-mongo</artifactId>
<version>3.10.2</version>
</dependency>
Appreciate any help I can get.
Link to project that exemplifies the issue:
Github project
This issue is fixed in JaVers 3.11.3

Spring Data JPA and Generics

I have an entity that looks like this
#Entity(name = "encounter_pdf_export")
public class EncounterPDFExport<T extends Encounter> implements Serializable {
public static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long pdfExportId;
#Any(metaColumn = #Column(name = "encounter_type"))
#Cascade(CascadeType.ALL)
#AnyMetaDef(
idType = "long",
metaType = "string",
metaValues = {
#MetaValue(value = "FooEncounter", targetEntity = FooEncounter.class)
})
#JoinColumn(name = "encounter_id")
private T encounter;
The abstract type that I'm extending is:
public abstract class Encounter {
public abstract Long getEncounterId();
}
Here is my Spring Data Repository
#Repository
public interface EncounterPDFExportRepository extends PagingAndSortingRepository<EncounterPDFExport, Long> {
EncounterPDFExport findOneByEncounter_encounterId(#Param("encounterId") Long encounterId);
}
I am getting a stack trace when starting up the application related to to the findOneByEncounter_encounterId method:
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [encounter] on this ManagedType [com.iimassociates.distiller.domain.EncounterPDFExport]
at org.hibernate.jpa.internal.metamodel.AbstractManagedType.checkNotNull(AbstractManagedType.java:144)
at org.hibernate.jpa.internal.metamodel.AbstractManagedType.getAttribute(AbstractManagedType.java:130)
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:468)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.getTypedPath(JpaQueryCreator.java:300)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.build(JpaQueryCreator.java:243)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.toPredicate(JpaQueryCreator.java:148)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:88)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:46)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:109)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:88)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:73)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.<init>(PartTreeJpaQuery.java:116)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$CountQueryPreparer.<init>(PartTreeJpaQuery.java:237)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:65)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:100)
I am assuming that either Spring Data JPA doesn't support abstracted/generic fields? If that's the case, would creating a #Query be a sufficient workaround?
Not sure if this will be helpful to anyone, but I did get this working.
Removed the abstract class and made it an interface with a single public getEncounterId() method
Modified FooEncounter to implement the above interface
Removed generics from the EncounterPDFExport class
Modified the encounter field to utilize the above interface rather than a generic
Apparently, I'm hitting some Hibernate bug/limitation when accessing fields within FooEncounter. Accessing Encounter within EncounterPDFExport works OK, though. I modified my Spring Data JPA Repository to look like the following (note the modification from finding by encounter.encounterId vs. just encounter):
#Repository
public interface EncounterPDFExportRepository extends PagingAndSortingRepository<EncounterPDFExport, Long> {
EncounterPDFExport findOneByEncounter(#Param("encounter") Encounter encounter);
}
The Hibernate bug in question seems to be related to https://jira.spring.io/browse/DATAJPA-836.

Resources