Spring trying to set audit fields on null object - spring

Spring-data-mongodb is trying to add createdDate to a null object and fails to do so. How do I configure spring audit to ignore null objects so it does not try to add audit fields to it?
I have an abstract class with all the audit info. Two classes (A,B) extend this abstract class and one of those two classes (A) has a reference to the other class (B) (that can be nullable). If I try to save an object of A with a null reference to B. It all fails because spring is trying to add audit info to the B null reference.
public abstract class AA {
#Id
private String id;
#Version
private Long version;
#CreatedDate
private LocalDateTime createdDate;
#LastModifiedDate
private LocalDateTime LastModifiedDate;
}
public class A extends AA {
private String name;
#Nullable
private B b;
}
public class B extends AA {
private String name;
}
public class ControllerA {
private AMongoRepository aMongoRepo;
public void saveSomeA(String name) {
A a = new A();
a.setName("Some Name");
a.setB(null);
aMongoRepo.save(a); // <-- Fails can not set createdDate on B null
}
}
The error message I get is the following.
org.springframework.data.mapping.MappingException: Cannot lookup property B A.b on null intermediate! Original path was: b.createdDate on A.
A quick google search brought me to jira.spring.io, which is the same issue I am having.
Any idea on how to cleanly handle this issue? Maybe move from inheritance to composition? Or should I just be patient an wait on the new version?

Related

JPA findTopBy cannot work on spring boot 2.6.3

I am studying spring-boot and JPA. the spring-boot version is 2.6.3, which is the latest current version.
I defined my entity class like:
#Entity
public class Author implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int age;
private String name;
private String genre;
// ignore the getters, setters and toString()
And defined with one projection DTO interface.
public interface AuthorDto {
public String getName();
public int getAge();
}
then defined the repository as follows:
#Repository
public interface AuthorRepository extends JpaRepository<Author, Long> {
Author findFirstByGenre(String genre);
AuthorDto findTopByGenre(String genre);
}
the method findFirstByXXX worked well, but the second method, AuthorDto findTopByGenre(String genre);, could not work well, and was ended up with the following error in the log:
Reason: Failed to create query for method public abstract com.bookstore.dto.AuthorDto com.bookstore.repository.AuthorRepository.findTopByGenre(java.lang.String)! Cannot invoke "java.lang.Class.isInterface()" because "typeToRead" is null; nested exception is java.lang.IllegalArgumentException: Failed to create query for method public abstract com.bookstore.dto.AuthorDto com.bookstore.repository.AuthorRepository.findTopByGenre(java.lang.String)! Cannot invoke "java.lang.Class.isInterface()" because "typeToRead" is null
I tested the same code on spring-boot 2.1.4.RELEASE, it could work well.
I tried to find any official documents on this, but didn't find useful thread on this.

How to solve Spring Boot findBy method with underscore variable

