How do I insert data in a #JoinColumn column in Spring Boot? - java

When I pass data by POST request to my TodoItem model, only the columns specified by #column get filled in, but the #JoinColumn column is null even if I use the column name in the JSON I'm sending over. My GET request API work just fine however, so I omitted them from controller code.
TodoItem.java
#Entity
#Table(name = "todo_item")
public class TodoItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "todo")
private String todo;
#Column(name = "completed")
private boolean completed;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
public TodoItem() {
}
public TodoItem(String todo, boolean completed) {
this.todo = todo;
this.completed = completed;
}
// setters and getters
}
My constructor doesn't mention user_id, don't think it needs it though, but I may be wrong.
TodoController.java
#RestController
#RequestMapping("/api")
public class TodoController {
#Autowired
private UserRepository userRepository;
#Autowired
private TodoRepository todoRepository;
#PostMapping("/addItem")
public TodoItem addTodoItem(#RequestBody TodoItem todoItem) {
return this.todoRepository.save(todoItem);
}
}
I send POST to http://localhost:8080/api/addItem
Request:
{
"todo": "finish portfolio",
"completed": false,
"user_id": 1
}
However in my MySQL workbench, todo and completed get populated properly but user_id says null

In spring data (using hibernate or JPA), when saving entity which has reference to another entity . you must first fetch the referenced object first by id and then set it using setter in persistence entity. after that you can save and get (FK column) to be saved.
for example you first must use user repository and call
User user = userRepository.findById(userId);
and then
todoItem.setUser(user);
after that you can save item and FK column will get populated.
I think this the way to save reference entity. you can not relay on int id only.
also your request body JSON must be like this :
{
"todo": "finish portfolio",
"completed": false,
"user": {
"user_id":1
}
}
also it best practice to define and use DTO object instead of entity itself.

Related

I am getting NoSuchElementException: No value present

I have two classes one for Customers and one for Transaction. In the transaction, I have a field custID(int) which is also present in Customer. I have all the getter and setter, repos, services, and controller as well. But in one of my methods in the service layer, I am getting NoSuchElementException.
I understand that while the code runs and checks for a record in the database with passed custID, it cannot find the record. But I have mentioned what to do in such case. But my code doesn't move to the code block at all.
Customer Class
#Entity
#Table(name = "customers")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int custID;
private String custName;
private String email;
private String phone;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private LocalDate registrationDate;
Transaction Class
#Entity
#Table(name = "transactions")
public class Transaction {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int transID;
private int custID;
private int transAmount;
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private LocalDate transDate;
Service layer from where the issue arises:
TransactionServiceImpl
#Override
public Transaction addTransaction(CustomerTransaction customerTransaction) {
Customer customer = customerTransaction.getCustomer();
Transaction transaction = customerTransaction.getTransaction();
if(customerService.findCustomerByID(transaction.getCustID()) == null) {
customerService.addCustomer(customer);
return transactionRepository.save(transaction);
} else{
return transactionRepository.save(transaction);
}
}
What I am doing is, pass a wrapper object CustomerTransaction that has the info for the customer and transaction. And check if the customer is already registered with custID. If its there, it only records the transaction(which works fine as in 'else' block). But if it is not there I want to record the customer and the transaction both as in 'if' block. But it throws the NoSuchElementException: No value present.
But if I am to pass only the customer details via customer's service layer it adds the customer.
CustomerServiceImpl
#Override
public Customer addCustomer(Customer customer) {
return customerRepository.save(customer);
}
Postman Requests:
For customer only:
"custName": "Bibek Bhattarai",
"email": "spongebob#gmail.com",
"phone": 9803064423,
"registrationDate": "03-01-2023"
}
For customerTransaction:
{
"customer":{
"custName": "Sponge Bob",
"email": "spongebob#gmail.com",
"phone": 9803064423,
"registrationDate": "03-01-2023"
},
"transaction":{
"custID":9,
"transAmount": 5000,
"transDate": "04-01-2023"
}
}
You may get this exception because the value returned by findCustomerByID() is an Optional of Customer (Optional<Customer>) and not null.
Instead of customerService.findCustomerByID(transaction.getCustID()) == null you should have customerService.findCustomerByID(transaction.getCustID()).isEmpty().

