Not able to join 2 tables in spring data jpa - spring-boot

I am new to spring-data-jpa. I am working on a task management system.
I have 2 entities:
public class Task {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long taskId;
private String title;
private String description;
private Status status;
#OneToOne
#JoinColumn(name = "user_id", referencedColumnName = "userId")
private User assignee;
and:
#Entity
#Table(name = "tbl_user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long userId;
private String name;
private String email;
private Active active;
private String password;
}
I have an endpoint that creates a new task:
#PostMapping
#ResponseStatus(HttpStatus.CREATED)
public TaskResponse addTask(#Valid #RequestBody Task task){
return taskService.addTask(task);
}
This is the implementation:
#Override
public TaskResponse addTask(Task task) {
taskRepository.save(task);
return mapToTaskResponse(task);
}
The error I get when I send a request is:
2023-01-24 15:10:01.825 WARN 1961 --- [nio-8080-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.tasksmanagement.entity.User` (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.tasksmanagement.entity.User` (although at least one Creator exists): no int/Int-argument constructor/factory method to deserialize from Number value (1)<EOL> at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 5, column: 17] (through reference chain: com.tasksmanagement.entity.Task["assignee"])]
I am not sure what am I doing wrong. Basically I create a user and send the id of this user in the request (the assignee field in the payload - screenshot attached) in order to assign the new task to that user.
Can anyone please assist and help me understand what is the issue with the request?
Should I send something else instead of the userId?
Thank you

assignee is of type User not Integer, and yet you are sending assignee:1 which fails the deserialization of your request.
it should be rather
assignee:{ userId:1 }
but it will fail anyway on later on during atempt to persist the Task (but that is a different issue)

So basically you want to create a Task for a User who has an id of 1.
All you need to do is map you User object with your Task object.
Try the following
#Override
public TaskResponse addTask(Task task) {
User assignee = new User();
assignee.setUserId(task.assignee);
task.setAssignee(assignee);
taskRepository.save(task);
return mapToTaskResponse(task);
}

Related

Child table is not mapping in OneToMany relationship in JPA

