org.hibernate.PropertyAccessException: Could not set field value [1] value by reflection - spring

Hi guys I am new to Spring and I am getting this error in my project:
org.hibernate.PropertyAccessException: Could not set field value [1] value by
reflection : [class com.**.domain.identities.NurseAgencyIdentity.agencyId]
setter of com.**.domain.identities.NurseAgencyIdentity.agencyId
There are some classes involved in this process: Nurse , Agency, Named(abstract), NurseAgency and NurseAgencyIdentity. There is a many-to-many relationship between Nurse--Agency with an extra column nurse record. The Named class is an abstract class that contains the fields id and name and is being used by many tables in my design being id the identifier of the descendant table. To implement the many-to-many I had to use the #Embeddable annotation in the last class NurseAgencyIdentity which is the id of my NurseAgency join table. Here is the code:
NurseAgencyIdentity
#Embeddable
#Data
public class NurseAgencyIdentity implements Serializable {
#Column(name="nurse_id")
private Long nurseId;
#Column(name="agency_id")
private Long agencyId;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NurseAgencyIdentity that = (NurseAgencyIdentity) o;
return Objects.equals(nurseId, that.nurseId) &&
Objects.equals(agencyId, that.agencyId);
}
#Override
public int hashCode() {
return Objects.hash(nurseId, agencyId);
}
}
NurseAgency
#Entity
#Data
public class NurseAgency {
#EmbeddedId
private NurseAgencyIdentity id;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("nurseId")
private Nurse nurse;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("agencyId")
private Agency agency;
private String nurseRecord;
}
Nurse
#Entity
#Data
public class Nurse {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
#Enumerated(EnumType.STRING)
private License license;
#OneToMany(mappedBy = "nurse", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NurseAgency> agencies = new ArrayList<>();
// need the extra column
public void addAgency(Agency agency) {//, String nurseRecord) {
NurseAgency nurseAgency = new NurseAgency();
nurseAgency.setAgency(agency);
nurseAgency.setNurse(this);
//nurseAgency.setNurseRecord(nurseRecord);
agency.getNurses().add(nurseAgency);
}
public void removeAgency(Agency agency) {
for (Iterator<NurseAgency> iterator = agencies.iterator(); iterator.hasNext(); ) {
NurseAgency nurseAgency = iterator.next();
if (nurseAgency.getNurse().equals(this) && nurseAgency.getAgency().equals(agency)){
iterator.remove();
nurseAgency.getAgency().getNurses().remove(nurseAgency);
nurseAgency.setNurse(null);
nurseAgency.setAgency(null);
}
}
}
#Override
public String toString() {
return id + " " + firstName + " " + middleName + " " + lastName;
}
}
Named
#MappedSuperclass
#EntityListeners(AuditingEntityListener.class)
#Data
public abstract class Named implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Agency
#Entity
#Data
public class Agency extends Named {
private String description;
#OneToMany(mappedBy = "agency", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NurseAgency> nurses = new ArrayList<>();
}
And I am having this error when trying to seed the join table:
BootStrapData
#Component
public class BootStrapData implements CommandLineRunner {
#Autowired
private final NurseRepository nurseRepository;
#Autowired
private final AgencyRepository agencyRepository;
private final NurseAgencyRepository nurseAgencyRepository;
public BootStrapData(NurseRepository nurseRepository, AgencyRepository agencyRepository, NurseAgencyRepository nurseAgencyRepository) {
this.nurseRepository = nurseRepository;
this.agencyRepository = agencyRepository;
this.nurseAgencyRepository = nurseAgencyRepository;
}
#Override
public void run(String... args) throws Exception {
System.out.println("Loading agencies ");
ArrayList<Agency> agencies = GetAgencies();
System.out.println("Loading Nurses ");
ArrayList<Nurse> nurses = GetNurses(agencies);
nurses.stream().forEach( n -> nurseRepository.save(n));
agencies.stream().forEach( a -> agencyRepository.save(a));
//Nurses Agencies
ArrayList<NurseAgency> nurseAgencies = new ArrayList<>(1);
nurseAgencies.addAll(SetNurseAndAgencies(nurses.get(0), new Agency[]{agencies.get(0), agencies.get(1), agencies.get(2)}));
nurseAgencies.addAll(SetNurseAndAgencies(nurses.get(1), new Agency[]{agencies.get(0), agencies.get(1)}));
nurseAgencies.addAll(SetNurseAndAgencies(nurses.get(2), new Agency[]{agencies.get(1), agencies.get(2)}));
for (int i=0; i<nurseAgencies.size();i++){
nurseAgencyRepository.save(nurseAgencies.get(i)); // I've got the error in first iteration in this line
}
}
private ArrayList<Agency> GetAgencies() {
ArrayList<Agency> agencies = new ArrayList<>(3);
Agency a1 = new Agency();
a1.setName("Agency 1");
agencies.add(a1);
Agency a2 = new Agency();
a2.setName("Agency 2");
agencies.add(a2);
Agency a3 = new Agency();
a3.setName("Agency 3");
agencies.add(a3);
return agencies;
}
private ArrayList<Nurse> GetNurses(ArrayList<Agency> agencies) {
ArrayList<Nurse> nurses = new ArrayList<>(3);
Nurse n1 = new Nurse();
n1.setFirstName("Mario");
n1.setLastName("Perez");
nurses.add(n1);
Nurse n2 = new Nurse();
n2.setFirstName("Luis");
n2.setLastName("Ruiz");
nurses.add(n2);
Nurse n3 = new Nurse();
n3.setFirstName("Maria");
n3.setLastName("Crez");
nurses.add(n3);
return nurses;
}
private ArrayList<NurseAgency> SetNurseAndAgencies(Nurse nurse, Agency[] agencies) {
ArrayList<NurseAgency> nurseagencies = new ArrayList<>(agencies.length);
for (int i=0; i<agencies.length; i++){
NurseAgency na = new NurseAgency();
na.setNurse(nurse);
na.setAgency(agencies[i]);
na.setNurseRecord(nurse.getFirstName() + agencies[i].getName());
nurseagencies.add(na);
}
return nurseagencies;
}
}
Where is the problem?

