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.
Related
My classes are like..
Employee Entity:
import javax.persistence.Entity;
import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;
import javax.persistence.PrePersist;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
#Entity
#Data
#DynamicUpdate
#EntityListeners(AuditingEntityListener.class)
#Table(name = "tbl_employee", indexes = {
#Index(name = "idx_employee_status", columnList = "status"),
#Index(name = "idx_employee_createdAt", columnList = "createdAt") })
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
private String name;
private String remarks;
private String status;
#PrePersist
public void setCreatedAt() {
this.createdAt = OffsetDateTime.now();
}
}
Repository:
public interface EmployeeRepository extends CrudRepository<Employee, Long>, PagingAndSortingRepository<Employee, Long>{
}
Service methods:
createEmployee(){
Employee employee = new Employee();
employee.setName("John");
employee.setRemarks("Very Good Performance");
employeeRepository.save(employee);
}
updateEmployee(){
Employee employee = employeeRepository.findById(1L); // Id is long
employee.setName("Thomas");
employee.setRemarks("Above average Performance");
employeeRepository.save(employee);
}
createEmployee() is working and data is getting saved in DB (MySQL), but updateEmployee() is not. No sql query is generated in the eclipse console.
AM I missing something in configuration?
Make sure that the service methods are annotated with #Transactional.
Otherwise the hibernate cannot track changes which are needed for #DynamicUpdate.
Alternatively you can also try to remove the annotation.
Hibernate/JPA only access the database when it has to.
In the create variant it needs to return an instance with a updated ID value. The id value gets generated by the database when the insert is performed.
Therefore the insert is performed immediately.
In the other case no information from the database is required.
Therefore Hibernate/JPA only marks the entity as dirty and moves on.
Only when the transaction ends the update is actually performed.
I have a Spring Boot User class which always comes up with the error "java.sql.SQLException: Field 'id' doesn't have a default value". I have tried many times to provide a default value, both in the Java class and in the database table, but to no avail. And I have also switched from generation type = auto and = identity, but to no avail. Thank you very much for your help. Here is my Java Class and my Database Table:
package com.ykirby.myfbapp;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.springframework.beans.factory.annotation.Value;
import java.sql.Timestamp;
import java.util.Date;
#Entity // This tells Hibernate to make a table out of this class
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
#Value("#{User.id ?: 0}")
private int id = 12345;
#Column(name = "fbuserid")
private String fbuserid;
#Column(name = "apttime")
#Temporal(TemporalType.TIMESTAMP)
private Date apttime;
#Column(name = "apttitle")
private String apttitle;
#Column(name = "aptaddress")
private String aptaddress;
#Column(name = "aptlonglat")
private String aptlonglat;
#Column(name = "aptdetails")
private String aptdetails;
public String getFbuserid() {
return fbuserid;
}
public void setFbuserid(String fbuserid) {
this.fbuserid = fbuserid;
}
public Date getApttime() {
return apttime;
}
public void setApttime(Date apttime) {
this.apttime = apttime;
}
public String getApttitle() {
return apttitle;
}
public void setApttitle(String apttitle) {
this.apttitle = apttitle;
}
public String getAptaddress() {
return aptaddress;
}
public void setAptaddress(String aptaddress) {
this.aptaddress = aptaddress;
}
public String getAptlonglat() {
return aptlonglat;
}
public void setAptlonglat(String aptlonglat) {
this.aptlonglat = aptlonglat;
}
public String getAptdetails() {
return aptdetails;
}
public void setAptdetails(String aptdetails) {
this.aptdetails = aptdetails;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
The changes in your User entity class may sometimes not reflect your DB schema accurately. You can try one of these below solutions:
1. Update your DB schema manually by adding AUTO_INCREMENT attribute
ALTER TABLE `user` CHANGE COLUMN `id` `id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT;
2. Drop the User table in your DB, and rerun the application
Make sure that spring.jpa.hibernate.ddl-auto is set to create or update. The default is none if you are NOT using an embedded/in-memory DBs like H2 database.
This configuration of Spring Data JPA will set Hibernate's hibernate.hbm2ddl.auto to the setting value. In our case, it is create or update.
You can read more about this in the below articles and docs.
Spring Boot reference - Database Initialization
What are the possible values of the Hibernate hbm2ddl.auto configuration and what do they do
In production, I suggest you not to use this option but instead use a database migration tool like Liquibase or Flyway and leave the spring.jpa.hibernate.ddl-auto configuration to be none.
More reads about this Hibernate: hbm2ddl.auto=update in production?
You should drop the existing database and re-generate it because sometimes changes done through the model don't reflect properly in the database. While re-generating the database you can scaffolding it with SchemaExport.
Does
#Column(name = "apttitle")
private String apttitle="default";
work?
I am trying to join two entities in Spring JPA so that I can access the data in react.
I have an Event and Course entity with corresponding tables in postgres.
In react I loop through all the events in the database and display them on a card for each event. The Event table contains the courseid where that event is being played at. But I want to show on the card the coursename rather than the courseid.
I dont currently have access to this so need to join the tables so I have access to it.
I have never used queries in Spring JPA and struggling to create one to make this join.
I want something like this SQL query,
select * from event join course on course.courseid=event.course_id where eventid=5
where the eventid will be passed from react to Spring so that during each loop, it will get the correct eventid and display the corresponding coursename for that event.
Implementation:
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToMany;
#Entity
public class Course {
#Id
#Column(name = "courseid")
private Long id;
#Column(name = "coursename")
private String courseName;
#OneToMany(mappedBy = "course")
private List<Event> events;
// ...
}
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
#Entity
public class Event {
#Id
#Column(name = "eventid")
private Long id;
#ManyToOne
#JoinColumn(name = "course_id")
private Course course;
// ...
}
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface EventRepository extends JpaRepository<Event, Long> {
}
Usage:
import java.util.Map;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class MyController {
#Autowired
private EventRepository eventRepository;
#GetMapping
public Map<String, ? extends Object> index(#RequestParam("id") final long id) {
// find by eventid
final Optional<Event> res = eventRepository.findById(id);
res.ifPresent(e -> {
// course name
System.out.println(e.getCourse().getCourseName());
});
return res.map(e -> Map.of("id", e.getId(), "course", e.getCourse().getCourseName()))
.orElse(Map.of());
}
}
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);
});
}
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
}
}