Hotel Search System - spring-boot

I need to build a hotel search spring boot application.
The issue that I was confused about is the hotel system objects must be build up in an object-oriented manner.
So I build up some objects, but the question is how will I search this in the JPA repository.
Mainly JPA works with one or two tables,
but in this case, I need to use all of the tables to return the result.
Do you recommend writing custom SQL for that?
And the second issue, how will I search for availability. I decided to keep the availability matrix per room because hotels can have several rooms.
Querying hotels, cities, etc. are easy but the date range in multiple rooms that a hotel keeps is the challenge?
How could I put this in my query in this case?
Thanks,
Regards.
City
package com.booking.hotel.entity;
import java.util.List;
import lombok.Data;
#Data
public class City {
private long id;
private String name;
List<Hotel> hotelList;
}
Hotel.java
package com.booking.hotel.entity;
import java.util.List;
import lombok.Data;
#Data
public class Hotel {
private String name;
private City city;
private int ranking;
private String location;
private int distanceToCenter;
private List<Room> roomList;
}
Room.java
package com.booking.hotel.entity;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import lombok.Data;
#Data
public class Room {
private long id;
private long hotelId;
private String type;
private BigDecimal pricePerNight;
private int roomSize;
private boolean wiFi;
private boolean airCondition;
HashMap<Date, Boolean> availabilityHashMap;
private Room() {
// initialize room available by default
}
}

Related

Spring Boot JPA returns correct count but no data

Evening,
I have a Spring application that is connected to a PostgresSQL db. I can connect to the database and see that the query is returning the correct number of elements for the array but nothing in them:
curl http://localhost:8080/books
[{},{},{}]%
My Book model looks like this:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.math.BigDecimal;
#Entity
public class Book {
#Id
#GeneratedValue
private Long id;
private String name;
private String author;
private BigDecimal price;
public Book() {}
public Book(String name, String author, BigDecimal price) {
this.name = name;
this.author = author;
this.price = price;
}
}
and the controller:
#RestController
public class BookController {
#Autowired
private BookRepository repository;
// Find
#GetMapping("/books")
List<Book> findAll() {
List<Book> books = repository.findAll();
System.out.println(books);
return repository.findAll();
}
}
I've looked at these questions here, here and here but those answers didn't fit with this.
What am I not doing to see data come back?
In order for your entity to be serialized by Spring the entity needs to have getters for its properties. You could use lombok to auto-generate getter/setters for you entity properties or just write them your own.

How do I update a product that is partially defined then later add additional product details to it