Try changing the NurseAgencyIdentity declaration on NurseAgency from:
#EmbeddedId
private NurseAgencyIdentity id;
to:
#EmbeddedId
private NurseAgencyIdentity id = new NurseAgencyIdentity();
I didn't see the full stack trace but the root cause can be a NullPointerException when hibernate tries to set fields (generated agencyId [ 1 ] in your case) via reflection on NurseAgencyIdentity and it's null.
See org.hibernate.tuple.entity.AbstractEntityTuplizer#getIdentifier

Related

I want to input boolean value in ChallengeDto

public class ChallengeDto {
private Long id;
private Category category;
private String title;
private String subTitle;
private boolean like;
private int totalScore;
private int requiredScore;
public ChallengeDto(Long id, Category category, String title, String subTitle, boolean like, int totalScore, int requiredScore) {
this.id = id;
this.category = category;
this.title = title;
this.subTitle = subTitle;
this.like = like;
this.totalScore = totalScore;
this.requiredScore = requiredScore;
}
}
I created challengeDto that include challenge's properties(id, category, title, subtitle, totalScore, requiredScore) and like property(can know that if i like challenge or not).
If I put like button, that information stored challengeLike table.
public class ChallengeLike {
#Id
#GeneratedValue
#Column(name = "challenge_like_id")
private Long id;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "challenge_id")
private Challenge challenge;
private LocalDateTime createDate;
}
Now I'm trying to write a code to retrieve challengeDto that checks if I clicked like or not, but I'm having a problem... I can't think of what kind of code to make.
#Repository
#RequiredArgsConstructor
public class ChallengeDtoRepository {
private final EntityManager em;
#Transactional
public List<ChallengeDto> findChallenges(Long userId) {
return em.createQuery(
"select new " +
"com.example.candy.controller.challenge.ChallengeDto(c.id,c.category,c.title,c.subTitle,????,c.totalScore,c.requiredScore)" +
" from Challenge c" +
" left join ChallengeLike cl on c.id = cl.challenge.id" +
" and cl.user.id = : userId", ChallengeDto.class)
.setParameter("userId", userId)
.getResultList();
}
}
try to rename the field to likeDone or something different than like, it makes the code ambiguous.
However, just simply do:
cl.likeDone
which means:
return em.createQuery(
"select new " +
"com.example.random.demo.dto.ChallengeDto(c.id,c.category,c.title,c.subTitle,cl.likeDone,c.totalScore,c.requiredScore)" +
" from Challenge c" +
" left join ChallengeLike cl on c.id = cl.challenge.id" +
" where cl.user.id = : userId", ChallengeDto.class)
.setParameter("userId", userId)
.getResultList();
However, try to use JPA if you don't have any mandatory condition to use native query or jpql.
JPA implementation:
#Repository
public interface ChallengeLikeRepository extends JpaRepository<ChallengeLike, Long> {
List<ChallengeLike> findAllByUser_Id(long userId);
}
Just call the repository method from service layer and map to your required dto:
public List<ChallengeDto> findChallenges(Long userId) {
List<ChallengeLike> entities = this.repository.findAllByUser_Id(userId);
return entities.stream().map(this::mapToDto).collect(Collectors.toList());
}
The mapToDto() method converts the entity to corresponding ChallengeDto
private ChallengeDto mapToDto(ChallengeLike x) {
return ChallengeDto.builder()
.category(x.getChallenge().getCategory())
.id(x.getChallenge().getId())
.like(x.isLikeDone())
.requiredScore(x.getChallenge().getRequiredScore())
.subTitle(x.getChallenge().getSubTitle())
.title(x.getChallenge().getTitle())
.totalScore(x.getChallenge().getTotalScore())
.userId(x.getUser().getId())
.build();
}
For your convenience, some properties has been added or changed in some classes. The #Builder annotation has been added to the ChallengeDto class. The rest of the corresponding entity and other classes:
a) ChallengeLike.java
#Entity
#Data
public class ChallengeLike {
#Id
#GeneratedValue
#Column(name = "challenge_like_id")
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
#JsonIgnoreProperties("challengeLikes")
private User user;
#ManyToOne
#JoinColumn(name = "challenge_id")
#JsonIgnoreProperties("challengeLikes")
private Challenge challenge;
private boolean likeDone;
private LocalDateTime createDate;
}
b) Challenge.java
#Entity
#Data
public class Challenge {
#Id
private Long id;
private Category category;
private String title;
private String subTitle;
private int totalScore;
private int requiredScore;
#OneToMany(mappedBy = "challenge", cascade = CascadeType.ALL)
#JsonIgnoreProperties("challenge")
private List<ChallengeLike> challengeLikes = new ArrayList<>();
}
c) Category.java
public enum Category {
CAT_A,
CAT_B
}
Update
If you want to fetch Challenge entity instead of ChallengeLike and map that to ChallengeDto, first implement ChallangeRepository:
#Repository
public interface ChallengeRepository extends JpaRepository<Challenge, Long> {
}
Add the fetchType to EAGER in Challange Entity class:
#OneToMany(mappedBy = "challenge", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JsonIgnoreProperties("challenge")
private List<ChallengeLike> challengeLikes = new ArrayList<>();
And to map the Challenge to ChallengeDto, you can add another mothod as follows:
private ChallengeDto mapToDto(Challenge x) {
return ChallengeDto.builder()
.category(x.getCategory())
.id(x.getId())
.like(!x.getChallengeLikes().isEmpty() && x.getChallengeLikes().get(0).isLikeDone())
.requiredScore(x.getRequiredScore())
.subTitle(x.getSubTitle())
.title(x.getTitle())
.totalScore(x.getTotalScore())
.userId(x.getUserId()) // if you have user reference in Challenge, remove this otherwise.
.build();
}
finally, to incorporate everything properly, change the caller:
public List<ChallengeDto> findChallenges(Long userId) {
List<Challenge> entities = this.repository.findAll();
List<ChallengeDto> entitiesWithoutChallengeLikes = entities.stream()
.filter(x -> x.getChallengeLikes() == null
|| x.getChallengeLikes().isEmpty())
.map(this::mapToDto).collect(Collectors.toList());
List<ChallengeDto> entitiesInferredFromChallengeLikes = entities.stream()
.filter(x -> x.getChallengeLikes() != null && !x.getChallengeLikes().isEmpty())
.flatMap(x -> x.getChallengeLikes().stream())
.map(this::mapToDto)
.collect(Collectors.toList());
entitiesInferredFromChallengeLikes.addAll(entitiesWithoutChallengeLikes);
return entitiesInferredFromChallengeLikes;
}
Final Update
Well, I finally understood properly what you expected. Adopt the following changes to the previous solution and you will get exactly what you want.
Change the 2 occurrence of the following in the findChallanges method:
.map(this::mapToDto)
To:
.map(x -> mapToDto(x, userId))
And the two mapToDto functions will be changed to follows:
private ChallengeDto mapToDto(ChallengeLike x, long userId) {
return ChallengeDto.builder()
.category(x.getChallenge().getCategory())
.id(x.getChallenge().getId())
.like(x.getUser().getId() == userId && x.isLikeDone())
.requiredScore(x.getChallenge().getRequiredScore())
.subTitle(x.getChallenge().getSubTitle())
.title(x.getChallenge().getTitle())
.totalScore(x.getChallenge().getTotalScore())
.userId(x.getUser().getId())
.build();
}
private ChallengeDto mapToDto(Challenge x, long userId) {
return ChallengeDto.builder()
.category(x.getCategory())
.id(x.getId())
.like(false)
.requiredScore(x.getRequiredScore())
.subTitle(x.getSubTitle())
.title(x.getTitle())
.totalScore(x.getTotalScore())
.userId(userId)
.build();
}

