How to save an object in two different tables using Spring MVC and Hibernate - spring

Let's consider the following:
#Entity
#Table(name="person")
public class Person implements Serializable {
#Id
#GeneratedValue
#Column(name="id")
private int id;
#Column(name="firstName")
private String firstName;
#Column(name="lastName")
private String lastName;
...getters/setters...
}
and
#Entity
#Table(name="car")
public class Car implements Serializable {
#Id
#GeneratedValue
#Column(name="id")
private int id;
#Column(name="brand")
private String brand;
#Column(name="model")
private String model;
#Column(name="personId")
private String personId;
...getters/setters...
}
Let's imagine that a user is going to subscribe and enter his personal info, like first name, last name, the brand of his car, as well as the model of the car.
I do not want the personal info of the person to be stored in the same table than the car info.
I also would like to be able to retrieve the car information with the personId, this is why I have personId in the Car class.
Which annotations should I use to be able to accomplish this? Obviously I will need a constraint on the Car table and make personId a foreign key, right? What is the best way?
I have seen different things, what is the best?

In Car class, replace
#Column(name="personId")
private String personId;
with
#ManytoOne(fetch = FetchType.LAZY)
#CJoinColumn(name="person")
private Person person;
In Person class, add
#OneToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REMOVE})
private List<Car> cars;
You are now forming bi-directional one-to-many which means you can retrieve cars of person and person (ownder) of the car.
The cascade allows saving or updating of cars when person is saved. All cars are also deleted when person is removed.

It depends on your requirements.
If you want to use the same vehicle for multiple users, then you shall make it an entity, and use a many-to-many relationship.
If you don't want to change your entity structure at all, but just the database mapping then look at #SecondaryTable and #SecondaryTables annotations, they define more tables for an entity, and then you shall specify which table to use for each column (otherwise they are assigned to main table).

Related

#ManyToOne Referencing a composite key in JPA

I have following entities
#Entity
#IdClass(SubjectId.class)
class Subject {
#Id
String name;
#Id
String volume;
........
}
class SubjectId {
String name;
String volume;
//constructor, getters and setters
..........
}
#Entity
class Student {
#Id
String studentId;
String subject;
String subjectVolume;
}
I want to map the fields subject and subjectVolume of class Student to composite primary key of class Subject as a #ManyToOne relationship. But I don't know what should I pass inside #ManyToOne(?).
Any help would be appreciated, thanks.
Edit:
I want to use the columns subjectName and subjectVolume as entity fields in class Student as well. I don't want to do student.getSubject().getSubjectName() instead I want student.getSubjectName().
You could just declare the relation this way (instead of declaring the fk fields):
class Student{
#Id
String studentId;
#ManyToOne // this is sufficient create foreign-key columns in the Student-table
Subject subject;
}
The generated columns of the Student table will have these names by default:
In case you need different column names you should look for the #JoinColumn annotation.
edit: to be able to directly call student.getSubjectName() you could still decide to include single parts of the composite foreign key additionally as entity properties, in this case you need to make sure to declare the second (duplicate) column mapping with insertable=false and updatable=false, since its value is already managed by the #ManyToOne fk:
#Entity
static class Student {
#Id
String studentId;
#ManyToOne
Subject subject;
#Column(name = "subject_name", insertable = false, updatable = false)
String subjectName;
}
However, I'd probably prefer simply declaring a custom getSubjectName() getter which just returns subject.getName().

One To One Mapping Spring Data JPA

I've a question about One to One unidirectional Mapping in Spring Boot.
I've a Customer class with a One to One unidirectional mapping to an Address class.
But when I try to associate a new customer with an existing Address, the database is updated.
So two Customers are now associated with the one Address.
As I understand it only one Customer should be associated with one unique Address. Do I understand the concept correctly, or am I doing something wrong in Spring Boot/ Spring Data JPA/ Hibernate?
Customer
#Entity
public class Customer {
#Id
private Long cId;
private String cName;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="aid")
private Address cAddr;
:
}
Address
#Entity
public class Address {
#Id
private Long aid;
private String town;
private String county;
:
}
data.sql
insert into address values (100, "New York", "NY");
insert into customer values (1, "John Smith", 100);
Application.java
#Override
public void run(String... args) throws Exception {
Customer c1 = new Customer((long)5, "Mr. Men");
Optional<Address> a100 = ar.findById((long)100);
c1.setcAddr(a100.get());
cr.save(c1);
}
Database
There are 2 options on how to make #OneToOne relation: unidirectional and bidirectional: see hibernate doc.
When you scroll down a little bit you will find the following:
When using a bidirectional #OneToOne association, Hibernate enforces the unique constraint upon fetching the child-side. If there are more than one children associated with the same parent, Hibernate will throw a org.hibernate.exception.ConstraintViolationException
It means that you'll have the exception only on fetching and when you have a bidirectional association. Because Hibernate will make an additional query to find the dependent entities, will find 2 of them, which doesn't fit #OneToOne relation and will have to throw an exception.
One way to "fix" uniqueness for your entities, is to make cAddr unique:
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="aid", unique=true)
private Address cAddr;
If you create your db tables, by setting hbm2ddl property this will add a unique constraint to the aid column.
I really recommend to read the following:
#OneToOne javadoc itself provides examples of how to do everything correctly (for you Examples 1 and 2 are the most useful)
Check Vlad's blog about #OneToOne. It must be the best you can find. At least jump to the chapter "The most efficient mapping" and implement it bidirectional and sharing the PK, using #MapsId.
Also maybe you will come up to the idea to use #ManyToOne option (at least i can imagine that customer can have multiple addresses)
This is not One-to-Many relation. It's One-to-Many as One object has multiple related objects. Checkout this article.
Example:
Post.java
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table
public class Post {
#Id
#GeneratedValue
#Column(name = "post_id")
private Long id;
#Column
private String postHeader;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Comment> comments = new ArrayList<>();
public void addComment(Comment comment) {
comments.add(comment);
}
public void removeComment(Comment comment) {
comments.remove(comment);
}
// equals() and hashCode()
}
Comment:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table
public class Comment {
#Id
#GeneratedValue
#Column(name = "postcom_id")
private Long id;
#Column
private String text;
// equals() and hashCode()
}
Check out step "3. Uni-directional one-to-one mapping demonstration" at this site basically carbon copy of what you're trying to do.