JPA ManyToOne returning complete parent entity vs id

I have a springboot application with JPA. The ORM setup is a ManyToOne and I have roughly followed the excellent post here from Vlad so that I have only setup the #ManyToOne on the child.
My Entities are HealthCheck (Many) which must have a Patient (One). The issue is that when I retrieve a HealthCheck via my Rest controller instead of getting just the id of the patient I get the whole entity.
For my project I probably will get the whole patient with a HealthCheck, but I would like to know how i could get just the HealthCheck with the patient_id instead of the whole patient entity if I so needed to do so.
HEALTH CHECK
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Data
public class HealthCheck {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Patient patient;
//Getters and Setters
PATIENT
#Entity
#Data
public class Patient {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(unique = true)
#NotEmpty(message = "Name must not be null or empty")
private String name;
// Getters and Setters
The HealthCheckServiceImpl uses the derived queries to get one by id, and its this call thats used to get a HealthCheck by the REST controller:
#Override
public HealthCheck get(Long id) {
log.info("Getting a single Health Check");
return healthCheckRepository.getById(id);
}
The result of a call to the REST controller results in something like:
{
"id": 2,
"patient": {
"id": 2,
"name": "Jono",
"hibernateLazyInitializer": {}
},
"other fields": "some comment",
"hibernateLazyInitializer": {}
}
Note1 the whole patient entity is returned
Note2 that because I have only used the ManyToOne annottaion on the child side I dont get the Jackson recursion issues some others do
QUESTION: How can I control the returned HealthCheck so that it includes the patient_id not the whole object?
UPDATE
The line that calls to the service is :
#GetMapping("get/{id}")
public ResponseEntity<HealthCheck> getHealthCheck(#PathVariable("id") Long id) {
return ResponseEntity.ok()
.header("Custom-Header", "foo")
.body(healthCheckService.get(id));
I breakpoint on healthCheckService.get(id)) but noting on the debugger looks like it contains an entity reference:
UPDATE2
Well, it seems you're returning your entities objects directly from your controller.
The issue is that when I retrieve a HealthCheck via my Rest controller instead of getting just the id of the patient I get the whole entity.
You can use the DTO Pattern explained here and here as response in your controller.
That, also decouples your domain from your controller layer.
This is a simplified example:
#Data
#NoArgsConstructor
public class HealthCheckDto {
private Long id;
private PatientDto patient;
private String otherField;
public HealthCheckDto(HealthCheck healthCheck) {
this.id = healthCheck.getId();
this.patient = new PatientDto(healthCheck.getPatient());
this.otherField = healthCheck.getOtherField();
}
}
#Data
#NoArgsConstructor
public class PatientDto {
private Long id;
public PatientDto(Patient patient) {
this.id = patient.getId();
}
}
// In your controller
public ResponseEntity<HealthCheckDto> getHealthCheck(Long id) {
HealthCheck healthCheck = healthCheckService.getById(id);
return ResponseEntity.ok(new HealthCheckDto(healthCheck));
}

Spring: Create object with endpoint by id

I'm currently creating a microservice with spring boot and mysql to manage information about auctions. I have created a Bid-object and an Offer-object. Next to some properties of bid and offers, the most important thing here is the OneToMany-Relationship between Offer and Bid, since obviously every offer can have multiple related Bids.
I use the default JpaRepository-Interface for my database interactions, and tested my database structure by entering data and testing if I would get the correct output. This all worked fine, but when I tried to test the endpoints of my service that entered the data, I got some curious behaviour. First of all, here's my structure, so you can keep up with what I'm talking about. These are (shortened) versions of my Bid and Offer-Objects:
#Entity
#Data
public class Bid {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotNull
private String bidderUUID;
#NotNull
#JsonBackReference
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "offerId")
private Offer offer;
#NotNull
private Integer amount;
private Boolean hasWon;
}
#Entity
#Data
public class Offer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
private String creatorUUID;
#JsonManagedReference
#OneToMany(mappedBy = "offer")
List<Bid> bids;
}
This is my very simple repsitory and controller:
public interface BidRepository extends JpaRepository<Bid, Long> {
}
#RestController
#RequestMapping("/bid")
public class BidController {
#Autowired
private BidRepository bidRepository;
#GetMapping("/bids")
public List<Bid> getAllBids() {
return bidRepository.findAll();
}
#PostMapping("/add")
public void createBid(#RequestBody Bid request) {
bidRepository.saveAndFlush(request);
}
}
With and offer with the id 27 in the database I proceeded to send a bid to the service.
I'm using postman to test my requests, and this is what I put in my request body, when adressing the endpoint localhost:8080/bid/add:
{
"amount": 2,
"bidderUUID": "eine uuid",
"offerId": 27
}
I received a 200 OK response, and thought, seems fine, but the data in the database is wrong, since it looks like this:
The offer is missing, even though the ID 27 definitely exists. Also, when I'm entering 27 manually and pushing it to the database, the data is correctly recognized.
I think this problem has something to do with the fact, that I expect an offer-object when posting the new bid, but only give him the ID, but when I enter the entire object, I get an org.hibernate.PersistentObjectException: detached entity passed to persist.
How can I make spring accept the id of the offer, when transmitting a new bid object?
This happed to me also, so as the solution I added the mapping column as Entity variable. In your case if I say, your Bid entity is missing the offer_id column mapping, although it mentioned the relationship. Add below entry in your Bid table as below:
#Column(name = "offer_id")
private Long offerId;
// generate setter-getter
add referencedColumn on the #JoinColumn annotation.
#JoinColumn(name = "offerId", referencedColumn = "id")
private Offer offer;

