How to show data from table that is mapped with another table in Many To One relationship full stack using Spring boot (backend) and Thymeleaf (front) - spring

This is full-stack project based on Spring Boot (backend) and Thymeleaf (frontend). I have this page (table) on my website which shows all hotels in my Hotel Management System App:
When I click on Details it shows new page for particular hotel:
When I click on View Rooms it should show the list of the rooms available for that particular Hotel. I wonder how can I do that? So far, I was only available to list all the rooms (not related to particular hotel) but I want to list the rooms for the particular hotel only.
class Room is mapped with class Hotel by #ManyToOne relationship, which means that several rooms can be listed in one hotel (I will show souce code bellow). When I want to create new room, there is an option to choose to which hotel that particular room belongs:
Here is the code of classes Room and Hotel :
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Builder
#Table(
name = "room"
)
public class Room {
#Id
#SequenceGenerator(
name = "room_sequence",
sequenceName = "room_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "room_sequence"
)
private Long roomId;
#Column(
name = "name",
nullable = false
)
private String name;
#Column(
name = "price"
)
private Integer price;
#ManyToOne
#JoinColumn(
name = "hotel_id",
referencedColumnName = "hotelId"
)
private Hotel hotel;
}
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Builder
#Table(
name = "hotel"
)
public class Hotel {
#Id
#SequenceGenerator(
name = "hotel_sequence",
sequenceName = "hotel_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "hotel_sequence"
)
private Long hotelId;
#Column(
name = "name",
nullable = false
)
private String name;
#Column(
name = "star",
nullable = false
)
private Integer star;
#Column(
name = "location",
nullable = false
)
private String location;
#JsonIgnore
#OneToMany(mappedBy = "hotel")
#ToString.Exclude
private Set<Room> rooms;
}
And here is the whole RoomController with methods to: create new room, update existing room, delete room, show "create new room" form, show "update existing room" form etc.:
#Controller
#RequestMapping("/hms/rooms")
public class RoomController {
private final RoomServiceImpl roomService;
private final HotelServiceImpl hotelService;
#Autowired
public RoomController(RoomServiceImpl roomService, HotelServiceImpl hotelService) {
this.roomService = roomService;
this.hotelService = hotelService;
}
#GetMapping
public String listRooms(Model model) {
model.addAttribute("rooms", roomService.getAllRooms());
return "rooms";
}
#GetMapping("/new")
public String addRoomForm(Model model) {
Room room = new Room();
List<Hotel> listHotels = hotelService.getAllHotels();
model.addAttribute("room", room);
model.addAttribute("listHotels", listHotels);
return "room_create";
}
#PostMapping
public String addNewRoom(#ModelAttribute("room") Room room) {
roomService.addNewRoom(room);
return "redirect:/hms/rooms";
}
#GetMapping("/edit/{roomId}")
public String updateRoomForm(#PathVariable Long roomId, Model model) {
List<Hotel> listHotels = hotelService.getAllHotels();
model.addAttribute("room", roomService.findRoomById(roomId));
model.addAttribute("listHotels", listHotels);
return "room_edit";
}
#PostMapping("/{roomId}")
public String updateRoom(#PathVariable Long roomId, #ModelAttribute("room") Room room) {
// get room from database by id
Room existingRoom = roomService.findRoomById(roomId);
existingRoom.setRoomId(roomId);
existingRoom.setName(room.getName());
existingRoom.setPrice(room.getPrice());
existingRoom.setHotel(room.getHotel());
// save updated room object
roomService.updateRoom(existingRoom);
return "redirect:/hms/rooms";
}
// handler method to handle delete room request
#GetMapping("/{roomId}")
public String deleteRoom(#PathVariable Long roomId) {
roomService.deleteRoomById(roomId);
return "redirect:/hms/rooms";
}
#GetMapping("/details/{roomId}")
public String viewRoom(#PathVariable Long roomId, Model model) {
model.addAttribute("room", roomService.findRoomById(roomId));
return "room_view";
}
}
Also, here is the thymeleaf logic code which is used to get list of hotels on "create new room form":
<div class="form-group">
<label> Hotel </label>
<select th:field="*{hotel}" class="form-control" required>
<th:block th:each="hotel : ${listHotels}">
<option th:text="${hotel.name}" th:value="${hotel.hotelId}"/>
</th:block>
</select>
</div>

You could easily achieve at your repository level as well as in your thymleaf too, please have a look at code below
<span th:each="room : ${hotel.getRooms()}">
I hope it helps!

Related

Spring Boot JPA Using Many-to-Many relationship with additional attributes in the join table

