JPA - Hibernate - OneToOne - java

I have a OneToOne relation between tables User and Profile defined like so:
#Entity
#Builder
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name="users", uniqueConstraints = {
#UniqueConstraint(columnNames = "username"),
#UniqueConstraint(columnNames = "email")
}
)
public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idUser;
private String username;
private String email;
private String password;
#OneToOne(cascade=CascadeType.ALL, orphanRemoval=true)
#JoinColumn(name="id_profile", referencedColumnName="idProfile")
private Profile profile;
#ManyToMany(fetch = FetchType.EAGER)
private Set<Role> roles = new LinkedHashSet<Role>();
}
#Entity
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Table(name="profile",
uniqueConstraints = #UniqueConstraint(columnNames = "discordId")
)
public class Profile implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idProfile;
private Date birthday;
private String discordId;
private String description;
#ElementCollection(fetch=FetchType.EAGER)
private Set<String> spokenLanguages = new LinkedHashSet<String>();
#OneToMany(fetch=FetchType.EAGER)
private Set<ProfileGame> profileGames = new LinkedHashSet<>();
#OneToOne(mappedBy="profile")
private User user;
#ManyToOne
private TimeSlot timeSlot;
}
Then I run a test to make some fixtures in my database:
Profile profile = this.profileRepository.save(Profile.builder()
.birthday(new Date())
.discordId("discord-" + i)
.description("Description de user-" + i)
.spokenLanguages(spokenLanguages)
.timeSlot(timeSlot)
.build());
user.setProfile(profile);
System.err.println(user);
Everything works fine here, System.err.println(user) returns correct values.
The issue comes from the database, which doesn't "apply" the id_profile in my User table:
What did I miss?

Try to do something like this:
Profile profile = Profile.builder()
.birthday(new Date())
.discordId("discord-" + i)
.description("Description de user-" + i)
.spokenLanguages(spokenLanguages)
.timeSlot(timeSlot)
.user(user)
.build();
user.setProfile(profile);
profile = this.profileRepository.save(profile);
You use a bidirectional #OneToOne association, so you should make sure both sides are in-sync at all times.
Also, as you need to propagate persistent state to the user, you should add cascade = CascadeType.ALL to the Profile.user like below:
#Entity
public class Profile implements Serializable {
// ...
#OneToOne(mappedBy="profile", cascade=CascadeType.ALL)
private User user;
}

Class User:
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL,
fetch = FetchType.LAZY, optional = false)
private Profile profile;
Class Profile:
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="id_user")
private User user;

Related

Enum values are not saved in the database(PostgreSql)

The issue is that the enum values are not being saved in the database, so whenever I register new user it returns user with 0 role size even though I have all the right configurations, so came to the root cause which is enum values of ERole not being saved in the database and the Role table is empty.
ERole enum:
public enum ERole {
ROLE_USER,
ROLE_MODERATOR,
ROLE_ADMIN
}
Role entity:
#EqualsAndHashCode
#NoArgsConstructor
#Getter
#Setter
#Entity
#Table(name = "roles")
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Enumerated(EnumType.STRING)
#Column(length = 20)
private ERole role;
#ManyToMany(mappedBy="roles")
private List<User> users = new ArrayList<>();
public Role(ERole role) {
this.role = role;
}
}
User entity:
#Getter
#Setter
#NoArgsConstructor
#EqualsAndHashCode
#Entity
#Table(name = "users",
uniqueConstraints = {
#UniqueConstraint(columnNames = "name"),
})
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String pass;
#JsonIgnoreProperties("users")
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",
joinColumns = { #JoinColumn(name = "user_id") },
inverseJoinColumns = { #JoinColumn(name = "role_id") })
private List<Role> roles = new ArrayList<>();
public User(String name, String pass) {
this.name = name;
this.pass = pass;
}
}
As you see below in the diagram Role entity has the role column with ERole type
I have seen the oter similar threads where it is suggested to use the #Enumerated(EnumType.STRING) which I've been using in the first place.

Spring Boot API: Failing to save object to Database using Spring Boot

I have already built projects similar to this one, I even based this one in a different project I have. Thing is, I don't see why it keeps failing when I try to save an object to the Database. These are my Classes:
Car
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity(name = "cars")
public class Car {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column
#NotBlank
private String brand;
#Column
#NotBlank
private String model;
#Column
#NotBlank
private String color;
#Column
#NotBlank
private int kilometers;
#Column(name = "fabrication_date")
private LocalDate fabricationDate;
#Column
#PositiveOrZero
private float price;
#Override
public String toString() {
return "------------------------------------------------------------"+ "\n"
+brand + " - " + model;
}
#OneToMany(mappedBy = "car")
#JsonBackReference(value = "car-purchases")
private List<Purchase> purchases;
}
Purchase
#Data
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "purchases",
uniqueConstraints = { #UniqueConstraint(columnNames =
{ "user_id", "car_id" }) })
public class Purchase {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "creation_date")
private LocalDate creationDate;
#Column
private float price;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "car_id")
private Car car;
}
This is the Add to Database function:
#Override
public CarOutDTO addCar(CarInDTO carInDTO) {
Car car = new Car();
modelMapper.map(carInDTO, car);
car.setFabricationDate(LocalDate.now());
car.setKilometers(0);
Car newCar = carRepository.save(car); <--- It fails here
CarOutDTO carOutDTO = new CarOutDTO();
modelMapper.map(car, carOutDTO);
return carOutDTO;
}
And, this is the info that comes when I try sending a POST:
I can't find the error here, I assume it has something to do with this part, maybe the #JsonBackReference part?
#OneToMany(mappedBy = "car")
#JsonBackReference(value = "car-purchases")
private List<Purchase> purchases;