How to load a full graph of 2 entities that are in relationship #OneToMany each other with a Join Table

I'm using Spring Boot and Spring Data and I have a problem when trying to load entities using JPA and EntityGraph.
I have a Patient and Insurance entities. Each Patient can have many Insurances and each Insurance can be assigned to many patients. I decided to use a Join Table PatientInsurance because I need to store extra fields like 'active', and also the relation code (a Patient can be a Member, Spouse, or Child for that specific insurance).
Using Spring Data repositories I annotated the method to find a patient, with an EntityGraph, to have ready the list of PatientInsurances (and Insurances) for that patient in one query.
This is the code (I removed the non-necessary parts in the scope)
Patient class
#Entity
#Table(name = "patient")
public class Patient {
#NotNull
#NotEmpty
#Column(length = 60, nullable = false)
private String patientFirstName;
#NotNull
#NotEmpty
#Column(length = 60, nullable = false)
private String patientLastName;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "patient", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
List<PatientInsurance> patientsInsurances = new ArrayList<>();
public void addPatientInsurance(PatientInsurance patientIns) {
if (!patientsInsurances.contains(patientIns)) {
patientsInsurances.add(patientIns);
}
}
//other properties...
Insurance class
#Entity
#Table(name = "insurance")
public class Insurance {
#Column(name = "policy_id", length = 20)
private String policyId;
#OneToMany(mappedBy = "insurance", fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
private List<PatientInsurance> patientsInsurances = new ArrayList<PatientInsurance>();
public void addPatientInsurance(PatientInsurance patientIns) {
if (!patientsInsurances.contains(patientIns)) {
patientsInsurances.add(patientIns);
}
}
//other properties
Entity for the join table between patient and insurance (needed a join table for extra field in this entity like active and relCode
#Entity
#IdClass(PatientInsurance.PatientInsurancePK.class)
#Table(name = "patient_insurance")
public class PatientInsurance implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "patient_id")
private Patient patient;
#Id
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "insurance_id")
private Insurance insurance;
#Column(name = "active")
private boolean active;
#Column(length = 1)
private String relCode;
public PatientInsurance() {
insurance = new Insurance();
patient = new Patient();
}
public PatientInsurance(Patient p, Insurance i, boolean active, String relCode) {
this.patient = p;
this.insurance = i;
this.active = active;
this.relCode = relCode;
p.addPatientInsurance(this);
i.addPatientInsurance(this);
}
public Patient getPatient() {
return patient;
}
public Insurance getInsurance() {
return insurance;
}
public void setInsurance(Insurance insurance) {
this.insurance = insurance;
insurance.addPatientInsurance(this);
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
public void setPatient(Patient patient) {
this.patient = patient;
patient.addPatientInsurance(this);
}
public String getRelCode() {
return relCode;
}
public void setRelCode(String relCode) {
this.relCode = relCode;
}
static public class PatientInsurancePK implements Serializable {
protected Patient patient;
protected Insurance insurance;
public PatientInsurancePK() {
}
public PatientInsurancePK(Patient patient, Insurance insurance) {
this.patient = patient;
this.insurance = insurance;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PatientInsurancePK)) return false;
PatientInsurancePK that = (PatientInsurancePK) o;
if (!patient.equals(that.patient)) return false;
return insurance.equals(that.insurance);
}
#Override
public int hashCode() {
int result = (patient != null) ? patient.hashCode() : 0;
result = 31 * result + ((insurance != null) ? insurance.hashCode() : 0);
return result;
}
}
}
Implementation of the PatientService
#Transactional
#Service("patientService")
public class PatientServiceImpl implements PatientService {
#Autowired
PatientRepository patientRepository;
#Override
public Optional<Patient> findByIdFull(Long id) {
Optional<Patient> patient = patientRepository.findById(id);
return patient;
}
//other methods...
Patient Repository
public interface PatientRepository extends JpaRepository<Patient, Long> {
#EntityGraph(
attributePaths = {
"patientsInsurances",
"patientsInsurances.patient",
"patientsInsurances.insurance"},
type = EntityGraph.EntityGraphType.LOAD)
Optional<Patient> findById(Long id);
A snippet of code that calls the method in PatientService
Optional<Patient> patientOptional = patientService.findByIdFull(p.getId());
if (patientOptional.isPresent()) {
Patient patient1 = patientOptional.get();
List<PatientInsurance> patientInsurances = patient1.getPatientInsurances();
PatientInsurances patientInsurance = patientInsurances.get(0);
Patient patient2 = patientInsurance.getPatient(); //and this is same istance of patient1, it's ok
Insurance insurance = patientInsurance.getInsurance();
//here is the problem!!!
insurance.getPatientInsurances();
//Unable to evaluate the expression Method threw 'org.hibernate.LazyInitializationException' exception.
So the problem seems that when I go inside the patient side, I can loop into his Insurances without problems, but when I try to do the same starting from the Insurance instance, I cannot loop into its patients cause they are lazily loaded.
So how to make jpa download the full graph in the correct way?

Issue mapping fields ModelMapper

I use DTO and modelMapper in order not to make visible some fields.
I have a CategoryEntity that can have subcategories
public class CategoryEntity {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#Column(length = 30, nullable = false)
private String categoryKeyId;
#Column(nullable = false)
private String name;
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name="parent_id", nullable=true)
private CategoryEntity parentCategory;
// allow to delete also subcategories
#OneToMany(mappedBy="parentCategory", cascade = CascadeType.ALL)
private List<CategoryEntity> subCategories;
}
When i create a category I use a model:
#Getter #Setter
public class CategoryRequestModel {
private String name;
private String parentCategoryKeyId;
}
In this model i want parentCategoryKeyId to match with the categoryKeyId of the parent.
For example if i create a "top" category :
{
"name": "topCategory"
}
It returns me :
{
"categoryKeyId": "jUcpO27Ch2YrT2zkLr488Q435F8AKS",
"name": "topCategory",
"subCategories": null
}
When i do this :
{
"name": "sub",
"parentCategoryKeyId": "jUcpO27Ch2YrT2zkLr488Q435F8AKS"
}
In my Controller, i pass the rest object to a DTO Layer which calls a service :
public CategoryRestResponseModel createCategory(#RequestBody CategoryRequestModel categoryRequestModel) {
CategoryRestResponseModel returnValue = new CategoryRestResponseModel();
if( categoryRequestModel.getName().isEmpty())
throw new NullPointerException(ErrorMessages.MISSING_REQUIRED_FIELDS.getErrorMessage());
ModelMapper modelMapper = new ModelMapper();
CategoryDto categoryDto = modelMapper.map(categoryRequestModel, CategoryDto.class);
CategoryDto createdCategory = categoryService.createCategory(categoryDto);
returnValue = modelMapper.map(createdCategory, CategoryRestResponseModel.class);
return returnValue;
}
My CategoryDto is a basic POJO :
#Getter #Setter
public class CategoryDto implements Serializable {
#Getter(AccessLevel.NONE)
private static final long serialVersionUID = 1L;
private String categoryKeyId;
private String parentCategoryKeyId;
private String name;
private CategoryDto parentCategory;
private List<CategoryDto> subCategories;
}
In my Service :
public CategoryDto createCategory(CategoryDto categoryDto) {
//1. Create an empty object to return
System.out.println("Hello World");
CategoryDto returnValue = new CategoryDto();
System.out.println("CategoryDto: " + categoryDto);
// check if category exists
if (categoryRepository.findByName(categoryDto.getName()) != null)
throw new ApplicationServiceException("Record already in Database");
ModelMapper modelMapper = new ModelMapper();
CategoryEntity categoryEntity = modelMapper.map(categoryDto, CategoryEntity.class);
// Generate categoryKeyId
String categoryKeyId = utils.generateCategoryKeyId(30);
categoryEntity.setCategoryKeyId(categoryKeyId);
System.out.println("categoryDto parentCategory: " + categoryDto.getParentCategory());
System.out.println("CategoryDto: " + categoryDto);
if(categoryDto.getParentCategoryKeyId() != null) {
CategoryEntity parentCategory = categoryRepository.findByCategoryKeyId(categoryDto.getParentCategoryKeyId());
categoryEntity.setParentCategory(parentCategory);
System.out.println("CategoryEntity: " + categoryEntity);
System.out.println("parentCategory: " + parentCategory);
}
CategoryEntity storedCategory = categoryRepository.save(categoryEntity);
returnValue = modelMapper.map(storedCategory, CategoryDto.class);
return returnValue;
}
My issue is that I would like to save the subcategory and retrieve the ID that match the categoryKeyId ...
In the database my entry should be like this
My First entry should have:
id = 1 - parent_id = null, category_key_id = jUcpO27Ch2YrT2zkLr488Q435F8AKS, name = topCategory ...
AND :
id = 2 - parent_id = 1 , category_key_id = "another generated key", name= sub
Unfortunatelly I just persist the id, the categorykeyid and the name.
I removed id from CategoryDto and i obtain : 1) Converter org.modelmapper.internal.converter.NumberConverter#348fc3d8 failed to convert java.lang.String to java.lang.Long.
I solved it in a "dirty" way.
I just changed my object in entry and added a long id.
It gives me :
#Getter #Setter
public class CategoryRequestModel {
private Long id;
private String name;
private String parentCategoryKeyId;
}