I have two simple classes Student and Course. I am trying to set up many to many relationship between these classes. I want to use additional table whose PRIMARY KEY is the combination of the primary keys of student and course tables (student_id and course_id).
The student class:
#Entity
#Table(name = "student")
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#OneToMany(mappedBy = "student")
private Set<CourseStudent> courses;
}
The course class:
#Entity
#Table(name = "course")
public class Course {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String courseName;
#OneToMany(mappedBy = "course")
Set<CourseStudent> students;
}
The entity that stores the relationship between course and the student:
#Entity
#NoArgsConstructor
#Data
public class CourseStudent {
#EmbeddedId
CourseStudentKey id;
#ManyToOne
#MapsId("studentId")
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#MapsId("courseId")
#JoinColumn(name = "course_id")
Course course;
public CourseStudent(Student student, Course course) {
this.student = student;
this.course = course;
this.rating = 0;
}
int rating;
}
Attention: Since I want to have additional features in this entity (for example, storing the rating of the students for courses), I don't want to use #JoinTable idea that we implement in the Student class.
Since I have multiple attributes in the primary key of CourseStudent entity, I used the following class
#Embeddable
#Data
public class CourseStudentKey implements Serializable {
#Column(name = "student_id")
Long studentId;
#Column(name = "course_id")
Long courseId;
}
I have the following POST request to insert the student into a course:
#PostMapping("/insert/students/{studentId}/courses/{courseId}")
public CourseStudent insertStudentIntoCourse(#PathVariable(value = "studentId") Long studentId,
#PathVariable(value = "courseId") Long courseId) {
if (!studentRepository.existsById(studentId)) {
throw new ResourceNotFoundException("Student id " + studentId + " not found");
}
if (!courseRepository.existsById(courseId)) {
throw new ResourceNotFoundException("Course id " + courseId + " not found");
}
CourseStudent courseStudent = new CourseStudent(
studentRepository.findById(studentId).get(),
courseRepository.findById(courseId).get()
);
return courseStudentRepository.save(courseStudent);
}
I have manually added Student and the Course into my local database and send this request by using Postman.
http://localhost:8080/insert/students/1/courses/1
However, I get the following error:
{
"timestamp": "2022-08-04T12:33:18.547+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/insert/students/1/courses/1"
}
In the console, I get NullPointerException. What is the thing I am doing wrong here?

Best way to handle negative scenario in spring boot

So I am working on a spring-boot application for a zoo. while adding animals I can assign some room and mark some rooms as favorites, My sample JSON looks like below
{
"id":null,
"title":null,
"located":null,
"type":null,
"preference":null,
"favoriteRooms":null,
"room":{
"id":null,
"title":"Green",
"size":25,
"createdOn":null,
"favorites":null,
"animals":null
}
}
now I want to make sure the room should be valid while adding animals, if there is no room available I want to throw an error. currently, I am using CascadingType.MERGE but it throws hibernate exception I want to do some valid addition for room and favorite room what is the best way to do this?
my entity class looks like below
#Entity
#Data
#NoArgsConstructor
#FieldDefaults(level = AccessLevel.PRIVATE)
public class Animal {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_animal_id")
#SequenceGenerator(name = "seq_animal_id")
Long id;
#NotEmpty(message = "title should be given")
String title;
#CreatedDate
#JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss")
LocalDateTime located;
String type;
Long preference;
#OneToMany(mappedBy = "animal")
Set <Favorite> favoriteRooms;
#ManyToOne(cascade = CascadeType.MERGE)
#JoinColumn(name = "room_id")
Room room;
}
I'm not sure if I really understand what you're asking for but you can handle this in your controller or better in ServiceImplementation which handles the business logic.
Using #Valid the constraints will be checked which you have defined in your models:
example:
#Autowired
RoomRepository roomRepository;
#PostMapping
public ResponseEntity<?> insertAnimal(#Valid #RequestBody Animal animal) {
Optional<Room> optionalRoom = roomRepository.findById(animal.getRoom.getRoomId());
if (optionalRoom.isPresent()) {
Room room = optionalRoom.get();
// your logic to check if the room is valid
}
else {
return new ResponseEntity<String>("Your message here", HttpStatus.NOT_FOUND);
}
}

Failed to find by foreign key

I want to display all the rooms belong to the hospital of the current user
Class Room :
#Entity(name = "rooms")
#Table( name = "rooms",
uniqueConstraints = {
#UniqueConstraint(columnNames = "roomNumber")
})
public class Room {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long idRoom;
private String roomNumber;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(nullable = false, name = "idhospital")
private Hospital hospital;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "room")
private Set<Bed> beds;
//getter and setter
Repository :
#Query("SELECT rm from rooms rm where rm.hospital=?1" )
List<Room> findRoomsByHospital(Hospital hospital);
Service :
List<Room> getRoomsByHospital(Hospital hospital);
ServiceImpl :
#Override
public List<Room> getRoomsByHospital(Hospital hospital) {
return roomRepository.findRoomsByHospital(hospital);
}
Controller :
#GetMapping("/display/room")
public ResponseEntity<List<Room>> getRoomsbyHospitalId(Authentication authentication) {
logger.info("Getting all RoomsByHospitalId from room table...Call getRoomsbyHospitalId ");
// to get the current user
Optional<User> user = userRepository.findByUsername(authentication.getName());
// to get the id of the hospital of the current user
Hospital hospital = user.get().getHospital();
logger.info("Data extracted from room table...");
//return ResponseEntity.ok(HospitalRooms);
return new ResponseEntity<>(roomService.getRoomsByHospital(hospital), HttpStatus.OK) ;
}
I had this error :
enter image description here
enter image description here
=========================================================================
help please!
I had the same problem. Solution in to add this notation #JsonIgnore here
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(nullable = false, name = "idhospital")
private Hospital hospital;
Just NullPointerException doesn't really show where It's comming from, to help you debbug in the application.properties write the following:
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=create
When running now It's going to show you how hibernate is handling your queries where are the null Objects or if it's getting the error before that. Here you are getting just an Id and It's being instance a HospitalModel
// to get the id of the hospital of the current user
Hospital hospital = user.get().getHospital();
Try just doing that instead:
Long hospital = user.get().getHospital();

