I am trying to learn Hibernate and I could create some simple CRUD operation using a Single Class and Single Table. I am just reading the Hibernate Doc and some online tutorial.
But I have a problem on how to define this relationship with two tables involved. I basically have an Employee table with this structure.
CREATE TABLE EMPLOYEE
(
EMP_ID VARCHAR(10) NOT NULL,
EMP_FIRST_NAME VARCHAR(30) NOT NULL,
EMP_LAST_NAME VARCHAR(30) NOT NULL,
STATUS_ID INT NOT NULL,
PRIMARY KEY (EMP_ID)
);
The STATUS_ID field references another table. STATUS_DESC can either be 'PERMANENT', 'CONTRACTUAL', 'ON-DEMAND'
CREATE TABLE EMP_STATUS
(
STATUS_ID VARCHAR(10) NOT NULL,
STATUS_DESC VARCHAR(100) ,
PRIMARY KEY (STATUS_ID)
);
I am thinking of having an Entity class like this. Now my goal is to return list of Employee object with status, but I don't know how to go about on doing this.
#Entity
public class Employee{
//other private instance
private EmployeeStatus empStatus;
//getters and setters.
}
public class EmployeeStatus{
private int statusID;
private String statusDesc;
//getters and setters
}
You want to know how to map it? ManyToOne?
Employee.java
#Entity
public class Employee{
//other private instance
#JoinColumn(name = "empStatus", referencedColumnName = "yourColName")
#ManyToOne(optional = false)
private EmployeeStatus empStatus;
//getters and setters.
}
Dont forget to change "referencedColumnName" value...
EmployeeStatus.java
#Entity
public class EmployeeStatus{
#Id //this is your pk?
private int statusID;
private String statusDesc;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "empStatus", fetch = FetchType.LAZY) //or EAGER
private List<Employee> empList;
//getters and setters
}
To create a relationship between two tables you need to decide:
Is the relationship bi-directional? That is, do the statuses know the employees or not? If no then it is uni-directional. In that case you can add the annotation on the Employee class like this:
#ManyToOne
#JoinColumn(name = "status")
private EmployeeStatus empStatus;
And there is a few other options that you may add.
You can do what you are doing, but I would suggest, if the status can only be one of three values, create an Enum with the three values. No need for a separate table.
The downside for this is you need to create a hibernate custom type (the code is on the wiki) to support persisting enums.
A simpler answer is to not use a secondary table, and just save the status as a String on the domain object. You can put business logic on your model to ensure the String is in the list of acceptable values.
If you really want to use a relationship between two entities, then check out the hibernate docs on many-to-one relationships.
You can use HQL to query the entities. Like so
Query q = s.createQuery("from Employee as e where e.empStatus = :status");
q.setParameter("status", status);
List emps= q.list();
Related
According to the Spring JPA documentation, in the Many-To-Many relationship (student - course) we must create a new table (student_course)
class student ---> class student_course <--- class course
According to the documentation, if we want to add a new property to the table (student_course) we must create a new class that will contain the compound keys of the student class and the course class
#Embeddable
class CourseStudentKey implements Serializable {
#Column(name="student_id")
Long studentId;
#Column(name = "course_id")
Long courseId;
}
_ Then to the Student_Course class we assign the id of type CourseStudentKey that contains the compound keys:
#Entity
class StudentCourse {
#EmbeddedId
CourseRatingKey id;
#ManyToOne
#MapsId("studentId")
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#MapsId("courseId")
#JoinColumn(name = "course_id")
Course course;
}
My question is: What is the difference in creating only the StudentCourse class and doing the #ManyToOne mapping to the Student class and the Course class??... in this way we can also add attributes to the StudentCourse class
_Clase Student
#Entity
class Student {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private idStudent;
#JsonIgnore
#OneToMany(mappedBy = "student")
List<StudentCourse> studentCourses = new ArrayList<>();
_Clase Course
#Entity
class Course{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private idCourse;
#JsonIgnore
#OneToMany(mappedBy = "course")
List<StudentCourse> studentCourses = new ArrayList<>();
}
_Clase StudentCourse
#Entity
class StudentCourse {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private idStudentCourse;
#ManyToOne
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#JoinColumn(name = "course_id")
Course course;
}
The only difference in the examples posted by you, is, in case of Embeddable, the student_id course_id would be a composite key, so there would only be one row allowed per student_id course_id combination. Whereas, in the second example, you have used generated primary key, ensuring multiple rows for each student_id course_id combination. This would be particularly useful if the student fails the course for the first time and attempts it again. You can then add parameters like attemped_on, is_completed, etc. to the student_course entity
Your examples show differences in the key, and as Chetan's answer states, this affects the key used in the table. The choices here isn't necessarily in using a separate class/embbeded class, but in using a single generated Identifier vs using a composite primary key for the entity.
In the embedded example you've posted, you have a composite primary key based on foreign key mappings. There are many other ways to map this same setup though, but the common parts will be:
composite PKs need an ID class. It doesn't have to be embedded in your class (see JPA derived IDs) but does need to exist. This is part of the JPA spec and allows em.find operations to deal with a single object.
ID values are immutable. They cannot change without remove/persist operations as per the JPA specification. Many providers don't like you even attempting to modify them in an Entity instance. In your embeddable example, you cannot change the references, while in the generated id example, you can.
It also affects what JPA requires you to use in foreign keys. If you use a composite ID, any references to that entity (*ToOne) that require foreign keys to that table are required to use its defined IDs - all columns that make up that ID. Some providers don't enforce this, but it will affect entity caching; since entities are cached on their IDs, using something else as the target of FKs might mean database hits for entities already in the cache.
I have two tables Quiz and Question which I want to associate with a #OneToMany relationship but the join table is not being created in mysql workbench database. Here are the entities :
Quiz.java
#Entity
public class Quiz {
// attributes :
private Integer idQuiz;
private String quizTopic;
#OneToMany
#JoinColumn(name = "quiz_Id")
private List<Question> questions;
// constructors
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "quiz_Id")
public Integer getIdQuiz() {
return idQuiz;
}
#Column(name = "quiz_topic")
public String getQuizTopic() {
return quizTopic;
}
//setters
}
Question.java
#Entity
public class Question {
// attributes
private Integer idQuestion;
private String value;
private String op1;
private String op2;
private String op3;
private String correctAnswer;
// constructors
// Getters :
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "question_Id")
public Integer getIdQuestion() {
return idQuestion;
}
#Column(name = "question_value")
public String getValue() {
return value;
}
#Column(name = "question_op1")
public String getOp1() {
return op1;
}
#Column(name = "question_op2")
public String getOp2() {
return op2;
}
#Column(name = "question_op3")
public String getOp3() {
return op3;
}
#Column(name = "question_op4")
public String getCorrectAnswer() {
return correctAnswer;
}
//setters
}
no foreign keys are created
here is what I get after running
Hibernate: create table question (question_id integer not null auto_increment, question_op4 varchar(255), question_op1 varchar(255), question_op2 varchar(255), question_op3 varchar(255), question_value varchar(255), primary key (question_id)) engine=InnoDB
Hibernate: create table quiz (quiz_id integer not null auto_increment, quiz_topic varchar(255), primary key (quiz_id)) engine=InnoDB
application.properties:
spring.main.web-application-type=none
spring.application.ui.title=Quiz
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/quiz?createDatabaseIfNotExist=true&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=justpass
spring.jpa.show-sql=true
The annotation
#OneToMany
private ArrayList<Question> questions;
will not create a join table. It should create a foreign-key in the Question table to the Quiz table. This foreign key is not the same as the
private Integer idQuestion;
column.
For your solution it dependes wether you want to build a uni- or bidirectional #OneToMany relationship. If you want a unidirectional one-to-many relationship you have to define a #JoinColumn to tell Hibernate that it shall create a foreign key with the given name in the related table. Most of the time a unidirectional one to many relationship is the easiest and sufficient way to go.
Unidirectional
#OneToMany
#JoinColumn(name = "quiz_id")
private List<Question> questions;
By this Hibernate will create the foreign key with name "quiz_id" in the question table.
In the bidirectional case we are able to access the quiz from the question vice versa the questions from the quiz. In this case you will you will have to define the variable which shall represent the parent class in the child class. For example if the quiz shall be the parent class, you will define the annotation. #OneToMany(mappedBy = "quiz"). Additionally to this you will have to define the question to be the #ManyToOne side as well. All in all:
Bidirectional
Quiz.java:
#OneToMany(mappedBy = "quiz")
private List<Question> questions;
Question.java
#ManyToOne(fetch = FetchType.LAZY)
private Quiz quiz;
You will want to define the fetch type to be lazy due to performance reasons (I even ran into some overflow errors in the past, without the lazy method).
Keep in mind that if you are using the bidirectional mapping together with serialization libraries such as jackson, you will run into the JSON infinite recursion problem when de-/serializing from/to JSON. In this case you will want to use #JsonIdentityInfo to serialize the id only instead of the complete entity (which will lead to infinite recursion) or #JsonIgnore respectively #JsonManagedReference and #JsonBackReference to not serialize the child dependencies of an entity.
I also recommend to use the properties cascade defining the cascade type and orphanRemoval for the deletion of entities when you work with relationships, but I did not want to blow up my answer with unrelated information.
It looks suspicious that you try to mix up access strategies in the Quiz entity.
As it is stated in the documentation:
As a JPA provider, Hibernate can introspect both the entity attributes (instance fields) or the accessors (instance properties). By default, the placement of the #Id annotation gives the default access strategy. When placed on a field, Hibernate will assume field-based access. When placed on the identifier getter, Hibernate will use property-based access.
So, instead of this:
#OneToMany
#JoinColumn(name = "quiz_Id")
private List<Question> questions;
put the #OneToMany and #JoinColumn(name = "quiz_Id") annotations on the appropriate getter:
#OneToMany
#JoinColumn(name = "quiz_Id")
public List<Question> getQuestions() {
return questions;
}
then try to recreate your schema by setting the spring.jpa.hibernate.ddl-auto property like this:
spring.jpa.hibernate.ddl-auto=create
Suppose I have two database tables, Product and ProductDetails.
create table Product
{
product_id int not null,
product_name varchar(100) not null,
PRIMARY KEY (product_id)
}
create table ProductDetails
{
detail_id int not null,
product_id int not null,
description varchar(100) not null,
PRIMARY KEY (detail_id,product_id),
FOREIGN KEY (product_id) REFERENCES Product(product_id)
}
Each product can have multiple product detail entries, but each product detail can only belong to one product. In SQL, I want to be able to retrieve each product detail but with the product name as well, and I would do that with a join statement.
select p.product_id,pd.detail_id,p.product_name,pd.description
from Product p join ProductDetails pd on p.product_id=pd.product_id
Now I need to have that concept in Spring data JPA form. My current understanding is the following:
#Table(name = "Product")
public class ProductClass
{
private int productID;
private String productName;
}
#Table(name = "ProductDetails")
public class ProductDetailsClass
{
private int detailID;
private int productID;
// this is the part I don't know how to set. #OneToMany? #ManyToOne? #JoinTable? #JoinColumn?
private String productName;
private String description;
}
(I didn't include any attributes such as #Id to keep the code minimal)
What do I need to write to get this private String productName; working?
My research on the #JoinTable and #OneToMany and other attributes just confuses me more.
P.S. This is a legacy Java program I inherited. The private String productName; part wasn't in the original code, but now I need the ProductDetails class to have the productName available.
P.P.S. I want to have a clear understanding of what I'm doing before trying anything and deploying. This is a legacy program deployed to production, and from what I understand, any code changes here can change the database structure as well, and no amount of money is enough to make me want to restore the Java program, the Spring Framework, the Apache server and MySQL database to a working order if anything catastrophic happens. Also I don't really have a development environment to test this. Help...
You research already goes in the right direction: You would need a #OneToMany relationship. The best descriptions for Hibernate has Vlad Mihalcea. On his webpage you could also find a good explanation of those relationships: The best way to map a #OneToMany relationship with JPA and Hibernate.
Firstly, you would have to create the entities correctly (an entity is represented by a table in a relational database).
Unidirectional (#OneToMany)
#Entity
#Table(name = "product")
public class Product
{
#Id
#GeneratedValue
private Long productID;
private String productName;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProductDetail> productDetails;
//Constructors, getters and setters...
}
#Entity
#Table(name = "product_details")
public class ProductDetail
{
#Id
#GeneratedValue
private Long detailID;
private String description;
//Constructors, getters and setters...
}
This is based on a unidirectional relationship. Therefore, each Product knows all the allocated ProductDetails. But the ProductDetails do not have a link to its Products. However, this unidirectional implementation is not recommended. It results in an increase of the size of the database, even its optimisation with #JoinColumn is not ideal because of more SQL calls.
Unidirectional (#ManyToOne)
#Entity
#Table(name = "product")
public class Product
{
#Id
#GeneratedValue
private Long productID;
private String productName;
//Constructors, getters and setters...
}
#Entity
#Table(name = "product_details")
public class ProductDetail
{
#Id
#GeneratedValue
private Long detailID;
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = product_id)
private Product product;
//Constructors, getters and setters...
}
In this unidirectional relationship only the ProductDetails know which Product is assigned to them. Consider this for a huge number of ProductDetail objects for each Product.
The #JoinColumn annotation specifies the name of the column of the table product_details in which the foreign key to the Product (its id) is saved. It also works without but it is more efficient with this annotation.
Bidirectional (#OneToMany and #ManyToOne)
#Entity
#Table(name = "product")
public class Product
{
#Id
#GeneratedValue
private Long productID;
private String productName;
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProductDetail> productDetails;
//Constructors, add, remove method, getters and setters...
}
#Entity
#Table(name = "product_details")
public class ProductDetail
{
#Id
#GeneratedValue
private Long detailID;
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = product_id)
private Product product;
//Constructors, getters and setters...
}
With a bidirectional relationship objects of both sides (Product and ProductDetail) know which other objects got assigned to them.
But according to Vlad Mihalcea, this should not be used if too many ProductDetails exist per Product.
Also remember to implement proper add and remove methods for list entries (see article again, otherwise weird exceptions).
Miscellaneous
With the cascading, changes in a Product also get applied to its ProductDetails. OrphanRemoval avoids having ProductDetails without a Product.
Product product = new Product("Interesting Product");
product.getProductDetails().add(
new ProductDetails("Funny description")
);
product.getProductDetails().add(
new ProductDetails("Different description")
);
entityManager.persist(product);
Often the question about the correct equals and hashCode methods is a complex puzzle in your head. Especially for bidirectional relationships but also in other situations relying on a database connection it is recommendable to implement them quite simply as described by Vlad.
It is good practice to use objects for primitive data types as well. This gives you the option to retrieve a proper null when calling the getter.
Avoiding eager fetching should be quite clear...
When you now try to retrieve a Product out of the database, the object automatically has a list of all the ProductDetails assigned to it. To achieve this, JPA repositories in Spring could be used. Simple methods do not have to be implemented. When you have the need to customise the functionality more, have a look at this article by Baeldung.
Now I have two tables, the first table called StudentBase and has three columns: id, firstname and lastname. The second table called ResearchAssistant and has two columns: id and course. I designed the tables like this because there are different kinds of students and research assistant is one of them. The two table could be joint together with the primary key id.
I'm writing an endpoint /researchAssistant and take following content as request body of POST method.
{
"firstname":"Jack",
"lastname":"Peter",
"course":"MATH"
}
What I want is that saving firstname and lastname into StudentBase table and save course into ResearchAssistant table. And generate a same id for both.
The first idea comes to my mind is building 3 model classes: StudentBase(id, firstname, lastname), ResearchAssistant(id, course) and ResearchAssistantMixed(firstname, lastname, course). I use ResearchAssistantMixed class as the request body class. After getting the data I will seperate it into a new StudentBase object and a ResearchAssistant object, then I store them seperately.
This process seems really stupid and the performance should be quite low. Do you have some better ideas? How does Spring Boot deal with such cases? Thank you!
This is a database problem and not a spring-boot problem. This is how I would approach (I'm assuming you're using some relational DB like MySql and hibernate for ORM):
Database Tables:
student_base
- id (primary key)
- first_name
_ last_name
research_assistant
- id (primary key)
- student_base_id (foreign key referencing id of student_base)
- course
You can now have equivalent entity classes in Java (for hibernate):
#Entity
#Table(name = "student_base")
public class StudentBase {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
// getters and setters
}
#Entity
#Table(name = "research_assistant")
public class ResearchAssistant {
#Id
#Column(name = "id")
private Integer id;
#ManyToOne(optional = false)
#JoinColumn(name = "student_base_id")
private StudentBase studentBase;
#Column(name = "course")
private String course;
// getters and setters
}
Now in your DAOs, you don't need to do much, just persist a student_base record and use the returned object to persist a research_assistant record. For example:
StudentBase studentBase = persist(new StudentBase(1, "abc", "xyz");
persist(new ResearchAssistant(1, studentBase, "pqr");
You can (and should) have two separate classes to accept the request object of the post API (don't use entity classes to accept request data).
I need to map a OneToMany relationship in hibernate, with the JPA annotations, in which is involved a weak entity.
For example
Table orders:
CREATE TABLE orders(
idorder serial NOT NULL,
note varchar(30),
CONSTRAINT orders_pkey PRIMARY KEY (idorder)
)
Table OrderItems:
CREATE TABLE orderitems(
idorder integer NOT NULL,
iditem serial NOT NULL,
qnt integer,
CONSTRAINT orderitems_pk PRIMARY KEY (idorder, iditem),
CONSTRAINT fk_orderitems FOREIGN KEY (idorder)
REFERENCES orders (idorder) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Within my class "Orders" I have realized the method getOrderItem() in this way:
// i need cascadeType.All here
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "order")
public Set<OrderItem> getOrderItems() {
return items;
}
now,
not being able to know the identifier that will be assigned to a new order
prior to insertion, such as Annotations can I use within the class OrderItem in order to submit automatically (in cascade mode) the correct idOrder?
my goal is to get a situation like this.
Order myOrder = new Order();
// myOrder.setId(1) not necessary
myOrder.setNote("orderNote");
OrderItem firstItem = new OrderItem();
// firstItem.setIdOrder() no need to specify idorder
// firstItem.setId(12);
firstItem.setName("firstItem");
firstItem.setQnt(2);
OrderItem secondItem = new OrderItem();
// secondItem.setId(13);
secondItem.setName("secondItem");
secondItem.setQnt(4);
Set<OrderItem> items = new HashSet<OrderItem>();
items.add(firstItem);
items.add(secondItem);
myOrder.setItems(items);
OrderDAO dao = new OrderDAO();
dao.save(myOrder); // i want inser all items in cascade with the idOder assigned to "myOrder"
Ok I will try to add Entity classes you need to have above given scenario.
#Entity
#Table(name="orders")
public class Order{
#Id
#GeneratedValue
#Column(name="ID")
private Long id;
#Column(name="note ")
private String note ;
#OneToMany(mappedBy="orders")
private Set<OrderItem> orderitems;
// Getter and Setter methods
}
And then the OrderItem class
#Entity
#Table(name="OrderItems")
public class OrderItem{
#Id
#Column(name="iditem")
private Long iditem;
#Column(name="qnt")
private long qnt ;
#ManyToOne
#JoinColumn(name="idorder")
private Order order;
public OrderItem() {
}
// Getter and Setter methods
}
Also i dint get which column your setname maps to.. and iditem isnt generated value its assigned id type
Ok then it also could be issue about inverse that who could be the relationship owener .. Inverse=true is same behaviour as mappedBy attribute usage so try changing it..
Let me know if this works or you get a issue while trying this out..