Is there any way to add a abstract method for JPA repository : findByEnumContaining(String enum) [enum is subString for possible ENUM values] - spring-boot

This is my JPA #Repository, here we can get list<Person> with findByFullNameContaining(String query) - by providing just substring of fullName in query
#Repository
public interface PersonRepository extends CrudRepository<Person,String> {
Optional<Person> findByFullName(String fullName);
List<Person> findByDepartment(Department department);
List<Person> findByFullNameContaining(String query);
}
Similarly, Can we perform something for ENUM values - by providing sub-string value of ENUM? How?
Eg.
public enum Department {
NORTH,
SOUTH,
EAST,
WEST
}
List<Person> findByDepartmentContaining(String query);
JPA #Entity Person:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
#Entity
#Table(name = "Person")
public class Person {
#Id
#NotNull(message = "Mobile number is required")
#Size(min = 10, max = 10, message = "Mobile no. must be 10 digits")
#Column(name = "person_id", unique = true, length = 10)
private String mobileNum;
#Transient
#NotNull(message = "Password is required")
#Size(min = 1, message = "Password cannot be empty")
private String password="****";
#NotNull(message = "Name cannot be empty")
#Size(min = 1, max = 255, message = "fullName must be 1-255 characters long")
#Column(name = "full_name")
private String fullName;
#Column(name = "department")
#Enumerated(EnumType.STRING)
#NotNull(message = "Department must be specified")
private Department department = Department.UNKNOWN;
public Person() {
}
public Person(String mobileNum, String fullName, String password, Department department) {
this.mobileNum = mobileNum;
this.password = password;
this.fullName = fullName;
this.department = department;
}
public String getMobileNum() {
return mobileNum;
}
public void setMobileNum(String mobileNum) {
this.mobileNum = mobileNum;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
#Override
public String toString() {
return "Person [fullName=" + fullName + ", mobileNum=" + mobileNum + ", password=" + password + ", department=" + department + "]";
}
}

The question does not specify what exactly the issue is when declaring such a method.
But having tried with spring boot 2.7 with PostgreSql Database the application throws the following runtime error:
java.lang.IllegalArgumentException: Parameter value [%some value%] did not match expected type [com.Department (n/a)]
at org.hibernate.query.spi.QueryParameterBindingValidator.validate(QueryParameterBindingValidator.java:54) ~[hibernate-core-5.6.5.Final.jar:5.6.5.Final]
The issue at least with hibernate is that it will expect a Department field in an entity to be always passed as parameter in it's own object type which is Department.
I don't think there is a way to avoid this, as this is an out of the box functionality for Hibernate.
I think however that the correct approach would not be to define this type of method but the following. Department already exists in application code so it is known at the time the query needs to be invoked. So I think that the following solution would be considered a better practice:
//Repository method to be used in PersonRepository
List<Person> findByDepartmentIn(List<Department> departments);
And then the repository could be invoked in the following way from the service layer.
//Service method to be used
public List<Person> findByDepartmentIn(String searchBy) {
List<Department> departments = Arrays.stream(Department.values()).filter(dep -> dep.getName().contains(searchBy)).collect(Collectors.toList());
return personRepository.findDepartmentIn(departments);
}

Related

JPA #OneToMany mapping inserting null values

I had a hard time with hibernate OneToMany mapping. I have googled to find the solution from morning nothing helped. Following are my code snippets.
package com.student.app.model;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import com.fasterxml.jackson.annotation.JsonProperty;
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "student_id")
private long id;
#Column(name = "student_name")
private String name;
#Column(name = "student_class")
#JsonProperty("class")
private String clazz;
#Column(name = "total_marks")
private int totalMarks;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "student")
private List<Subject> subjects;
public Student() {
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClazz() {
return clazz;
}
public void setClazz(String clazz) {
this.clazz = clazz;
}
public int getTotalMarks() {
return totalMarks;
}
public void setTotalMarks(int totalMarks) {
this.totalMarks = totalMarks;
}
public List<Subject> getSubjects() {
return subjects;
}
public void setSubjects(List<Subject> subjects) {
this.subjects = subjects;
}
#Override
public String toString() {
return "Student [id=" + id + ", name=" + name + ", clazz=" + clazz + ", totalMarks=" + totalMarks
+ ", subjects=" + subjects + "]";
}
}
Subject.java
package com.student.app.model;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
#Entity
#Table(name = "subject")
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "subject_id")
private long id;
#Column(name = "subject_name")
private String name;
#Column(name = "marks")
private String marks;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "student_id")
private Student student;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMarks() {
return marks;
}
public void setMarks(String marks) {
this.marks = marks;
}
#Override
public String toString() {
return "Subject [id=" + id + ", name=" + name + ", marks=" + marks + "]";
}
}
StudentRepository.java
package com.student.app.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.student.app.model.Student;
#Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
Student findStudentByName(String name);
}
POST Mapping
#PostMapping("/students")
public ResponseEntity<?> addStudent(#RequestBody Student student) {
try {
System.out.println("Student Object is : " + student);
Student studentData = studentRepository.save(student);
if (null == studentData) {
return ResponseEntity.noContent().build();
}
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{name}")
.buildAndExpand(studentData.getName()).toUri();
return new ResponseEntity<>(location, HttpStatus.CREATED);
} catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Post Request JSON
{
"name": "Vinod Kumar",
"class": "8th class",
"subjects": [
{
"name": "Telugu",
"marks": 85
},
{
"name": "English",
"marks": 80
},
{
"name": "Maths",
"marks": 90
}
],
"totalMarks": 580
}
Student Table data
STUDENT_ID STUDENT_CLASS STUDENT_NAME TOTAL_MARKS
1 8th class Vinod Kumar 580
Subject Table data
SUBJECT_ID MARKS SUBJECT_NAME STUDENT_ID
2 85 Telugu null
3 80 English null
4 90 Maths null
Here the issue is the STUDENT_ID column storing null values.
When you save a Student, Hibernate smart enough to insert a Student record to the database, get that record id and save all subjects of the Student with Subject.STUDENT_ID = generated student id, when the student and the subject connected to each other from both sides.
But by some reasons Hibernate doesn't connect a Subject with a Student as Java objects from the Subject side. It doesn't do for you
subject.setStudent(student)
This is the reason why Subject.STUDENT_ID = null in the database.
Before saving a student you have to assign the student to a subject for each subject, doing it in the loop
emptyStreamIfNull(student.getSubjects()).forEach(subject -> setStudent(student));
Some recommendations
Your database schema is not convenient. Student and Subject are tabular data.
So you need an additional object StudentSubjectMark.
Also use Long id for ids (not long id).
You have to log error here
catch (Exception e) {
e.printStackTrace();
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}

SpringBoot - 400 bad request after adding List of objects to input

I have a simple ReactJS/SpringBoot application which generates XML files used for software licenses.
This has been working fine, but now I'm trying to add an "attributes" table which has a many-to-one relationship with the license table. It will keep track of attributes specified in the front end that will be set to true on the license.
I've used these URLs as a guide for the backend (video and related code):
https://www.youtube.com/watch?v=8qhaDBCJh6I
https://github.com/Java-Techie-jt/spring-data-jpa-one2many-join-example
However, I'm getting a 400 error both on the update and the addition of a license when I try to use the updated code.
The front end seems to be working correctly.
Edit: looks like this is the culprit; although I haven't figured out why, yet.
Could not resolve parameter [0] in org.springframework.http.ResponseEntity<com.license.gen.app.model.License> com.license.gen.app.web.LicenseController.updateLicense(com.license.gen.app.model.License): JSON parse error: Cannot construct instance of `com.license.gen.app.model.Attribute` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CONFIG_MSC_PARAMETERS'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.license.gen.app.model.Attribute` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('CONFIG_MSC_PARAMETERS')
/endEdit
It's producing a JSON object with the attributes added as an array (e.g. at the end of the following object):
{
"id": 861,
"fullName": "johnsmith#abc.com",
"contact": "",
"requester": "johnsmith#abc.com",
"tag": "",
"company": "ACME",
"companyId": "ABC",
"template": "AN_4_2",
"product": "Analytics",
"expiration": "2022-04-15",
"macs": "11-11-11-11-11-11",
"typeId": "555",
"family": "FACILITY",
"systems": "2",
"licenseFilename": "license_johnsmith#abc.com.xml",
"url": "https://test-licenses.s3.amazonaws.com/license_johnsmith%40abc.com.xml",
"dateCreated": "2021-04-09T02:43:39.000+0000",
"dateUpdated": "2021-04-09T02:43:39.000+0000",
"user": {
"id": "00u560lmjop5poy624x6",
"name": "myname",
"email": "myname#gmail.com"
},
"attributes": [
"CONFIG_MSC_PARAMETERS",
"REPORTING"
]
}
Here is the updated License entity, with attributes added as a one-to-many List:
#EqualsAndHashCode
#Data
#NoArgsConstructor
#RequiredArgsConstructor
#Entity
#Table(name = "licenses")
public class License {
#Id
#GeneratedValue
private Long id;
#NonNull
private String fullName;
private String contact;
private String requester;
private String tag;
private String company;
private String companyId;
private String template;
private String product;
private String expiration;
private String macs;
private String typeId;
private String family;
private String systems;
#ManyToOne(cascade = CascadeType.PERSIST)
private User user;
#OneToMany(targetEntity = Attribute.class,cascade = CascadeType.ALL)
#JoinColumn(name ="license_fk",referencedColumnName = "id")
private List<Attribute> attributes;
// getters, setters
...
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public List<Attribute> getAttributes() {
return attributes;
}
public void setAttributes(List<Attribute> attributes) {
this.attributes = attributes;
}
}
License Repository (no change):
package com.license.gen.app.model;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface LicenseRepository extends JpaRepository<License, Long>
{
License findByFullName(String fullName);
List<License> findAllByUserId(String id);
Page<License> findAll(Pageable pageable);
#Query("SELECT l FROM License l " +
"WHERE l.company LIKE %:company% " +
"OR l.macs LIKE %:macs% " +
"OR l.requester LIKE %:requester% " +
"OR l.tag LIKE %:tag% " +
"OR l.fullName LIKE %:fullName% " +
"OR l.template LIKE %:template% " +
"OR l.expiration LIKE %:expiration% " +
"OR l.family LIKE %:family% " +
"OR l.licenseFilename LIKE %:filename% " +
"OR l.product LIKE %:product%"
)
List<License> findBySearchString(
#Param("company") String company,
#Param("macs") String macs,
#Param("requester") String requester,
#Param("tag") String tag,
#Param("fullName") String fullName,
#Param("template") String template,
#Param("expiration") String expiration,
#Param("family") String family,
#Param("filename") String filename,
#Param("product") String product);
#Query("SELECT l FROM License l " +
"WHERE l.macs LIKE %:macs%"
)
List<License> findByMacs(
#Param("macs") String macs);
#Query("SELECT l FROM License l " +
"WHERE l.fullName LIKE %:fullName%"
)
List<License> findMatchesByFullName(
#Param("fullName") String fullName);
}
Attribute Entity (new):
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Attribute {
#Id
private Long id;
#NonNull
private String attribute;
#Temporal(TemporalType.TIMESTAMP)
private Date dateCreated = new Date();
#ManyToOne(cascade = CascadeType.PERSIST)
private License license;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAttribute() {
return attribute;
}
public void setAttribute(String attribute) {
this.attribute = attribute;
}
public Date getDateCreated() {
return dateCreated;
}
public void setDateCreated(Date dateCreated) {
this.dateCreated = dateCreated;
}
}
Attribute Repository (new):
public interface AttributeRepository extends JpaRepository<User, Long> {
}
And the License Controller:
#RestController
#RequestMapping("/api")
class LicenseController {
private final Logger log = LoggerFactory.getLogger(LicenseController.class);
private LicenseRepository licenseRepository;
private UserRepository userRepository;
private AttributeRepository attributeRepository;
public static String bucket;
public LicenseController(LicenseRepository licenseRepository,
UserRepository userRepository,
AttributeRepository attributeRepository) {
this.licenseRepository = licenseRepository;
this.userRepository = userRepository;
this.attributeRepository = attributeRepository;
}
.....
#PostMapping("/license")
ResponseEntity<LicensePojo> createLicense(#Valid #RequestBody License license,
#AuthenticationPrincipal OAuth2User principal) throws URISyntaxException {
log.info("Request to create license: {}", license);
Map<String, Object> details = principal.getAttributes();
String userId = details.get("sub").toString();
// check to see if user already exists
Optional<User> user = userRepository.findById(userId);
license.setUser(user.orElse(new User(userId,
details.get("name").toString(), details.get("email").toString())));
if(license.getLicenseFilename() == null){
license.setLicenseFilename("");
}
License result = licenseRepository.save(license);
User myUser = license.getUser();
// Generate the license
LicensePojo licensePojo = new LicensePojo(result);
String fileName = GenLicense.genLicense(licensePojo);
AmazonS3Utils.putObject(fileName);
AmazonS3Utils.setToFileDownload(fileName);
AmazonS3Utils.setObjectPublic(fileName);
result.setLicenseFilename(fileName);
String url = AmazonS3Utils.getUrl(fileName).toString();
result.setUrl(url);
String origTypeId = String.valueOf(result.getTypeId());
String origId = String.valueOf(result.getId());
if ((origTypeId == null) || origTypeId.equalsIgnoreCase("")){
result.setTypeId(origId);
}
result = licenseRepository.save(result);
return ResponseEntity.created(new URI("/api/license/" + result.getId()))
.body(licensePojo);
}
#PutMapping("/license/{id}")
ResponseEntity<License> updateLicense(#Valid #RequestBody License license) {
List<Attribute> attributes = license.getAttributes();
License result = licenseRepository.save(license);
LicensePojo licensePojo = new LicensePojo(result);
String fileName = GenLicense.genLicense(licensePojo);
AmazonS3Utils.putObject(fileName);
AmazonS3Utils.setToFileDownload(fileName);
AmazonS3Utils.setObjectPublic(fileName);
String url = AmazonS3Utils.getUrl(fileName).toString();
result.setUrl(url);
result.setLicenseFilename(fileName);
return ResponseEntity.ok().body(result);
}
...
}
As far as I can see, there are no error messages being generated. The IDE is showing the AttributeRepository isn't being used in the controller, but they may be because it's part of the underlying SpringData JPA code to implement it.
Any ideas what the problem might be?

Spring Boot (5) JPA : Column name annotation fails (with set naming convention)

I am having an issue where columns are getting renamed to snakecase. Sadly I can not rename the columns since I am creating a 3rd party tool to work on software developed for us by another company. I have googled / searched SO and tried all sorts of fixes. I am a noob at spring / JPA/ hibernate and some help would be greatly appreciated. Using IntelliJ Ultimate if that matters.
Edit:
Might help if I include the error I am getting.
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Invalid column name 'birth_date'.
Entity
package net.glmhc.dmhwebservices.entities;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Type;
import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import org.hibernate.annotations.Cache;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
#Entity
#Table(name = "Clients")
#Cache(usage = CacheConcurrencyStrategy.NONE)
#NamedQueries({#NamedQuery(name = "Client.findById", query = "SELECT c FROM Clients c WHERE c.id = :id")
,#NamedQuery(name = "findGenderById", query = "SELECT c FROM Codes c WHERE c.id = :id")})
public class Clients{
#Id
#Column(columnDefinition = "char(36)")
private String id;
#CreatedBy
#Column(name = "CreateUser", columnDefinition = "char(36)")
private String createUser;
#CreatedDate
#Column(name = "CreateDate")
private LocalDateTime createDate;
#LastModifiedBy
#Column(name = "UpdateUser", columnDefinition = "char(36)")
private String updateUser;
#LastModifiedDate
#Column(name = "UpdateDate")
private LocalDateTime updateDate;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = false)
private final Set<ClientNames> clientNames = new HashSet<>();
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = false)
private final Set<ClientAddresses> clientAddresses = new HashSet<>();
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = false)
private final Set<ClientEmails> clientEmails = new HashSet<>();
#Column(name = "ClientCode")
private String clientCode;
#Column(name = "AlternateClientCode")
private String alternateClientCode;
#Column(name = "BirthDate")
private LocalDate birthDate;
public Clients(String id, String createUser, LocalDateTime createDate, String updateUser, LocalDateTime updateDate, String clientCode, String alternateClientCode, LocalDate birthDate, boolean declinedEthnicity, boolean declinedLanguage, boolean declinedRace, String mpi, String preferredName, String ssn, Codes genderCodes, String rcopiaId, boolean memoHasHighImportance, String memo, String profileImageId, boolean confidentialClient, String maritalStatus, String stateReportingEthnicity, String veteranStatus, String legalStatus, String employmentStatus, String livingArrangement, String incomeSource, LocalDate livingArrangementLastChanged, Integer grossMonthlyIncome, Integer householdSize, String appUser, Long mendWardId, String genderIdentity, String religiousPreference, String sexualOrientation, String genderAtBirth, String preferredPronouns, String uniqueStateIdentifier) {
this.id = id;
this.createUser = createUser;
this.createDate = createDate;
this.updateUser = updateUser;
this.updateDate = updateDate;
this.clientCode = clientCode;
this.alternateClientCode = alternateClientCode;
this.birthDate = birthDate;
}
public Clients() {
}
#Override
public String toString() {
return "Clients{" +
"id='" + id + '\'' +
", createUser='" + createUser + '\'' +
", createDate=" + createDate +
", updateUser='" + updateUser + '\'' +
", updateDate=" + updateDate +
", clientNames=" + clientNames +
", clientAddresses=" + clientAddresses +
", clientEmails=" + clientEmails +
", clientCode='" + clientCode + '\'' +
", alternateClientCode='" + alternateClientCode + '\'' +
", birthDate=" + birthDate +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCreateUser() {
return createUser;
}
public void setCreateUser(String createUser) {
this.createUser = createUser;
}
public LocalDateTime getCreateDate() {
return createDate;
}
public void setCreateDate(LocalDateTime createDate) {
this.createDate = createDate;
}
public String getUpdateUser() {
return updateUser;
}
public void setUpdateUser(String updateUser) {
this.updateUser = updateUser;
}
public LocalDateTime getUpdateDate() {
return updateDate;
}
public void setUpdateDate(LocalDateTime updateDate) {
this.updateDate = updateDate;
}
public Codes getGenderCodes() {
return genderCodes;
}
public void setGenderCodes(Codes genderCodes) {
this.genderCodes = genderCodes;
}
public Set<ClientNames> getClientNames() {
return Collections.unmodifiableSet(clientNames);
}
public Optional<ClientNames> getConfidentialName() {
return clientNames.stream().filter(ClientNames::getConfidentialClient).findFirst();
}
public Optional<ClientNames> getNonAliasName() {
return clientNames.stream().filter(clientNames -> !clientNames.getAlias()).findFirst();
}
public Set<ClientAddresses> getClientAddresses() {return Collections.unmodifiableSet(clientAddresses);}
public Set<ClientEmails> getClientEmails() { return Collections.unmodifiableSet(clientEmails); }
public String getClientCode() {
return clientCode;
}
public void setClientCode(String clientCode) {
this.clientCode = clientCode;
}
public String getAlternateClientCode() {
return alternateClientCode;
}
public void setAlternateClientCode(String alternateClientCode) {
this.alternateClientCode = alternateClientCode;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
}
}
application.yml
spring:
jpa:
show-sql: true
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
hibernate:
dialect: org.hibernate.dialect.SQLServer2008Dialect
properties:
datasource:
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://localhost:1433/;databaseName:DBNAME
username: sa
password:
You have configured the physical naming strategy to use org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl which by implementation should return the Identifier name as set ( which is what you want).
Maybe you also have to configure the implicit naming strategy for the case you have some fields where you didn't set the column name explicitely.
Your application.yml file could look like this (i removed the properties field from it since you didn't used it):
spring:
jpa:
show-sql: true
hibernate:
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
hibernate:
dialect: org.hibernate.dialect.SQLServer2008Dialect
datasource:
driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
url: jdbc:sqlserver://localhost:1433/;databaseName:DBNAME
username: sa
password:
If none of the strategies works for you, you can always write your own by implementing the interfaces org.hibernate.boot.model.naming.PhysicalNamingStrategy (for physical naming) and org.hibernate.boot.model.naming.ImplicitNamingStrategy (for implicit naming).
Turns out the context of my application was being bypassed as I was not using all of spring boot and partially creating my own context. Rebuilding the context to work more in line with spring boot made it pick up the proper configuration from applicatiom.yml.

How can save order detail associated with the user and How can I return order data associated with the user details based the url parameters?

I have created the User and Order entities as bellow. What I want to achieve is that if http://localhost:8080/users/username? is given I want to return only the user detail based on username provided. if http://localhost:8080/users/username?detail=true, I want to return user detail and order details for the username provided. How can I achieve this?
User.java
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String userName;
private String password;
private String firstName;
private String lastName;
private String gender;
private String lastLoggedIn;
#OneToMany
List<Order> listOfOrder;
//constructors
//getter and setter
}
Order.java
#Entity
public class Order
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private float amount;
private String createdAt;
private String deliveredDate;
//constructors
//getter and setter
}
Controller.java
//CREATE CUSTOMER
#RequestMapping(method = POST, value = "/create")
public ResponseEntity createCustomerDetails(#RequestParam String userName, String password, String firstName,
String lastName, String gender) {
String lastLogged = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
User user = new User(userName, password, firstName, lastName, gender, lastLogged);
userRepository.save(user);
return ResponseEntity.status(OK).body(user.getId() + " User were successfully saved");
}
//CREATE ORDER
#RequestMapping(method = POST, value = "/order/{userName}")
public ResponseEntity createOrder(#PathVariable ("userName") String userName, #RequestParam float amount)
{
String createdAt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
String deliveredDate = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(Calendar.getInstance().getTime());
User user = orderService.findUser(userName);
if (!user.equals(null))
{
Order order = new Order(amount,createdAt,deliveredDate);
user.getListOfOrder().add(order);
return ResponseEntity.status(OK).body("order details were saved under "+user.getUserName() + " "+user.getFirstName());
}
return ResponseEntity.status(NOT_FOUND).body(null + " was not found");
}
//GET THE USER DETAILS
#RequestMapping(method = GET, value = "/users/{userName}")
public ResponseEntity getUserDetail(#PathVariable("userName") String userName,
#RequestParam(defaultValue ="none", required = false) String detail) {
if (!detail.equals("none")){
return .....;
}else {
return ........;
}
}
UserRepository
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByUserName(String userName);
}
If you're ok with doing the serialization manually, you can employ JsonView to determine what gets serialized.
https://www.baeldung.com/jackson-json-view-annotation
User.java
import com.fasterxml.jackson.annotation.JsonView;
public class User {
#JsonView(Views.Lite.class)
private String name;
#JsonView(Views.Full.class)
private List<Order> orders;
}
Views.java
public class Views {
public static class Lite {}
public static class Full extends Lite {}
}
UserController.java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class UserController {
#Autowired
private UserRepository userRepository;
#Autowired
private ObjectMapper mapper;
#GetMapping("/user/{username}")
public ResponseEntity<String> getUserDetail(#PathVariable String username, #RequestParam(required = false) String detail) throws JsonProcessingException {
User user = userRepository.findByUserName(username);
Class viewClass = Views.Lite.class;
if (!StringUtils.isEmpty(detail)) {
viewClass = Views.Full.class;
}
return ResponseEntity.status(HttpStatus.OK)
.body(mapper.writerWithView(viewClass).writeValueAsString(user));
}
}