Hibernate joining tables with multiple primary keys

I have figured out how to join 2 tables with single primary key. But now I need to join 4 tables and some of these table with composite primary keys.
Here is my table picture
And I want to join them, so I generate classes for them:
// Record
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "record")
public class Record implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "student_id")
private Integer studentId;
#Id
#Column(name = "exam_id")
private Integer examId;
#Column(name = "total_score")
private Integer totalScore;
#Column(name = "student_score")
private Integer studentScore;
#Column(name = "submission_id")
private Integer submissionId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "student_id")
private Student student;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "submission_id")
private Submission submission;
}
// Submission
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "submission")
public class Submission implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "submission_id")
private Integer submissionId;
#Id
#Column(name = "question_id")
private Integer questionId;
#Column(name = "stu_answer")
private String stuAnswer;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "submission")
private Record record;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "submission")
private Set<Question> question;
}
// Question
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "question")
public class Question implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "question_id")
private Integer questionId;
#Column(name = "content")
private String content;
#Column(name = "score")
private Integer score;
#Column(name = "is_delete")
private Integer isDelete;
#Column(name = "option_id")
private Integer optionId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "submission_id")
private Submission submission;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "optional_id")
private Optional optional;
}
// Optional
#Getter
#Setter
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "optional")
public class Optional implements java.io.Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue
#Column(name = "option_id")
private Integer optionId;
#Column(name = "content")
private String content;
#Column(name = "is_delete")
private Integer isDelete;
#Column(name = "answer")
private String answer;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "optional")
private Question question;
}
// Final class to store information
public class RcdSubQuesOpt {
private Integer studentId;
private Integer examId;
private Integer questionId;
private String stuAnswer;
private String qContent;
private String oContent;
private String answer;
}
And this is code for JPA
#Override
public List<RcdSubQuesOpt> getRcdSubQuesOpt(int studentID, int examId) {
Session session = this.getSession();
List<RcdSubQuesOpt> results;
Transaction transaction = null;
try {
transaction = session.beginTransaction();
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<RcdSubQuesOpt> criteriaQuery = criteriaBuilder.createQuery(RcdSubQuesOpt.class);
// Try to join tables
Root<Record> pRoot = criteriaQuery.from(Record.class);
pRoot.join("submission", JoinType.INNER);
pRoot.join("question", JoinType.INNER);
pRoot.join("optional", JoinType.INNER);
criteriaQuery.multiselect(
pRoot.get(columns in RcdSubQuesOpt Class......));
// Try to add constraints
Predicate predicate = pRoot.get("examId").in(Arrays.asList(1));
criteriaQuery.where(predicate);
// try to do queries
results = session.createQuery(criteriaQuery).getResultList();
transaction.commit();
} catch (Exception e) {
results = null;
if (transaction != null) {
transaction.rollback();
}
} finally {
session.close();
}
return results;
}
But hibernate throw error as following:
Enitial SessionFactory creation failedA Foreign key refering com.domain.Submission from com.domain.Record has the wrong number of column. should be 2
Exception in thread "main" java.lang.ExceptionInInitializerError
I think it's the composite primary keys problem. But solution I searched is not suitable to solve it. Anyone give me some advice? Thanks!
To reference a composite primary key, you need to use #JoinColumns (plural).
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "sub_submission_id", referencedColumnName = "submission_id"),
#JoinColumn(name = "sub_question_id", referencedColumnName = "question_id")
})
private Submission submission;
However, I must admit, I don't understand your model - especially why Submission has a composite PK with question_id. It looks that one Submission has many Questions, why to include question_id as part of Submission PK?
Perhaps, I'm missing something, because the diagram is not fully visible.

Soft Delete : Child Entity not being deleted after Deleting parent Entity in #OneToMany relation in Spring Boot JPA Hibernate