Multi column search using Specifications Spring Data Jpa within associated entity?

I am taking this question Perform multi column search on Date, Integer and String Data type fields of Single Table? and This method must return a result of type Specification<Employee> in Java 8 further ahead.
Actually I wanted to search within association entity as well as a part of global search. Will that be possible using JPA 2 Specifications API ?
I've Employee and Department #OneToMany bi-directional relationship.
Employee.java
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "EMAIL_ID")
private String email;
#Column(name = "STATUS")
private String status;
#Column(name = "BIRTH_DATE")
private LocalDate birthDate;
#Column(name = "PROJECT_ASSOCIATION")
private Integer projectAssociation;
#Column(name = "GOAL_COUNT")
private Integer goalCnt;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DEPT_ID", nullable = false)
#JsonIgnore
private Department department;
}
Department.java
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
public class Department implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "DEPT_ID")
private Long departmentId;
#Column(name = "DEPT_NAME")
private String departmentName;
#Column(name = "DEPT_CODE")
private String departmentCode;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "department")
#JsonIgnore
private Set<Employee> employees;
}
and I saved Data like below.
MyPaginationApplication.java
#SpringBootApplication
public class MyPaginationApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(MyPaginationApplication.class, args);
}
#Autowired
private EmployeeRepository employeeRepository;
#Autowired
private DepartmentRepository departmentRepository;
#Override
public void run(String... args) throws Exception {
saveData();
}
private void saveData() {
Department department1 = Department.builder()
.departmentCode("AD")
.departmentName("Boot Depart")
.build();
departmentRepository.save(department1);
Employee employee = Employee.builder().firstName("John").lastName("Doe").email("john.doe#gmail.com")
.birthDate(LocalDate.now())
.goalCnt(1)
.projectAssociation(2)
.department(department1)
.build();
Employee employee2 = Employee.builder().firstName("Neha").lastName("Narkhede").email("neha.narkhede#gmail.com")
.birthDate(LocalDate.now())
.projectAssociation(4)
.department(department1)
.goalCnt(2)
.build();
Employee employee3 = Employee.builder().firstName("John").lastName("Kerr").email("john.kerr#gmail.com")
.birthDate(LocalDate.now())
.projectAssociation(5)
.department(department1)
.goalCnt(4)
.build();
employeeRepository.saveAll(Arrays.asList(employee, employee2, employee3));
}
}
EmployeeController.java
#GetMapping("/employees/{searchValue}")
public ResponseEntity<List<Employee>> findEmployees(#PathVariable("searchValue") String searchValue) {
List<Employee> employees = employeeService.searchGlobally(searchValue);
return new ResponseEntity<>(employees, HttpStatus.OK);
}
EmployeeSpecification.java
public class EmployeeSpecification {
public static Specification<Employee> textInAllColumns(Object value) {
return (root, query, builder) -> builder.or(root.getModel().getDeclaredSingularAttributes().stream()
.filter(attr -> attr.getJavaType().equals(value.getClass()))
.map(attr -> map(value, root, builder, attr))
.toArray(Predicate[]::new));
}
private static Object map(Object value, Root<?> root, CriteriaBuilder builder, SingularAttribute<?, ?> a) {
switch (value.getClass().getSimpleName()) {
case "String":
return builder.like(root.get(a.getName()), getString((String) value));
case "Integer":
return builder.equal(root.get(a.getName()), value);
case "LocalDate":
return builder.equal(root.get(a.getName()), value);//date mapping
default:
return null;
}
}
private static String getString(String text) {
if (!text.contains("%")) {
text = "%" + text + "%";
}
return text;
}
}
When I hit the /employees/{searchValue}, I want searching to be happened in Department Table along with Employee table (may be using Joins something like that). Is that possible ? If yes, how can we do that ?
Or:
Will this be good approach to put like here? Got reference from Using #Query
#Query("SELECT t FROM Todo t WHERE " +
"LOWER(t.title) LIKE LOWER(CONCAT('%',:searchTerm, '%')) OR " +
"LOWER(t.description) LIKE LOWER(CONCAT('%',:searchTerm, '%'))")
List<Todo> findBySearchTerm(#Param("searchTerm") String searchTerm);
Any pointers?
If you take a look at my post actually I have a solution for join
#Override
public Specification<User> getFilter(UserListRequest request) {
return (root, query, cb) -> {
query.distinct(true); //Important because of the join in the addressAttribute specifications
return where(
where(firstNameContains(request.search))
.or(lastNameContains(request.search))
.or(emailContains(request.search))
)
.and(streetContains(request.street))
.and(cityContains(request.city))
.toPredicate(root, query, cb);
};
}
private Specification<User> firstNameContains(String firstName) {
return userAttributeContains("firstName", firstName);
}
private Specification<User> lastNameContains(String lastName) {
return userAttributeContains("lastName", lastName);
}
private Specification<User> emailContains(String email) {
return userAttributeContains("email", email);
}
private Specification<User> userAttributeContains(String attribute, String value) {
return (root, query, cb) -> {
if(value == null) {
return null;
}
return cb.like(
cb.lower(root.get(attribute)),
containsLowerCase(value)
);
};
}
private Specification<User> cityContains(String city) {
return addressAttributeContains("city", city);
}
private Specification<User> streetContains(String street) {
return addressAttributeContains("street", street);
}
private Specification<User> addressAttributeContains(String attribute, String value) {
return (root, query, cb) -> {
if(value == null) {
return null;
}
ListJoin<User, Address> addresses = root.joinList("addresses", JoinType.INNER);
return cb.like(
cb.lower(addresses.get(attribute)),
containsLowerCase(value)
);
};
}
private String containsLowerCase(String searchField) {
return "%" + searchField.toLowerCase() + "%";
}
Here you can see how I search the users by their address columns (city and street).
EDIT: Also you cannot use the #Query annotation that much dinamically (you van insert parameter values dinamically, but not parameters. That's where Specificaion is handy)
EDIT2: I know this is not the 2.x.x Spring version, but 1.5.x, but the idea is the same for joins.

Getting ConstraintViolationException while saving a row with embedded key in the table with many-to-many mapping between two entities using Spring JPA

In our spring boot Restful WebService, we have two master tables with many-to-many relationship between them. But in the transaction table, we want one extra field (current_time) as part of the embedded key other than the primary keys of the two tables. Now, we’ve created a separate class for defining embedded primary key using #Embeddable. Now, while inserting one transaction row to transaction table using Spring JPA, I am manually setting the primary keys in the corresponding entity and calling the save method on corresponding repository. But It is giving me ConstraintViolationException as the current_time is going with null value even if I have manually set it. Any help would be highly appreciated.
First Entity is as follows :
#Entity
#Table(name = "project")
public class Project {
#Id
#GenericGenerator(name = "projectid", strategy = "com.sample.upload.entity.ProjectIDGenerator")
#GeneratedValue(generator = "projectid")
#Column(name = "projectid")
private String projectID;
#Column(name = "project_name")
private String projectName;
#Column(name = "project_descr")
private String projectDesc;
#Column(name = "project_input_path")
private String projectPath;
#Column(name = "project_creation_time")
private Calendar projectCreationTime;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "project_migration", joinColumns = #JoinColumn(name = "projectid", referencedColumnName = "projectid"), inverseJoinColumns = #JoinColumn(name = "migratorid", referencedColumnName = "migratorid"))
private List<Migrator> migrators;
#Column(name = "account_name")
private String accountName;
#Column(name = "account_group")
private String accountGroup;
public String getProjectID() {
return projectID;
}
public void setProjectID(String projectID) {
this.projectID = projectID;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public String getAccountGroup() {
return accountGroup;
}
public void setAccountGroup(String accountGroup) {
this.accountGroup = accountGroup;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getProjectDesc() {
return projectDesc;
}
public void setProjectDesc(String projectDesc) {
this.projectDesc = projectDesc;
}
public String getProjectPath() {
return projectPath;
}
public void setProjectPath(String projectPath) {
this.projectPath = projectPath;
}
public Calendar getProjectCreationTime() {
return projectCreationTime;
}
public void setProjectCreationTime(Calendar projectCreationTime) {
this.projectCreationTime = projectCreationTime;
}
public List<Migrator> getMigrators() {
return migrators;
}
public void setMigrators(List<Migrator> migrators) {
this.migrators = migrators;
}
}
Second Entity :
#Entity
#GenericGenerator(name = "generatorName", strategy = "increment")
#Table(name = "migrator")
public class Migrator {
#Id
#GeneratedValue(generator = "generatorName")
#Column(name = "migratorid")
private String migratorId;
#Column(name = "src_tech_name")
private String srcTechName;
#Column(name = "dest_tech_name")
private String destTechName;
#Column(name = "migrator_name")
private String migratorName;
#Column(name = "migrator_type")
private String migratorType;
public String getMigratorId() {
return migratorId;
}
public void setMigratorId(String migratorId) {
this.migratorId = migratorId;
}
public String getSrcTechName() {
return srcTechName;
}
public void setSrcTechName(String srcTechName) {
this.srcTechName = srcTechName;
}
public String getDestTechName() {
return destTechName;
}
public void setDestTechName(String destTechName) {
this.destTechName = destTechName;
}
public String getMigratorName() {
return migratorName;
}
public void setMigratorName(String migratorName) {
this.migratorName = migratorName;
}
public String getMigratorType() {
return migratorType;
}
public void setMigratorType(String migratorType) {
this.migratorType = migratorType;
}
#Override
public String toString() {
return "Technology [migratorId=" + migratorId + ", srcTechName=" + srcTechName + ", destTechName="
+ destTechName + ", migratorName=" + migratorName + ", migratorType=" + migratorType + "]";
}
}
The join (transaction) table's entity :
#Entity
#Table(name = "project_migration")
public class ProjectMigration {
#EmbeddedId
private ProjectMigrationID migrationId;
#Column(name ="migration_finish_time")
private Calendar migrationFinishTime;
#Column(name ="time_in_millis_for_migration")
private long timeInMillisForMigration;
#Column(name ="migration_status")
private String migrationStatus;
#Column(name ="migrated_codebase_path")
private String migratedCodeBasePath;
The embedded Primary Key class is as follows:
#Embeddable
public class ProjectMigrationID implements Serializable {
private static final long serialVersionUID = -3623993529011381924L;
#Column(name = "projectid")
private String projectId;
#Column(name = "migratorid")
private String migratorId;
#Column(name = "migration_start_time")
private Calendar migrationStartTime;
public ProjectMigrationID() {
}
public ProjectMigrationID(String projectId, String migratorId, Calendar migrationStartTime) {
this.projectId = projectId;
this.migratorId = migratorId;
this.migrationStartTime = migrationStartTime;
}
The snippet from service Class :
for (String migratorId : data.getMigratorIds()) {
Migrator migrator = migratorRepository.findByMigratorId(migratorId);
migrators.add(migrator);
}
if (projectId != null) {
project = projectRepository.findByProjectID(projectId);
System.out.println(project==null);
project.setMigrators(migrators);
System.out.println("I am here");
if (project != null) {
//project.setMigrationStatus("In Progress");
ProjectMigrationID pmId = new ProjectMigrationID();
pmId.setProjectId(project.getProjectID());
pmId.setMigratorId(project.getMigrators().get(0).getMigratorId());
pmId.setMigrationStartTime(new GregorianCalendar());
ProjectMigration pm = new ProjectMigration();
pm.setMigrationId(pmId);
pm.setMigrationStatus("Pending");
projectMigrationRepository.save(pm);
That's because of the #JoinTable where the date is not included and it skips the insertion. If you include a column with all the primary keys needed, it will work as expected.
Only the columns mapped via #JoinTable will be included during insertion or update (defaults to true when mapped)
Either include the date time column in the Project class or use association without #JoinTable.
I'm editing via mobile. So please ignore typos if any.

Resources