I have a product that is partially defined when it is first created. It is assigned a product code and a category to which it belongs. Later when the marketing group provide details, these details need to be updated by adding the details to the product. These are done by different groups. A batch job obtains the product partial product details from one database and the product details from a relational database and then updates the product with the product details in a MongoDb database. Here is the objects as they exist initially. The Product details get assigned a product code and category and written to the MongoDB database. The batch job runs nightly checking for product details for the Product and then should update the Product with the ProductDetails when they become available in the relational database. Here are the objects:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Document
public class Product {
#Id
private long productCode;
private String category;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ProductDetail {
private long productCode; // matches the product code in Product
private String description;
private int quantityOnHold;
private BigDecimal price;
private String warehouseLocationId;
private float discountFactor;
private String orderDescCode;
private String vendorId;
}
I am wondering what's the way to update the Product with the Product details. Do I create the equivalent Product document and add the Product details as a nested document? Is there a way to simply update the existing document by first modeling it as it is i.e. just the Product with its 2 fields and then add the ProductDetail? I'm relatively new to using Spring Data Mongo, so I don't know what the approach should be that makes the most sense, please help.
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Document(collection="product_collection")
public class Product {
#Id
private long productCode;
private String category;
private ProductDetail productDetail;
}
#Data
#NoArgsConstructor
#AllArgsConstructor
public class ProductDetail {
private String description;
private int quantityOnHold;
private BigDecimal price;
private String warehouseLocationId;
private float discountFactor;
private String orderDescCode;
private String vendorId;
}
You can use spring-data-mongodb to do that.
You can use the following methods to update documents.
save – Update the whole object, if “_id” is present, perform an update, else insert it. Notice that an "_id" field is generated by spring-data-mongo and mapped to the field annotated with #Id.
updateFirst – Updates the first document that matches the query.
updateMulti – Updates all documents that match the query.
Upserting – If no document that matches the query, a new document is created by combining the query and update object.
findAndModify – Same with updateMulti, but it has an extra option to return either the old or newly updated document.
The easiest way to find and update an document in my opinion is to use mongoRepository :
import com.globallogic.spring.mongodb.model.Book;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface ProductRepo
extends MongoRepository<Product, Long> {
}
And then inject your mongoRepository in a service class:
#Service
public interface ProductService {
ProductRepo productRepo;
//injecting productRepo into you service
public ProductService(ProductRepo productRepo) {
this.productRepo = productRepo;
}
public void updateproduct(Long productCode, ProductDetail productDetail ) {
Product pFromMongo = productRepo.findOne(productCode);
//set whatever you want on pFromMongo
pFromMongo.setDetail(productDetail);
....
//And then save the productCode. This will add or update product detail
productRepo.save(pFromMongo);
}
}
The resulting product in MongoDB will look like :
{
productCode : 1,
category: "pCateg",
productDetail : {
description : "description",
quantityOnHold : 11,
price : 12.33,
warehouseLocationId : "warehouseLocationId",
discountFactor : 1.0,
orderDescCode : "orderDescCode",
vendorId: "vendorId"
}
}
You can take a look at this presentation https://www.youtube.com/watch?v=ReqMU6bmPNM&ab_channel=JavaTechie

Problem Parsing request body of type json, containing a list of string to Flux of string in Spring reactive

I have a DTO as below:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import reactor.core.publisher.Flux;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class InternetPackageDto {
private String id;
private String name;
private String termsAndConditions;
private String price;
private Flux<String> packageAttributes;
private Flux<String> extras;
}
And a Database Object as below:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import reactor.core.publisher.Flux;
#Document("internet_packages")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class InternetPackage {
#Id
private String id;
private String name;
private String termsAndConditions;
private String price;
private Flux<StoreableAttribute> attributes;
private Flux<StoreableAttribute> extras;
}
The StorableAttribute Database Model like so:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document("package_attributes")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class StoreableAttribute {
#Id
private String id;
private String name;
private String description;
}
On the Data Object the fields: Flux<StoreableAttribute> attributes and Flux<StoreableAttribute> extras are stored in a separate collection alongside the Package Object. And is handled by the mapper as below:
public InternetPackage fromDto(InternetPackageDto dto) {
var internetPackage = new InternetPackage();
internetPackage.setName(dto.getName());
internetPackage.setPrice(dto.getPrice());
internetPackage.setId(dto.getId());
internetPackage.setExtras(this.resolePackageExtras(dto));
internetPackage.setAttributes(this.resolePackageAttributes(dto));
return internetPackage;
}
private Flux<StoreableAttribute> resolePackageExtras(InternetPackageDto dto) {
return this.storeableAttributeService.resolveAttributes(dto.getExtras());
}
for the extra and similarly for the attributes also.
And a simple controller method as below:
#PostMapping(produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE)
public Mono<InternetPackageDto> update(#RequestBody InternetPackageDto incomingPackageDto) {
return this.packageService
.updatePackage(this.dtoMapper.fromDto(incomingPackageDto))
.map(this.dtoMapper::toDto);
}
And when I make a post request I get an error stating
org.springframework.core.codec.CodecException: Type definition error: [simple type, class reactor.core.publisher.Flux]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `reactor.core.publisher.Flux` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (io.netty.buffer.ByteBufInputStream); line: 2, column: 13] (through reference chain: com.example.api.dto.InternetPackageDto["extras"])
Some more information:
I am using the class InternetPackageDto as a request object as well as a response object.
I am using Flux<String> and not List<String> since I wasn't sure if doing blocking resolution to list was a good idea.
The attributes are stored and managed separately.
And during the time of updating or inserting the package those; if a new extra or attribute is included the attributes collection in db will be updated with the insertion of new incoming extras and attributes.
It seems like I might have made a stupid mistake because I cannot find much information about this problem, or I am doing it completely wrong.
Any help would be greatly appreciated.
I think you should do smth like this
public Mono<InternetPackageDto> toDto(InternetPackage entity) {
var internetPackage = new InternetPackageDto();
internetPackage.setName(entity.getName());
internetPackage.setPrice(entity.getPrice());
internetPackage.setId(entity.getId());
return Mono.zip(Mono.just(internetPackage), entity.getExtras().collectList(), entity.getAttributes().collectList())
.flatMap(tu->{
var dto = tu.getT1();
dto.setExtras(tu.getT2()); //To make it work in my local i made entity.getAttributes() as Flux<String> so here you will probably need to use .stream().map(dbItem->dbItem.getPropertyName())
dto.setPackageAttributes(tu.getT2());
return Mono.just(dto);
});
}