fetch list based on id present in another entity

this is my order entity,
#Data
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
#Table(name = "ordertab")
public class Order {
#Id
private int orderId;
private String orderDate;
#ManyToMany(targetEntity = Medicine.class,cascade = CascadeType.ALL)
#JoinTable(name="ord_med",
joinColumns = {#JoinColumn(name="ord_id")},
inverseJoinColumns = {#JoinColumn(name="med_id")})
private List<Medicine> medicineList;
private String dispatchDate;
private float totalCost;
#ManyToOne(targetEntity = Customer.class,cascade = CascadeType.ALL)
#JoinColumn(name= "custord_fk",referencedColumnName = "customerId")
private Customer customer;
private String status;
}
and this is my medicine entity,
#Data
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class Medicine {
#Id
private String medicineId;
private String medicineName;
private float medicineCost;
private LocalDate mfd;
private LocalDate expiryDate;
**#ManyToMany(cascade = CascadeType.ALL, mappedBy = "medicineList")
private List<Order> orderList;** //order/ medicine many to many mapping
// OneToOne Mapping
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "categoryId", referencedColumnName = "categoryId")
private Category category;
in my order service interface i have a method,
List showAllOrder(string medId);
I have to fetch all orders that has the matching med id.
this many to many mapping have created a additional table ord_med with two columns named ord_id,med_id(type foreign keys).In addition to that due to this bidirectional mapping(i believe it is) while creating object of medicine entity its asking me to add orderlist ,how to approach this method or how exactly should i solve this. thankyou.
in your OrderRepository you can implements this method
findByMedicineId(String id);
if i go for findByMedicineId(String id);
it gives error saying no property medicineId is found in Order entity,cuz the property medicineId is in Medicine entity,while defining custom method in repository follows rules, refer https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
anyway I have found the solution for this,
public List<Order> getOrderListBasedOnMedicineId(String medicineid) {
Optional<Medicine> med=medicineRepo.findById(medicineid);//find if medicine is present in database with the id.
if(med.isEmpty()) {
return null;
}
List<Order> orders = medicineServ.getOrderList(); //getorderlist defined in service implementation of medicine.
List<Order> ordersWithMedId = new ArrayList();//new list to add all orders that has atleast one medicineId that matches.
for(int i=0;i<orders.size();i++) {
List<Medicine> medicines= orders.get(i).getMedicineList();
for(int j=0;j<medicines.size();j++) {
ordersWithMedId.add(orders.get(i));
}
}
return ordersWithMedId;//returning the list of orders.
}
#Override
public List<Order> getOrderList() {//medicine service implementation
return orderRepo.findAll();
}
//OrderController
#GetMapping("/orders/list/{id}")
public ResponseEntity<List<Order>> getOrderListBasedOnMedicineId(#PathVariable("id") String id) {
List<Order> ord= orderService.getOrderListBasedOnMedicineId(id);
if(ord==null) {
throw new OrderNotFoundException("Order not found with medicine id:"+id);
}
return new ResponseEntity<List<Order>>(orderService.getOrderListBasedOnMedicineId(id),HttpStatus.OK);
}

Joined column is null for OneToMany relationship

I am trying to create a Hotel Room relationship where in post call I am trying to pass rooms array . Hoping to save the room in controller .
The problem i am facing right now is when i call /newhotel, a hotel is saved and it's associated rooms get saved. But those rooms have their hotel_id NULL. Why the association was not created at the time of saving the room ?
public class Hotel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "hotel", cascade = CascadeType.ALL)
private List<Room> rooms;
}
public class Room {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "hotel_id")
private Hotel hotel;
#Enumerated(EnumType.STRING)
private RoomType roomType;
}
#RestController
public class HotelController {
#Autowired
HotelRepository hotelRepository;
#Autowired
RoomRepository roomRepository;
#CrossOrigin
#PostMapping(value = "/newhotel")
public Hotel addHotel(#RequestBody Hotel hotel) {
return hotelRepository.save(hotel);
}
}
Below is my post call request body :
{
"name" : "hotel1",
"rooms" : [
{
"roomType" : "SingleBed"
}
]
}

Resources