Springboot PUT request fails when updating nested Json object

I have a customer object and inside that customer object i have a login object which contains username and password. When i do a POST the request works fine however when i try to do a PUT request it fails. It fails because it says Duplicate entry on the username.
I would like to be able to update the customer details without having to change the username. How can i achieve this.
This is my code :
UserLogin Entity :
#Entity
#Table(name = "Customer",
uniqueConstraints =
{
#UniqueConstraint(columnNames = "email"),
#UniqueConstraint(columnNames = "id"),
#UniqueConstraint(columnNames = "phoneNumber")
}
)
public class Customer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int customerNumber;
#OneToOne(cascade= CascadeType.ALL)
#JoinColumn(name = "loginCredentialsID")
private UserLogin userlogin;
private String phoneNumber;
private String email;
private String physicalAddress;
private String country;
... getters and setters
}
UserLogin Entity :
#Entity
#Table(name = "UserLogin",
uniqueConstraints =
{
#UniqueConstraint(columnNames = "userName")
})
public class UserLogin implements Serializable, UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int loginCredentialsID;
private String username;
private String password;
... getters and setters
}
CustomerService Class :
public Response updatCustomeretails(int id,Customer customer) {
customer.setCustomerNumber(id);
if( customer== null ){
throw new ResourceNotFoundException("Empty", "Missing Data");
}else {
customerRepository.save(customer);
return new Response(" Customer Updated Successfully","Thank you ");
}
When using Sping data JPA to update you should use save which you correctly did when saving on this line customerRepository.save(customer);. However when persisting data to a database in a PUT request JPA uses the keys within your entity mappings to be able to update the proper record.
So in your case you get that error when JPA tries to save a new record rather than an update to an existing record. Your intent is to update but I suspect your keys are missing or they are not properly defined so JPA tries to go and save a new record instead of updating.
So when you do the update(PUT) make sure the object you are passing has the same keys as the one you want to update.

Spring data JPA: how to enable cascading delete without a reference to the child in the parent?

Maybe this is an overly simple question, but I am getting an exception when I try to delete a user entity.
The user entity:
#Entity
#Table(name = "users")
public class User
{
#Transient
private static final int SALT_LENGTH = 32;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#NotNull
private String firstName;
#NotNull
private String lastName;
#Column(unique = true, length = 254)
#NotNull
private String email;
// BCrypt outputs 60 character results.
#Column(length = 60)
private String hashedPassword;
#NotNull
private String salt;
private boolean enabled;
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(updatable = false)
private Date createdDate;
And I have an entity class which references a user with a foreign key. What I want to happen is that when the user is deleted, any PasswordResetToken objects that reference the user are also deleted. How can I do this?
#Entity
#Table(name = "password_reset_tokens")
public class PasswordResetToken
{
private static final int EXPIRATION_TIME = 1; // In minutes
private static final int RESET_CODE_LENGTH = 10;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String token;
#OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
#JoinColumn(nullable = false, name = "userId")
private User user;
private Date expirationDate;
The exception I am getting boils down to Cannot delete or update a parent row: a foreign key constraint fails (`heroku_bc5bfe73a752182`.`password_reset_tokens`, CONSTRAINT `FKk3ndxg5xp6v7wd4gjyusp15gq` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))
I'd like to avoid adding a reference to PasswordResetToken in the parent entity, becaue User shouldn't need to know anything about PasswordResetToken.
It is not possible on JPA level without creating a bidirectional relation. You need to specify cascade type in User class. User should be owner of the relation and it should provide the information on how to deal with related PasswordResetToken.
But if you cannot have a bidirectional relation I would recommend you to setup relation directly in schema generation SQL script.
If you create your schema via SQL script and not via JPA autogeneration (I believe all serious projects must follow this pattern) you can add ON DELETE CASCADE constraint there.
It will look somehow like this:
CREATE TABLE password_reset_tokens (
-- columns declaration here
user_id INT(11) NOT NULL,
CONSTRAINT FK_PASSWORD_RESET_TOKEN_USER_ID
FOREIGN KEY (user_id) REFERENCES users (id)
ON DELETE CASCADE
);
Here is the documentation on how to use DB migration tools with spring boot. And here is the information on how to generate schema script from hibernate (that will simplify the process of writing your own script).
Parent Entity:
#OneToOne
#JoinColumn(name = "id")
private PasswordResetToken passwordResetToken;
Child Entity:
#OneToOne(mappedBy = "PasswordResetToken", cascade = CascadeType.ALL, orphanRemoval = true)
private User user;
If you want the Password entity to be hidden from the client, you can write a custom responses and hide it. Or if you want to ignore it by using #JsonIgnore
If you don't want the reference in the Parent Entity (User), then you have to override the default method Delete() and write your logic to find and delete the PasswordResetToken first and then the User.
You can use Entity listener and Callback method #PreRemove to delete an associated 'Token' before the 'User'.
#EntityListeners(UserListener.class)
#Entity
public class User {
private String name;
}
#Component
public class UserListener {
private static TokenRepository tokenRepository;
#Autowired
public void setTokenRepository(TokenRepository tokenRepository) {
PersonListener.tokenRepository = tokenRepository;
}
#PreRemove
void preRemove(User user) {
tokenRepository.deleteByUser(user);
}
}
where deleteByPerson is very simple method of your 'Token' repository:
public interface TokenRepository extends JpaRepository<Token, Long> {
void deleteByUser(User user);
}
Pay attention on static declaration of tokenRepository - without this Spring could not inject TokenRepository because, as I can understand, UserListener is instantiated by Hybernate (see additional info here).
Also as we can read in the manual,
a callback method must not invoke EntityManager or Query methods!
But in my simple test all works OK.
Working example and test.

Categories