Java Model Hibernate Mapping Issue

Note: I have Used Spring Data Jpa for persistence.
Problem:
I have two Models: User and Badge
I have a List of Badges owned By a User as data member in User class.
I also have User as data member in Badge class (i.e. The creator of the badge)
I want to make relationship between user and List of badges data member.
relationship is of type OneToMany (i.e. One User will going to have Many Badges) and vice versa also.
I want it to work in this way,
in code ,
When I save badge object with issuer (aka user) set to a particular user object , then need not to add it (the badge) into user's List of badges Owned by it.
I have tried to create the relationship but it returns an empty list of User Owned Badge in REST API response.
Badge Model
import javax.persistence.*;
#Entity
#Table(name = "badges")
public class Badge {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "badge_id")
private int mId;
#Column(name = "badge_name" , nullable = false , unique = true)
private String mName;
#Column(name = "badge_description")
private String mDescription;
#Lob
#Column(name = "badge_logo" , nullable = false)
private String mLogo;
#ManyToOne
#JoinColumn(name = "issuer_id")
private User mIssuer;
}
User Model
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
#Entity
#Table(name = "users")
public class User {
#Id#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private long mId;
#Column(name = "username" , nullable = false , unique = true)
private String mUserName;
#Column(name = "fullname",nullable = false)
private String mFullName;
#Column(name = "salt")
private String mSalt;
#OneToMany(mappedBy = "mIssuer",cascade = CascadeType.ALL)
private List<Badge> mOwnedBadges;
#OneToMany
#JoinColumn(name = "received_badges_id")
private List<Badge> mReceivedBadges;
}
CommandLineRunner
import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import com.badging.spinnerbadger.SpinnerBadger.Models.User;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.BadgeSerivce;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.Arrays;
#Component
public class StartupExecutor implements CommandLineRunner {
#Autowired
private BadgeSerivce mBadgeSerivce;
#Autowired
private UserService mUserService;
#Override
public void run(String... args) throws Exception {
//TODO:: issuer cannot issue badge to itself
final User user1 = new User();
user1.setFullName("User1 FullName");
user1.setSalt("salt1");
user1.setUserName("User1 UserName");
mUserService.save(user1);
final User user2 = new User();
user2.setFullName("User2 FullName");
user2.setSalt("salt2");
user2.setUserName("User2 UserName");
mUserService.save(user2);
Badge badge1 = new Badge();
badge1.setDescription("Desc1");
badge1.setLogo("Logo1");
badge1.setName("Badge1");
badge1.setIssuer(user1);
mBadgeSerivce.save(badge1);
Badge badge2 = new Badge();
badge2.setDescription("Desc2");
badge2.setLogo("Logo2");
badge2.setName("Badge2");
badge2.setIssuer(user2);
mBadgeSerivce.save(badge2);
Badge badge3 = new Badge();
badge3.setDescription("Desc3");
badge3.setLogo("Logo3");
badge3.setName("Badge3");
badge3.setIssuer(user1);
mBadgeSerivce.save(badge3);
user1.setReceivedBadges(Arrays.asList(badge2));
user2.setReceivedBadges(Arrays.asList(badge1,badge3));
}
}
Note: It doesn't save user Received Badges list also , if you can figure that out too , then I will really be thankful to you.
BadgeRepo
import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface BadgeRepo extends PagingAndSortingRepository<Badge,Long> {
}
UserRepo
import com.badging.spinnerbadger.SpinnerBadger.Models.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface UserRepo extends JpaRepository<User,Long> {
}
BadgeServiceImpl
package com.badging.spinnerbadger.SpinnerBadger.Services.Implentations;
import com.badging.spinnerbadger.SpinnerBadger.Repository.BadgeRepo;
import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.BadgeSerivce;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
#Service
public class BadgeServiceImpl implements BadgeSerivce {
#Autowired
private BadgeRepo mBadgeRepo;
#Override
public List<Badge> getAllBadges(int pageNumber , int sizeOfPage) {
if (sizeOfPage > 20) {
sizeOfPage = 20;
}
final Page<Badge> allPages = mBadgeRepo.findAll(PageRequest.of(pageNumber,
sizeOfPage));
if (allPages.getTotalElements() > 0) {
return allPages.toList();
} else{
return new ArrayList<Badge>();
}
}
#Override
public void save(Badge badge) {
mBadgeRepo.save(badge);
}
}
UserServiceImpl
import com.badging.spinnerbadger.SpinnerBadger.Models.Badge;
import com.badging.spinnerbadger.SpinnerBadger.Models.User;
import com.badging.spinnerbadger.SpinnerBadger.Repository.UserRepo;
import com.badging.spinnerbadger.SpinnerBadger.Services.Intefaces.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
#Service
public class UserServiceImpl implements UserService {
#Autowired
private UserRepo mUserRepo;
#Override
public void save(User user) {
mUserRepo.save(user);
}
#Override
public List<Badge> getUsersReceivedBadgeList(long userId) {
final Optional<User> byId = mUserRepo.findById(userId);
return byId.orElse(new User()).getReceivedBadges();
}
#Override
public List<Badge> getUserOwnedBadgeList(long userId) {
final Optional<User> byId = mUserRepo.findById(userId);
return byId.orElse(new User()).getReceivedBadges();
}
}
Generated SQL by Hibernate -> 1st for User model and 2nd for Badge Model
Hibernate: insert into users (fullname, salt, username, user_id) values (?, ?, ?, ?)
Hibernate: insert into badges (badge_description, issuer_id, badge_logo, badge_name, badge_id) values (?, ?, ?, ?, ?)
I see a couple of things that might go wrong here.
You don't seem to have transactions specified. Add #Transactional to those beans or methods that should participate in a transaction. At the very least that should include everything that modifies (eventually) the database, i.e. any statement modifying a managed entity, including the one that loads it from the database and save statements. I'm expecting this to be the actual cause of the problem you are seeing.
You don't seem to have code in place that synchronises the both sides of a bidirectional relationship. So, when you call
badge1.setIssuer(user1),
user1 does not get updated, so if you call user1.getOwnedBadges()
it will still return the unchanged (empty) value.
I doubt it is a problem in this case, but it will result in the
relationship looking different within a single transaction,
depending from which side you are looking at it. And changes to the
non-owning side (User in your case) will not get persisted. So this should be fixed
anyways. See also https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/
When saving an entity, you should use the instance returned by the save method, not the one passed to save as an argument. Often they are the same but when they aren't modifying the one passed to save might not result in the state persisted to the database.
If these things are fixed and problems persist I recommend the following to gather more information about what is going on:
Activate logging of SQL statements including parameters in order to see what is actually persisted (and when).
Create a JUnit test testing your services. This makes it much clearer what is actually executed and allows to create variants to compare.