I'm trying to implement soft delete between a parent and child entity in such a way that deleting the parent entity would delete all its child entities as well.
My parent entity is User with a #OneToMany relation with Profile. Profile is created with a reference to User. Deleting the User works fine but when trying to retrieve the Profile that was referencing the User, I get an exception.
{
"title": "Internal Server Error",
"status": 500,
"detail": "Unable to find com.user.domain.User with id 951; nested exception is javax.persistence.EntityNotFoundException: Unable to find com.user.domain.User with id 951",
"path": "/api/profiles",
"message": "error.http.500"
}
User.java
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#EqualsAndHashCode(exclude = {"users"})
#ToString(exclude = {"users"})
#Getter
#JsonDeserialize(builder = Profile.Builder.class)
#Entity
#Table(name = "users")
#SQLDelete(sql="Update users SET deleted = 'true' where id=?")
#Where(clause="deleted != 'true'")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "password")
private final String password;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#Column(name = "email")
private String email;
#Enumerated(EnumType.STRING)
#Column(name = "gender")
private Gender gender;
#OneToMany(mappedBy = "users", cascade = { CascadeType.PERSIST, CascadeType.MERGE }, orphanRemoval = true)
private Set<Profile> profiles = new HashSet<>();
#Column(name="deleted")
String deleteFlag;
}
Profile.java
#NoArgsConstructor(force = true, access = AccessLevel.PRIVATE)
#EqualsAndHashCode(exclude = {"users"})
#ToString(exclude = {"users"})
#Getter
#JsonDeserialize(builder = Profile.Builder.class)
#Entity
#Table(name = "profile")
#SQLDelete(sql="Update users SET deleted = 'true' where id=?")
#Where(clause="deleted != 'true'")
public class Profile implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private final Long id;
#Column(name = "profile_name")
private final String profileName;
#Column(name = "date_of_birth")
private final LocalDate dateOfBirth;
#Column(name = "health_history")
private final String healthHistory;
#ManyToOne
#JoinColumn(name = "users_id")
#JsonIgnoreProperties("reports")
private final User users;
#Column(name="deleted")
String deleteFlag;
}
There is a problem in delete query for profile
#SQLDelete(sql="Update users SET deleted = 'true' where id=?")
Here you're updating users table. I think it's a problem why your profiles are not removed after deleting User.

Hibernate - the custom query did not find the entity by the child parameter for ManyToOne unidirectional relation

I have a problem with retrieving an entity using the child's entity as a search parameter. Entities are related to many to one relationship as unidirectional and each object is fetched as FetchType.LAZY.
When I looking for an entity by a child entity, the result is null. But when I set to fetch as Eager it is correct.
My Entities:
#NoArgsConstructor
#Getter
#Entity
#Table(name = "partner")
public class PartnerEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String login;
public PartnerEntity(String login) {
this.login = login;
}
}
#NoArgsConstructor
#Getter
#Entity
#Table(name = "point")
public class PointEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "partner_Id")
private PartnerEntity partnerEntity;
public PointEntity(PartnerEntity partnerEntity) {
this.partnerEntity = partnerEntity;
}
}
#NoArgsConstructor
#Getter
#Entity
#Table(name = "orer")
public class OrdEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PAYMENT_POINT_ID")
private PointEntity pointEntity;
public OrdEntity(PointEntity pointEntity) {
this.pointEntity = pointEntity;
}
}
#NoArgsConstructor
#ToString
#Getter
#Entity
#Table(name = "BL")
public class BLEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARTNER_LOGIN", referencedColumnName = "login")
private PartnerEntity partnerEntity;
private String number;
public BLEntity(PartnerEntity partnerEntity, String number) {
this.partnerEntity = partnerEntity;
this.number = number;
}
}
And I looking for BLEntity using OrdEntity child:
final OrdEntity byId = ordRepo.findById(id);
final PartnerEntity partnerEntity = order.getPointEntity().getPartnerEntity();
final BLEntity blEntityResult= blRepo.findOneByNumberAndPartner(number, partnerEntity);
The object partnerEntity is not null, it is correct object.
I got blEntityResult as null but if I change in PointEntity fetch to FetchType.EAGER, blEntityResult is not null(correct).
My custom query in repository below:
public interface BLRepo extends JpaRepository<BLEntity, Long> {
#Query("select b from BLEntity b where b.number = :number and b.partnerEntity= :partner")
BLEntity findOneByNumberAndPartner(#Param("number") String number, #Param("partner") PartnerEntity partner);
}
why does happens, if the partner object being downloaded is not null and is correct?
I think you should add the mapping in both sides,
because of default fetch type for #AllToMany=Lazy and #ManyToAll = Eager.
just add below code inside PartnerEntity.
#OneToMany(mappedBy="partnerEntity" , fetch = FetchType.Eager )
List<BLEntity> blEntity = new ArrayList<>();
I change FetchType into Eager in PointEntity:
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "partner_Id")
private PartnerEntity partnerEntity;
And everything is ok, but I don't understand why it does not work with PaymentType.Lazy. When I am looking for:
final PartnerEntity partnerEntity = order.getPointEntity().getPartnerEntity();
I get correct entity "PartnerEntity" which has proper login's field (login'field has value "test").
When I turned logged level to 'TRACE' I saw, Hibernate not binding correct login's parameter, it set null instead "test") why? :)

Categories

Resources