Can i use exactly same entity for two tables JPA HIBERNATE

I have a rare scenario where i have to maintain two tables say
Student_failed and Student_passed. both have exactly same schema.
#Entity
#Table(name = "student_failed")
public class StudentFailed {
#Id
#Column(name="id")
private String id;
#Column(name="student_name")
private String studentName;
#Column(name="home_town")
private String homeTown;
...
}
and
#Entity
#Table(name = "student_passed")
public class StudentPassed {
#Id
#Column(name="id")
private String id;
#Column(name="student_name")
private String studentName;
#Column(name="home_town")
private String homeTown;
...
}
as both entities are same i want to use single entity for both table. I have two different controllers that do crud operations on either of the tables.
Can i use single entity and map it to both the tables? I came across #SecondaryTables annotation but i am not sure it if will work.
PS: i know its a bad approach to keep two different tables with same fields but due to some specific requirement i am not allowed to do that).

Two tables connected via Primary Key

I have read about the use of #MapsId and #PrimaryKeyJoinColumn annotations, which sounds like a great options. I have two tables (UserList and UserInformation) which have a child, parent relationship, respectively; both classes below are abbreviated to just include the relevant columns. UserInformation's primary key value is always null and does not take the value of its parent column.
User Class
#Entity
#Data
#Table(name = "user_list")
public class UserList {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// List of foreign keys connecting different entities
#OneToOne(mappedBy = "user")
#MapsId("id")
private UserInformation userInfo;
}
UserInformation Class
#Entity
#Data
#Table(name = "user_information")
public class UserInformation implements Serializable {
#Id
private Integer userId;
#OneToOne
private UserList user;
}
I would prefer to not use an intermediary class if possible. I'm not tied to MapsId or even this implementation if there is a better solution.
Thanks!
The question is not very clear to me, but I think you could improve the following in the modeling of the entity:
The #column annotation can only be omitted when the class parameter is called exactly the same as the database column, taking into account the table name nomenclature, could it be that the column is user_id ?, if so the id parameter should be :
#Id
#column(name="USER_ID")
private Integer userId;
In the user entity being id, it will match the DB ID field so the #column annotation is not necessary

Hibernate JPA scenario to solve?

There is a class Consultant. A consultant can have many kind of experience like salaried, self-employed, freelancer. For each type of experience there are different data to save in database.
Salaried:
Total Experience
Company Name
Experience Time in years
Offer/Reveling letter Link
Self Employed:
Company Name
Total Experience
CIN_Number
GST_Number
CompanyCertificateLinkUrl
FreeLancer:
Total Experience
A user can have experience in more than one occupation type like a consultant is both salaried and freelancer, or self employed plus salaried and freelancer. So i am confused how to make the #Entity class for this type of use case.
My Solution
#Entity
class Consultant{
#Id
int id;
#OneToOne
Salaried semp;
#OneToOne
SelfEmployed selfemp;
#OneToOne
Freelancer femp;
}
But i think this is not good practice as it will lead to many null field in the database.
ANY BETTER SOLUTION
I think your approach is fine. #OneToOne fields are optional by default, so can be null. That means there wouldn't be a row in the corresponding tables, so you'd only have up to two null values per row in the Consultant table.
If you're really concerned about nulls in the database, then you can map the relationships the other way, so:
#Entity
class Consultant{
#Id
int id;
#OneToOne(mappedBy = "consultant")
Salaried semp;
#OneToOne(mappedBy = "consultant")
SelfEmployed selfemp;
#OneToOne(mappedBy = "consultant")
Freelancer femp;
}
This way, if there's no row in the Salaried table that's related to the Consultant, the semp field will be null in the Consultant object.
you can do with two classes consultant and profession(id,name) and the relation OneToMany,ManyToOne
Consultant's entity
#Entity
class Consultant{
#Id
private int id;
#OneToMany(mappedBy = "consultant",cascade = CascadeType.All)
List<ConsultantProfession> cp;
}
Profession's entity
#Entity
class Profession{
#Id
private int id;
private String name;
#OneToMany(mappedBy = "profession", cascade = CascadeType.All)
private List<ConsultantProfession> cp;
}
ConsultantProfession's entity
#Entity
#Table(name="consultant_profession")
public class ConsultantProfession{
#Id
private int id;
// this is the link to the consultant class
#ManyToOne
#JoinColumn(name="consultant_id")
private Consultant consultant; // this name is specified in the class seeing patients as value of parameter `mappedBy`
// this is the link to the profession class
#ManyToOne
#JoinColumn(name="profession_id")
private Profession profession;
}

Resources