Nested property not found in a Spring Data query method declaration

I'm trying to use findBy... in my repository to get a Savingaccount object passing a nested attribute(name) as a parameter. Currently I'm using:
Mono<SavingAccount> findByOwnerName(String name);
but I'm getting this error: No property name found for type Owner! Traversed path: SavingAccount.owner.
My repository:
package com...SavingAccMS.Repository;
import com.everis.SavingAccMS.Model.Owner;
import com.everis.SavingAccMS.Model.SavingAccount;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import reactor.core.publisher.Mono;
public interface SavingAccountRepo extends ReactiveMongoRepository<SavingAccount, String>
{
Mono<SavingAccount> findByNumber(String number);
//This one is the problem
Mono<SavingAccount> findByOwnerName(String name);
Mono<SavingAccount> findByOwner(Owner owner);
}
My Entity:
package com...SavingAccMS.Model;
import java.security.acl.Owner;
import javax.validation.constraints.NotBlank;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
#Data
#Document(collection = "SavingAccs")
public class SavingAccount
{
#Id
private String id;
#NotBlank
private String number;
#NotBlank
private Owner owner;
#NotBlank
private String currency;
#NotBlank
private double balance = 0.00;
#NotBlank
private String status;
}
package com...SavingAccMS.Model;
import org.springframework.data.mongodb.core.mapping.Document;
import lombok.Data;
#Data
#Document(collection = "Owners")
public class Owner
{
public String dni;
public String name; //findBy this attribute is required.
}
According to your imports, the Owner in your SavingAccount refers to java.security.acl.Owner, not the one you defined yourself in om...SavingAccMS.Model.Owner. The former does not carry a name attribute.
I'm not certain but I don't think you can find the owner name in hibernate like that.
I would try the following!
#Query("SELECT * FROM SavingAccount where owner.name = :name")
Mono<SavingAccount> findByOwnerName(#Param("name") String name);

Resources