One To One Mapping Spring Data JPA - spring-boot

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.

Related

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

How to load insert scripts for multiple tables which are linked with foreign key in spring boot

I am using spring boot. I am loading test data through yml by defining spring.datasource.data=classpath:/test-data/sql_file_EntityOne.sql, classpath:/test-data/sql_file_EntityTwo.sql,...
For every single entity it works seamlessly but problem comes when EntityOne and EntityTwo have foreign key relationship and the corresponding insert statements are written in 2 different SQL files as depicted above.
I am using in memory h2 dB for local.
sql_file_EntityOne.sql
(Id_One, data_1,data_2) values(101, 'dat', 5)
sql_file_EntityTwo.sql
(Id_two, Id_Onethis is fk, data_3,data_4)
values(1,101, 'dat2', null, 5)
EntityOne
#Id
IdOne
....
#OneToMany(Cascade.All, mappedBy="entityOneRef")
List entityTwoRef
EntityTwo
#Id
IdTwo
....
#ManyToOne(Cascade.All)
#JoinColumn("entityTwoRef")
EntityOne entityOneRef
Can you please mention the error you are getting here?
You can use the following hibernate annotations for bidirectional relationship:
#OneToMany(mappedBy = ) on parent enity
#ManyToOne #JoinColumn(name = , nullable = false) on child entity
for example, let's take an example of Cart and Item as two entities with a Cart related as one to many with item:
//Cart
#Entity
#Data
public class Cart {
#Id
private Integer cartId;
#Column
private String data;
#OneToMany(mappedBy = "cart")
private Set<Item> items;
}
//Item
#Entity
#Data
public class Item {
#Id
private Integer itemId;
#Column
private String data;
#ManyToOne
#JoinColumn(name = "cart_id", nullable = false)
private Cart cart;
}
#Data is just lombok annotation for getters and setters. Scripts as below:
//Script1
INSERT INTO CART(cart_id,data) VALUES (101,'data1')
//Script2
INSERT INTO ITEM(item_id,cart_id,data) VALUES (1,101,'data2')
Then load the scripts in spring-boot in order:
spring.datasource.data=classpath:sql_script1.sql,classpath:sql_script2.sql
Hope it helps :)

How to fix ' attempted to assign id from null one-to-one property '

I am working on a e-library app with spring boot which allows users to order books (these data is saved in OrderDetails table ). Whenever I am trying to save ordered book for a certain user, it gives me the following exception:
org.hibernate.id.IdentifierGenerationException: attempted to assign id
from null one-to-one property
[com.springboot.library.domains.OrderDetails.book]
Here is the OrderDetails class:
#Entity
public class OrderDetails{
#EmbeddedId
private OrderDetailsKey key;
#ManyToOne
#MapsId("bookId")
#JoinColumn(name = "book_id")
private Book book;
#ManyToOne
#MapsId("userId")
#JoinColumn(name = "app_user_id")
private AppUser appUser;
private Integer quantity;
public void setBook(Book book) {
this.book = book;
}
// other getters and setters
}
The Book Class
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", insertable = false, updatable = false)
private Long id;
private String isbn;
private String title;
//some other fields
#OneToMany(mappedBy = "book", cascade = CascadeType.ALL, orphanRemoval = true)
private Set <OrderDetails> orderDetails;
//getters and setters
**strong text**}
Based on some similar examples I saw, I need to initialize book property in OrderDetails class, but I'm not sure how to do that:
Something like this except that setOrderDetails method expects a Set<OrderDetails> and I'm passing a single one and that's where I'm stuck
public void setBook(Book book) {
this.book = book;
book.setOrderDetails(this);
}
I would really appreciate any help :)
Usually, that happens when trying to persist an #Entity that has no #Id annotated, which is the case. That error simply means that, when hibernate tries to persist, it is trying to come up with an PRIMARY KEY property, and it fails because that comes from the Book object, which is null.
Consider debugging your code to the point of persistence, and checking there if the entity to be persisted (OrderDetails) has a non-null primary key (Book).
On the second question:
Based on some similar examples I saw, I need to initialize book property in OrderDetails class, but I'm not sure how to do that:
What you wrote is correct. You simply need to give the OrderDetails object a Book object to work with. Since the OrderDetails object has a Book property (which is correct), you simply need to write a "setter", which is a method to set an inner property of the object, on the OrderDetails object, like you already did.
Also, since you have a bidirectional relationship between OrderDetails and Book, consider using the mappedBy annotation.

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

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).

Resources