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).
Related
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.
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
I have the entity Project and entity Cluster.
A Project can have multiple Clusters.
I don't want a third table to save this relationship. Just the Project ID saved to the Cluster.
This is my project Entity:
#Entity
#Table(name = "Project")
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String projectName;
#OneToMany
#JoinTable(name = "cluster")
private Set<Cluster> clusters;
}
This is my Cluster entity
#Entity
#Table(name = "Cluster")
public class Cluster {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String team;
private String concept;
}
This gives me the error: must have same number of columns as the referenced primary key .
How could I fix this? I don't see how to fix this.
Use #JoinColumn instead of #JoinTable
public class Project {
//...
#OneToMany
#JoinColumn(name="PROJECT_ID", referencedColumnName="id")
private Set<Cluster> clusters;
and add PROJECT_ID column to Cluster entity.
public class Cluster {
//...
#Column(name = "PROJECT_ID")
private Integer projectId;
Correct me if I'm wrong but, as far as I know about Software Engineering, what you want CAN'T be done: you can't store a relationship nowhere but in a third table. Lists, sets, maps, and so on MUST be stored that way.
Otherwise, and in your case, you'd have Project's properties replicated for each cluster of the relationship, and that's not desirable.
I have two pojo classes with one-to-many relationship in hibernate
CustomerAccountEnduserOrderDetails.class
#Entity #Table(name="customer_account_enduser_order_details")
public class CustomerAccountEnduserOrderDetails implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id")
private Long id;
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "product_id", insertable = false, updatable = false)
private CustomerCmsProduct customerCmsProduct;
}
Second is CustomerCmsProduct.class
#Entity
#Table(name="customer_cms_product")
#JsonIgnoreProperties(ignoreUnknown = true)
public class CustomerCmsProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="id")
private Long id;
#Column(name="name")
private String name;
#Column(name="offer_price")
private String offerPrice;
#Column(name="original_price")
private String originalPrice;
#Column(name="discount")
private String discount;
}
Here if I fetch the object of CustomerAccountEnduserOrderDetails class,then i will get the CustomerCmsProduct class also , my problem is that here i want the specific column of CustomerCmsProduct table (not all by default i am getting all) like only id and originalPrice.
How i can do like that projection here?
In the service layer or at a webservice layer( if this is a web project) Create two different classes other than #Entity as DTO(Data Transfer Objects) which helps is data transfer from one layer to the other.
public class CustomerAccountEnduserOrderDetailsPojo {
private List<CustomerCmsProductPojo> productPojoList = new ArrayList<> ();
// getter and setter
}
public class CustomerCmsProductPojo {}
Follow the below steps
Retrieve the #Entity class data by executing the query from service layer.
Iterate over all the fields and copy only required fields to pojo layer
Expose the data to other layers using service method.
This way, we can avoid changing the custom hibernate behavior as it is linked with many parameters like cache, one to many queries that are fired per iteration.
And also, do any customization that you want in this layer. Hope this is multi layered project where you have different layers which servers different purpose.
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).