When I run the below project, I receive the following error. How can I fix it?
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract com.example.pharmanic.model.Rdhs_Hospital_Current_Stock com.example.pharmanic.repositories.Rdhs_Hospital_Current_StockRepository.findBysr_no(java.lang.String)! No property sr found for type Rdhs_Hospital_Current_Stock!
This is my Rdhs_Hospital_Current_Stock model class.
#Entity
#Data
#Table(name = "Rdhs_Hospital_Current_Stock")
public class Rdhs_Hospital_Current_Stock {
#Id
private Long batchId;
private int quantity;
private String expiredate;
#ManyToOne
private Hospital_By_Rdhs hospital_by_rdhs;
#ManyToOne
#JoinColumn(name = "sr_no", nullable = false, referencedColumnName = "sr_no")
private Medicine medicine;
}
sr_no is the foreign key of the Medicine table.
This is my Medicine entity:
#Data
#Entity
public class Medicine {
private #Id String sr_no;
private String name;
private String side_effect;
private String description;
public Medicine() {
}
public Medicine(String sr_no, String name, String side_effect, String description) {
this.sr_no = sr_no;
this.name = name;
this.side_effect = side_effect;
this.description = description;
}
}
When I use sr_no with my findBy() function:
#GetMapping("/rhstock/{id}")
ResponseEntity<?> getMedicine(#PathVariable String id){
Optional<Rdhs_Hospital_Current_Stock> rdhs_hospital_current_stock = Optional.ofNullable(rdhs_hospital_current_stockRepository.findBysr_no(id));
return rdhs_hospital_current_stock.map(response->ResponseEntity.ok().body(response)).orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
This is my repository:
public interface Rdhs_Hospital_Current_StockRepository extends JpaRepository<Rdhs_Hospital_Current_Stock,Long> {
Rdhs_Hospital_Current_Stock findBysr_no(String id);
}
Inspired from: Spring-Data-Jpa Repository - Underscore on Entity Column Name
The underscore _ is a reserved character in Spring Data query derivation (see the reference docs for details) to potentially allow manual property path description.
Stick to the Java naming conventions of using camel-case for member variable names and everything will work as expected.
Change sr_no to srNo.
Update repository function
Rdhs_Hospital_Current_Stock findBymedicine_srNo(String id);
I solve this error. I change in Reposity Interface & Controller class like as below
Repository Interface -:
#Query(value="select * from Rdhs_Hospital_Current_Stock h where h.sr_no = :sr_no",nativeQuery=true)
List<Rdhs_Hospital_Current_Stock> findBySr_no(#Param("sr_no")String sr_no);
Controller class -:
#RequestMapping(value = "/rhstocksr/{sr_no}", method = RequestMethod.GET)
List<Rdhs_Hospital_Current_Stock> getBatchByMedicine(#PathVariable("sr_no") String sr_no) {
return rdhs_hospital_current_stockRepository.findBySr_no(sr_no);
}
To keep using a derived query you may change sr_no to srNo.
You could solve this by starting using a native query, but my advice is to use derived querys everytime as it is possible to use it.

How to map java class to SQL view without id in spring data jpa?

Here's my class
public class JobSteps {
private String jobNo;
private String stepNumber;
private String stepDescription;
}
And here's my view
JobNo CHAR(7),
StepNumber TEXT,
StepDescription TEXT
I tried to annotate my class with Entity, specifying the table name, but spring keeps complaining about id, I added #Id to JobNo, but spring complains about type. Is there a way to manage it? I have no access to the view and can't change anything there.
Fo example, You can add JobStepId class and wrap tit with your char column.
#Embeddable
class JobStepId implements Serializable {
String jobIdString;
Integer jobId;
//implements equals and hashCode
}
Then your class could imbed it:
#IdClass(JobStepId.class)
public class JobSteps {
#Id
private JobStepId jobNo;
private String stepNumber;
private String stepDescription;
}

Spring JPA saving distinct entities with composite primary key not working as expected, updates same entity

I have a logic that saves some data and I use spring boot + spring data jpa.
Now, I have to save one object, and after moment, I have to save another objeect.
those of object consists of three primary key properties.
- partCode, setCode, itemCode.
let's say first object has a toString() returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0021, qty=1.0, sortNo=2, item=null)
and the second object has a toString returning below:
SetItem(partCode=10-001, setCode=04, itemCode=01-0031, qty=1.0, sortNo=2, item=null)
there is a difference on itemCode value, and itemCode property is belonged to primary key, so the two objects are different each other.
but in my case, when I run the program, the webapp saves first object, and updates first object with second object value, not saving objects seperately.
(above image contains different values from this post question)
Here is my entity information:
/**
* The persistent class for the set_item database table.
*
*/
#Data
#DynamicInsert
#DynamicUpdate
#Entity
#ToString(includeFieldNames=true)
#Table(name="set_item")
#IdClass(SetGroupId.class)
public class SetItem extends BasicJpaModel<SetItemId> {
private static final long serialVersionUID = 1L;
#Id
#Column(name="PART_CODE")
private String partCode;
#Id
#Column(name="SET_CODE")
private String setCode;
#Id
#Column(name="ITEM_CODE")
private String itemCode;
private Double qty;
#Column(name="SORT_NO")
private int sortNo;
#Override
public SetItemId getId() {
if(BooleanUtils.ifNull(partCode, setCode, itemCode)){
return null;
}
return SetItemId.of(partCode, setCode, itemCode);
}
#ManyToMany(fetch=FetchType.LAZY)
#JoinColumns(value = {
#JoinColumn(name="PART_CODE", referencedColumnName="PART_CODE", insertable=false, updatable=false)
, #JoinColumn(name="ITEM_CODE", referencedColumnName="ITEM_CODE", insertable=false, updatable=false)
})
private List<Item> item;
}
So the question is,
how do I save objects separately which the objects' composite primary keys are partially same amongst them.
EDIT:
The entity extends below class:
#Setter
#Getter
#MappedSuperclass
#DynamicInsert
#DynamicUpdate
public abstract class BasicJpaModel<PK extends Serializable> implements Persistable<PK>, Serializable {
#Override
#JsonIgnore
public boolean isNew() {
return null == getId();
}
}
EDIT again: embeddable class.
after soneone points out embeddable class, I noticed there are only just two properties, it should be three of it. thank you.
#Data
#NoArgsConstructor
#RequiredArgsConstructor(staticName="of")
#Embeddable
public class SetGroupId implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#NonNull
private String partCode;
#NonNull
private String setCode;
}
Check howto use #EmbeddedId & #Embeddable (update you might need to use AttributeOverrides in id field, not sure if Columns in #Embeddable works).
You could create class annotated #Embeddable and add all those three ID fields there.
#Embeddable
public class MyId {
private String partCode;
private String setCode;
private String itemCode;
}
Add needed getters & setters.
Then set in class SetItem this class to be the id like `#EmbeddedId´.
public class SetItem {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name="partCode",
column=#Column(name="PART_CODE")),
#AttributeOverride(name="setCode",
column=#Column(name="SET_CODE"))
#AttributeOverride(name="itemCode",
column=#Column(name="ITEM_CODE"))
})
MyId id;
Check also Which annotation should I use: #IdClass or #EmbeddedId
Be sure to implement equals and hashCode in SetGroupId.
Can you provide that class?

Spring Data REST convert/show EmbeddedId including ManyToOne

I have a question for my project in Spring Data REST.
My model includes two tables with EmbeddedIds.
The first table (name=B) consist of two integers.
The second table (name=A) consist of a simple FK and the model of B (includes the EmbeddedId).
Now, if I make a request for table B, I'll get the two IDs.
However, if I make a request for table A, I wont get the IDs..
So I overrid the toString()-method in my EmbeddedId-class, to return at least the IDs right in the URI-link.
I read about BackendIdConverter or Spring core.converter and tried to convert the IDs right, but I wasn't able to reach my goal (got errors). So now, I need your help!
To fully understand my problem, here's my structure (as demo):
#Embeddable
public class IDsFromA implements Serializable {
#ManyToOne
#JoinColumn(name="cID")
private C c;
#ManyToOne
#JoinColumns({
#JoinColumn(name="b_1", referencedColumnName="b_1"),
#JoinColumn(name="b_2", referencedColumnName="b_2")
})
private B b;
}
#Embeddable
public class IDsFromB implements Serializable {
private int b_1;
private int b_2;
}
#Entity
public class A {
#EmbeddedId
private IDsFromA idsFromA;
// ...
}
#Entity
public class B {
#EmbeddedId
private IDsFromA idsFromA;
// ...
}
#Entity
public class c {
#Id
private int cID;
// ...
}
The Jackson JSON serializer will by default also serialize any public get..() methods. So you can simply add a couple of methods to return the relevant data and the values should be in the response:
e.g.
#Entity
public class A {
#EmbeddedId
private IDsFromA idsFromA;
public int getValueOne(){
return idsFromA.getB().getIdsFromB().getB_1();
}
public int getValueTwo(){
return idsFromA.getB().getIdsFromB().getB_2();
}
}

Resources