Spring Boot Hibernate one to many. Lazy many not written to database

I have two entities. Person (one) and Contactdetails (many).
Contactdetails are lazy loading. I have no problem with creating a new Person and get it written into the database. For my test I use the MySQL database installed locally filled up with dummy data. I run kind of integration test.
In my test code I use #Transactional annotation in order to stay inside one session where I fetch a Person, create a new Contacdetails, connect them together and then save the Person, which will cascade save the Contactdetails too. In theory...
Contactdetails is not written to the database. Interestingly, if I write to console all Contactdetails inside the #Transactional annotated test method, I see the new Contactdetails created. As soon es I leave this test method, this freshly created Contactdetails is no more visible.
My Entities are as follows:
Person:
package com.szivalaszlo.contracts.landon.data.entity;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
import java.time.LocalDate;
import java.util.Objects;
#Entity
#Table(name="person")
public class Person {
private static Logger logger = LogManager.getLogger();
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "date_of_birth")
private LocalDate dateOfBirth;
#Column(name = "first_name_mother")
private String firstNameMother;
#Column(name = "last_name_mother")
private String lastNameMother;
#OneToMany(fetch=FetchType.LAZY, mappedBy = "person", cascade = CascadeType.ALL) // refers to person attribute of Contactdetails class
private List<Contactdetails> contactdetails;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name = "buyer_contract",
joinColumns = #JoinColumn(name = "person_buyerid"),
inverseJoinColumns = #JoinColumn(name = "contractid"))
List<Contract> buyerContracts;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(name = "seller_contract",
joinColumns = #JoinColumn(name = "person_sellerid"),
inverseJoinColumns = #JoinColumn(name = "contractid"))
List<Contract> sellerContracts;
public Person(){
}
public Person(String firstName, String lastName, String dateOfBirth, String firstNameMother, String lastNameMother) {
this.firstName = firstName;
this.lastName = lastName;
this.dateOfBirth = LocalDate.parse(dateOfBirth);
this.firstNameMother = firstNameMother;
this.lastNameMother = lastNameMother;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public LocalDate getDateOfBirth() {
return dateOfBirth;
}
public void setDateOfBirth(LocalDate dateOfBirth) {
this.dateOfBirth = dateOfBirth;
}
public String getFirstNameMother() {
return firstNameMother;
}
public void setFirstNameMother(String firstNameMother) {
this.firstNameMother = firstNameMother;
}
public String getLastNameMother() {
return lastNameMother;
}
public void setLastNameMother(String lastNameMother) {
this.lastNameMother = lastNameMother;
}
public List<Contactdetails> getContactdetails() {
return contactdetails;
}
public void addContactdetail(Contactdetails contactdetail){
if(null == contactdetails){
contactdetails = new ArrayList<Contactdetails>();
}
contactdetails.add(contactdetail);
}
public String getStringForEqualsCheck(){
return firstName+lastName+dateOfBirth.toString()+firstNameMother+lastNameMother;
}
#Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (!(obj instanceof Person))
return false;
if (obj == this)
return true;
return this.getStringForEqualsCheck().equals(((Person) obj).getStringForEqualsCheck());
}
#Override
public int hashCode() {
return Objects.hash(firstName, lastName, dateOfBirth, firstNameMother, lastNameMother);
}
#Override
public String toString() {
return "Person{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", dateOfBirth=" + dateOfBirth +
", firstNameMother='" + firstNameMother + '\'' +
", lastNameMother='" + lastNameMother + '\'' +
'}';
}
}
Contactdetails:
package com.szivalaszlo.contracts.landon.data.entity;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.persistence.*;
import java.util.Objects;
#Entity
#Table(name="contactdetails")
public class Contactdetails {
private static Logger logger = LogManager.getLogger();
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "address")
private String address;
#Column(name = "email")
private String email;
#Column(name = "phone")
private String phone;
#ManyToOne
#JoinColumn(name = "personid", nullable = false)
private Person person;
public Contactdetails(){
}
public Contactdetails(String address, String email, String phone) {
this.address = address;
this.email = email;
this.phone = phone;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
logger.debug("Person is set for contactdetail: " + this.toString() + " person: " + this.person.toString());
}
public String getStringForEqualsCheck(){
return address+email+phone;
}
#Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (!(obj instanceof Contactdetails))
return false;
if (obj == this)
return true;
return this.getStringForEqualsCheck().equals(((Contactdetails) obj).getStringForEqualsCheck());
}
#Override
public int hashCode() {
return Objects.hash(address, email, phone);
}
#Override
public String toString() {
return "Contactdetails{" +
"id=" + id +
", address='" + address + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", person=" + person +
'}';
}
}
Service class:
package com.szivalaszlo.contracts.landon.business.domain;
import com.szivalaszlo.contracts.landon.data.entity.Contactdetails;
import com.szivalaszlo.contracts.landon.data.entity.Person;
import com.szivalaszlo.contracts.landon.data.repository.ContactdetailsRepository;
import com.szivalaszlo.contracts.landon.data.repository.PersonRepository;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.List;
#Service
public class PersonService {
private static Logger logger = LogManager.getLogger();
private PersonRepository personRepository;
private ContactdetailsRepository contactdetailsRepository;
#Autowired
public PersonService(PersonRepository personRepository, ContactdetailsRepository contactdetailsRepository){
this.personRepository = personRepository;
this.contactdetailsRepository = contactdetailsRepository;
}
public int createPerson(String firstName, String lastName, String dateOfBirth, String firstNameMother, String lastNameMother){
Person person = new Person(firstName, lastName, dateOfBirth, firstNameMother, lastNameMother);
if(personAlreadyExistsInDb(person)){
logger.debug("Same person already found in Db. Person: " + person.toString());
return -1;
}else{
personRepository.save(person);
return person.getId();
}
}
private boolean personAlreadyExistsInDb(Person person){
HashSet<Person> allPersonFromDb = personRepository.findAll();
if (allPersonFromDb.contains(person)){
return true;
}else{
return false;
}
}
public void createContactdetailsForPerson(Person person, String address, String email, String phone){
Contactdetails contactdetails = new Contactdetails(address, email, phone);
if(contactdetailsAlreadyExistForPerson(contactdetails, person)){
logger.debug("Same contactdetail for person already found " + person.toString() + " " + contactdetails.toString());
} else{
contactdetails.setPerson(person);
person.addContactdetail(contactdetails);
contactdetailsRepository.save(contactdetails);
personRepository.save(person);
}
}
private boolean contactdetailsAlreadyExistForPerson(Contactdetails contactdetails, Person person){
List<Contactdetails> allContactdetailsForPersonFromDb = person.getContactdetails();
if(null == allContactdetailsForPersonFromDb || allContactdetailsForPersonFromDb.size() == 0){
return false;
}
if(!allContactdetailsForPersonFromDb.contains(contactdetails)){
return false;
}
return true;
}
}
Test class:
package com.szivalaszlo.contracts;
import com.szivalaszlo.contracts.landon.business.domain.PersonService;
import com.szivalaszlo.contracts.landon.data.entity.Contactdetails;
import com.szivalaszlo.contracts.landon.data.entity.Person;
import com.szivalaszlo.contracts.landon.data.repository.ContactdetailsRepository;
import com.szivalaszlo.contracts.landon.data.repository.PersonRepository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Random;
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PersonServiceTest_ {
private static Logger logger = LogManager.getLogger();
#Autowired
private PersonRepository personRepository;
#Autowired
private ContactdetailsRepository contactdetailsRepository;
Random rand = new Random();
int randomNumber = rand.nextInt(899)+100;
private String address = "test address street 1 in City "+randomNumber;
private String email = "testemail#exmaple.com " +randomNumber;
private String phone = "+41 12 345 78 90 " +randomNumber;
#Test
#Transactional(propagation = Propagation.REQUIRED)
public void it_should_save_contactdetail_to_person(){
PersonService testPersonService = new PersonService(personRepository, contactdetailsRepository);
Person testPerson = personRepository.findById(179); //I have an id# 179 Person in the database fully detailed.
testPersonService.createContactdetailsForPerson(testPerson, address, email, phone);
List<Contactdetails> allStoredContactdetailsinDB = contactdetailsRepository.findAll();
allStoredContactdetailsinDB.forEach(item->System.out.println(item));
}
}
The test runs with no error. I see the following output in the console:
Bradlystad, ND 98886-5789', email='maverick85#example.com', phone='464-812-3618', person=Person{id=98, firstName='Eldridge', lastName='Reichel', dateOfBirth=1981-08-07, firstNameMother='Brianne', lastNameMother='Ryan'}}
Contactdetails{id=99, address='569 Langosh Turnpike Suite 235
East Archibald, FL 43208-3081', email='spouros#example.org', phone='08976297815', person=Person{id=99, firstName='Myrtie', lastName='Graham', dateOfBirth=1982-01-19, firstNameMother='Libby', lastNameMother='Veum'}}
Contactdetails{id=100, address='010 Pfeffer Islands
Kiehnside, FL 25044-1157', email='paucek.grover#example.com', phone='1-157-850-0688x390', person=Person{id=100, firstName='Katheryn', lastName='Hoppe', dateOfBirth=2009-06-22, firstNameMother='Virginie', lastNameMother='Donnelly'}}
Contactdetails{id=106, address='test address street 1 in City 623', email='testemail#exmaple.com 623', phone='+41 12 345 78 90 623', person=Person{id=179, firstName='TestJhonFirst808', lastName='TestSmithLast808', dateOfBirth=1990-11-11, firstNameMother='TestJackieMotherFirst808', lastNameMother='TestSmithMotherLast808'}}
The interesting part is the last line which shows, that the Contactdetails is created in the db with id#106.
When I query the database after the test is run, I don't see the new line in the table.
By default, test transactions will be automatically rolled back after
completion of the test; however, transactional commit and rollback
behavior can be configured declaratively via the #Commit and #Rollback
annotations
Add the following annotation to your test:
#Rollback(false)

Resources