I am trying to establish One to many relationship between User and Role.
One user can have many role.
Here is the code for User class
#Entity
public class User {
#Id
private int id;
private String name;
private String password;
private String email;
private String phoneNo;
#OneToMany(
targetEntity = Role.class,
mappedBy = "user",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY
)
private Set<Role> roles;
// Getters, setters and Constructor
Code for the Role class
#Entity
public class Role {
#Id
#GeneratedValue
private int roleId;
private String role;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
// Getters, setters and Constructor
POST request on Postman is
{
"id":101,
"name": "rahul",
"password": "456",
"email": "rahul#gmail.com",
"phoneNo": "1234561234",
"role": [{
"role":"USER"
}]
}
Code on Configuration part
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http)throws Exception
{
http.csrf().disable();
}
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
}
Code On Controller part
#RestController
public class AdminController {
#Autowired
UserRepository userRepo;
#Autowired
BCryptPasswordEncoder encryptPassword;
#PostMapping("/admin/add")
public String addUserByAdmin(#RequestBody User user)
{
String pass = user.getPassword();
String encrypt = encryptPassword.encode(pass);
user.setPassword(encrypt);
userRepo.save(user);
return "User added Successfully";
}
}
Role table connection to database through Jpa
public interface RoleRepository extends JpaRepository<Role, Integer> {
}
User table connection to database through Jpa
public interface UserRepository extends JpaRepository<User,Integer>{
}
Here problem is User table is mapped properly but Role table is not getting mapped.
roleId role user_id
NULL NULL NULL
Where I am wrong ? Could anyone help me ?
On your controller method below, have you tried debugging the incoming request User object?
Anyways, I have below points here:
First, looking into your request body, the field for your roles is named role while your User object has a field roles thus, I'm pretty sure it is null during your processing since it will not be deserialized there due to mismatch field names. Try changing your request body to something like this:
{
"id":101,
"name": "rahul",
"password": "456",
"email": "rahul#gmail.com",
"phoneNo": "1234561234",
"roles": [{
"role":"USER"
}]
}
Second, if you check your database, the roles will be persisted however your foreign key user_id is null. This is expected. The cascade you did on the User object will only means that (since you use CascadeType.ALL) once you save the User object the save operation will also be cascaded to the Role object however, JPA still needs to know the relationship thus you have to set the user for each role object. Hence, you can update your controller method to something below:
#PostMapping("/admin/add")
public String addUserByAdmin(#RequestBody User user)
{
String pass = user.getPassword();
String encrypt = encryptPassword.encode(pass);
user.setPassword(encrypt);
// JPA needs to know this relationship...
user.getRoles().forEach(role -> role.setUser(user));
userRepo.save(user);
return "User added Successfully";
}
Now you can try and see that your expected behavior should now be happening.
Additional recommendations:
Why are we passing ID field on the user request? You can just remove that from your request body and use below to auto-generate your IDs to avoid Unique index or primary key violation exceptions on all of your entities:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
You can also remove the targetEntity = Role.class on the mapping as it is only used for generics and for your case clearly you are not using generics for Set. Update your User object for roles mapping:
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Role> roles;
Lastly, it is better if you can wrap your incoming payload to a DTO since you would not want to expose your entity/model to your API but I am thinking this is just for your test environment.
You need to flush the changes to the database when using save(), try this instead:
userRepo.saveAndFlush(user);

How to write query for many to one mapped entity in JpaRepository

I have two entities and mapped those using many-to-one annotation but after writing a query for find object using another table id I got an error when I commented out that line and method called to that application work but I want to implement that functionality and please help me
These are my entity classes:
#Entity
#Table(name = "Contract")
public class Contract implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "contractId")
private long contractId;
#Column(name="start_date")
private Date st_date;
#Column(name="end_date")
private Date end_date;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "hotel_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private Hotel hotel;
// getters and setters
Second entity
#Entity
#Table(name="Hotel")
public class Hotel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="hotel_id")
private long hotel_id;
#Column(name="hotel_name")
private String hotel_name;
#Column(name="hotel_location")
private String hotel_location;
#Column(name="hotel_email")
private String hotel_email;
#Column(name="hotel_telephone")
private String hotel_telephone
// getters and setters
My contract service class
#Service
public class ContractService {
#Autowired
private ContractRepository contractRepository;
#Autowired
private HotelRepository hotelRepository;
public List<Contract> getAllContracts(){
return contractRepository.findAll();
}
public List<Contract> findByHotelId(Long hotelId,Pageable pageable){
return contractRepository.findByHotelId(hotelId, pageable);
}
public ResponseEntity<?> deleteContract(Long hotelId, Long contractId)
{
return contractRepository.findByIdAndHotelId(contractId,
hotelId).map(Contract -> {
contractRepository.delete(Contract);
return ResponseEntity.ok().build();
}).orElseThrow(() -> new ResourceNotFoundException("Comment not found
with ContractId " + contractId + " and hotelId " + hotelId));
}
My contract repository
#Repository
public interface ContractRepository extends JpaRepository<Contract, Long> {
List<Contract> findByHotelId(Long hotelId, Pageable pageable);
Optional<Contract> findByIdAndHotelId(Long id, Long hotelId);
}
I got this error when running my project
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'contractController': Unsatisfied dependency expressed through field 'contractService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'contractService': Unsatisfied dependency expressed through field 'contractRepository'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'contractRepository': Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.List com.sunTravel.sunRest.repository.ContractRepository.findByHotelId(java.lang.Long,org.springframework.data.domain.Pageable)! No property id found for type Hotel! Traversed path: Contract.hotel.
First Solution: based on your stack trace, Spring data is looking for id variable (primary key) in your Hotel class. So please change private long hotel_id; to private long id;
Another solution (no need to change anything just add your own query):
write your own JPA query using #Query.
Example:
#Query("SELECT contract from Contract as contract where contract.hotel.hotel_id = :hotelId")
List<Contract> findByHotelId(Long hotelId, Pageable pageable);
You should rename your Primary Key from hotel_id to id then only your repository method will work.
#Entity
#Table(name="Hotel")
public class Hotel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="hotel_id")
private long id;
#Column(name="hotel_name")
private String hotel_name;
#Column(name="hotel_location")
private String hotel_location;
#Column(name="hotel_email")
private String hotel_email;
#Column(name="hotel_telephone")
private String hotel_telephone
// getters and setters

Spring Data MongoRepository save causing Duplicate Key error

Here is the Entity:
#Document
#Data
public class ApplicationUser {
private String name;
#Indexed(unique = true)
private String email;
private String organization = null;
// other fields
}
I fetch this user using their email and then change their name. I use the autowired instance of ApplicationUserRepository.
ApplicationUser applicationUser = applicationUserRepository.findByEmail("abc#gmail.com");
applicationUser.setName("John Doe 2");
Then I try to update this entity again in the database:
applicationUserRepository.save(applicationUser);
I get a duplicate key error on the field email. Why is this happening?
As far as I get from the documentation, the save method updates the same document if the ObjectId is the same. Since I haven't changed the objectId then why is it trying to create a new ApplicationUser during saving?
I got the solution.
When creating the entity, I have to explicitly declare the Id.
Here is the Entity:
#Document
#Data
public class ApplicationUser {
#Id
private ObjectId _id;
private String name;
#Indexed(unique = true)
private String email;
private String organization = null;
// other fields
}
I had similar issue where I was retrieving by id and then trying to update the retrieved POJO and then save it back with MongoRepository.save() call. It was on MongoDB 4.x with Spring Boot 2.1.0. I added the #Transactional annotation to my service method and everything worked like a charm. The duplicate key exception on id field was resolved.

JsonMappingException: Can not construct instance of

I have an entity with two columns refering same column in other table. Basically, a Transaction depends on Account: when creating a new transaction a send money from one account to another.
Account:
#Entity
#Table(name = "accounts")
public class Account implements java.io.Serializable {
private static final long serialVersionUID = 2612578813518671670L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "idaccount", unique = true, nullable = false)
private Long idaccount;
#Column(name = "name", length = 50)
private String name;
#NotNull
#ManyToOne
#JoinColumn(name = "iduser")
private User user;
...
Transaction:
#Entity
#Table(name = "transactions")
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idtransaction", unique = true, nullable = false)
private Long idtransaction;
private BigDecimal amount;
#NotNull
#ManyToOne
#JoinColumn(name = "SOURCE_ACCOUNT")
private Account sourceAccount;
#NotNull
#ManyToOne
#JoinColumn(name = "TARGET_ACCOUNT")
private Account targetAccount;
...
TransactionController
#CrossOrigin
#RestController
#RequestMapping("/transaction")
public class TransactionController {
#Autowired
TransactionService transactionService;
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<Transaction> addTransaction(#RequestBody Transaction Transaction) {
transactionService.save(Transaction);
return new ResponseEntity<Transaction>(Transaction, HttpStatus.CREATED);
}
...
If I try post to create a transaction (naturally I have the accounts already created):
{
"amount": 111,
"sourceAccount": 1,
"targetAccount": 2
}
I get:
Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Can not construct instance of com.mycompany.basicbank.model.Account: no int/Int-argument constructor/factory method to deserialize from Number value (1)
at [Source: java.io.PushbackInputStream#63447acf; line: 3, column: 18] (through reference chain: com.mycompany.basicbank.model.Transaction["sourceAccount"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.mycompany.basicbank.model.Account: no int/Int-argument constructor/factory method to deserialize from Number value (1)
at [Source: java.io.PushbackInputStream#63447acf; line: 3, column: 18] (through reference chain: com.mycompany.basicbank.model.Transaction["sourceAccount"])
So my question is: what should I check in order to fix "Can not construct instance of com.livingit.basicbank.model.Account: no int/Int-argument constructor/factory method to deserialize from Number"?
The problem is the json you are sending doesn't exactly match the Transaction class. hence you see the error.
But what you are trying to achieve is a valid scenario and can be done.
Some options.
Create a new class(not Transaction) which matches the json. Like
class TransactionClient {
BigDecimal amount,
Long sourceAccount,
Long targetAccount
}
And in the backend(controller or some in service) you can get the Acccounts from database with this sourceAccount and targetAccount and create a transaction object with this objects and save.
From the frontend call backend to get the Accounts(json) for these source and target accounts and then call your transaction endpoint with the json like this
{
"amount": 111,
"sourceAccount": {
"idaccount" :123123,
..... // All the Non Null fields
},
"targetAccount": {
"idaccount" :45554,
..... // All the Non Null fields
},
}

retrieving data from database as json in spring boot

I have a MySQL database and I want to retrieve some data as json.
And I have an entity Offre wich has #OneToMany relation with the AssociationCandidatOffre entity.
and I have an api which calles this method in my repository :
offreRepository.findAll();
Offre entity :
#Entity
public class Offre implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "CODE_OFFRE")
private Long codeOffre;
private String titre;
#OneToMany(mappedBy = "offre")
private Collection<AssociationCandidatOffre> associationCandidatOffres;
public Collection<AssociationCandidatOffre> getAssociationCandidatOffres() {
return associationCandidatOffres;
}
public void setAssociationCandidatOffres(Collection<AssociationCandidatOffre> associationCandidatOffres) {
this.associationCandidatOffres = associationCandidatOffres;
}
//... getters/setters
}
AssociationCandidatOffre entity :
#Entity
public class AssociationCandidatOffre implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long idAssociation;
private String lettreMotivation;
private String tarifJournalier;
private Date dateDisponibilite;
#ManyToOne
private Candidat candidat;
#ManyToOne
private Offre offre;
#JsonIgnore
#XmlTransient
public Candidat getCandidat() {
return candidat;
}
#JsonSetter
public void setCandidat(Candidat candidat) {
this.candidat = candidat;
}
#JsonIgnore
#XmlTransient
public Offre getOffre() {
return offre;
}
#JsonSetter
public void setOffre(Offre offre) {
this.offre = offre;
}
//... getters/setters
}
the problem is when I call the api /offres to return me a json object I get this error message instead :
Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: could not extract ResultSet (through reference chain: java.util.ArrayList[0]->com.***.Rekrute.entities.Offre["associationCandidatOffres"]);
nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not extract ResultSet (through reference chain: java.util.ArrayList[0]->com.***.Rekrute.entities.Offre["associationCandidatOffres"])
when I use #JsonIgnore in the getAssocationCandidatOffres I dont get any errors but I want that association in the json result as well.
Normally, this shouldn't generate any error since I have #JsonIgnore in the other side of the relation which is getOffre().
how can I solve this problem ?
You can't convert a bidirectional relation of an enitity to JSON.
You get an endless loop.
JSON-Parser starts with the entity Offer and reads the associated AssociationCandidatOffre via getAssociationCandidatOffres(). For every AssociationCandidatOffre the JSON-Parser read getOffre() and starts again. The parser don't know when he must end.

Resources