Joining tables with composite keys in a legacy system in hibernate - java

I'm currently trying to create a pair of Hibernate annotated classes to load (read only) from a pair of tables in a legacy system. The legacy system uses a consistent (if somewhat dated) approach to keying tables. The tables I'm attempting to map are as follows:
Customer CustomerAddress
-------------------------- ----------------------------
customerNumber:string (pk) customerNumber:string (pk_1)
name:string sequenceNumber:int (pk_2)
street:string
postalCode:string
I've approached this by creating a CustomerAddress class like this:
#Entity
#Table(name="CustomerAddress")
#IdClass(CustomerAddressKey.class)
public class CustomerAddress {
#Id
#AttributeOverrides({
#AttributeOverride(name = "customerNumber", column = #Column(name="customerNumber")),
#AttributeOverride(name = "sequenceNumber", column = #Column(name="sequenceNumber"))
})
private String customerNumber;
private int sequenceNumber;
private String name;
private String postalCode;
...
}
Where the CustomerAddressKey class is a simple Serializable object with the two key fields. The Customer object is then defined as:
#Entity
#Table(name = "Customer")
public class Customer {
private String customerNumber;
private List<CustomerAddress> addresses = new ArrayList<CustomerAddress>();
private String name;
...
}
So, my question is: how do I express the OneToMany relationship on the Customer table?

I may have an answer for my own question. Add the following to Customer:
#OneToMany(mappedBy="customer")
#JoinColumn(name="customerNumber")
List<CustomerAddress> addresses = new ArrayList<CustomerAddress>();
And the following to CustomerAddress:
#ManyToOne
#JoinColumn(name="customerNumber")
protected Customer customer;
This turns out to be a simpler problem to solve than I first thought. Typical.

I assume you have read the Bauer/King Hibernate book, which is extremely bad at explaining how do implement composite primary keys correctly. Don't let yourself be fooled by a bad book: composite primary key are not a property of legacy systems...

Related

Obtain column data from One-To-Many join relation in Spring data JPA

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.

How to separate data into two tables with Spring Boot?

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

Why in this Hibernate mapping it is used #ManyToOne instead #OneToOne?

I am absolutly new in Hibernate development and I have the following problem.
I have 2 entity classes that maps 2 DB tables:
1) The first entity class (the main one) is named KM_ProjectInfo and map a DB table named KM_PROJECT.
2) The second entity class is named KM_ProjectInfoStatus and map a DB table named KM_PROJECT_INFO_STATUS.
So the second one represent a specific field of the first one (a status of the row representd by an instance of the KM_ProjectInfo class). Infact I have something like this:
1) KM_ProjectInfo class:
#Entity
#Table(name = "KM_PROJECT")
public class KM_ProjectInfo implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfo;
#Column(name = "name")
private String name;
#Column(name = "technology")
private String technology;
#ManyToOne
#JoinColumn(name = "idCountry")
private KMCountry country;
#Column(name = "power")
private long power;
#Column(name = "cod")
private String cod;
#ManyToOne
#JoinColumn(name = "idProjectInfoStatus")
private KM_ProjectInfoStatus status;
// GETTERS & SETTERS
}
2) KM_ProjectInfoStatus:
#Entity
#Table(name = "KM_PROJECT_INFO_STATUS")
public class KM_ProjectInfoStatus implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfoStatus;
#Column(name = "foldertech")
private Long foldertech;
#Column(name = "folderproject")
private Long folderproject;
// GETTERS & SETTERS
}
So, as you can see in the previous snippet, the KM_ProjectInfoStatuss is a field of the KM_ProjectInfo because I want that it contains the primary key of this table as foreign key.
In the logic of my application I want that at one row of the KM_PROJECT table (so at one instance of the KM_ProjectInfo entity class) is associated a single row of the KM_PROJECT_INFO_STATUS (one instance of the KM_ProjectInfoStatus entity class) because it represent a specific status for the KM_PROJECT row.
In my code I have:
#ManyToOne
#JoinColumn(name = "idProjectInfoStatus")
private KM_ProjectInfoStatus status;
but I think that is wrong because at one row of my first table it is associated a specific single row of the second table. But maybe I am missing something about how Hibernate work.
Can you help me to understand what I am missing? What it work? Why I have #ManyToOne instead #OneToOne?
Tnx
It all depends on how you want to model things. In terms of Database structure, OneToOne and ManyToOne are implemented in the same way:
One or more JoinColumns which makes a foreign key pointing to the primary key of the other table.
So both solutions correctly map to your database, but it depends if you want to allow several KM_ProjectInfo to point to the same KM_ProjectInfoStatus, or only allow a single one.
Note that, even though you would declare a OneToOne, you could still end up with multiple KM_ProjectInfo pointing to the same KM_ProjectInfoStatus if you don't manipulate Hibernate properly.
Here you did not declare the reverse relationship, but if you did, the declaration would have to be different:
In case of a OneToOne, you would have a KM_ProjectInfo member
In case of a OneToMany (reverse of ManyToOne), you would have a Collection<KM_ProjectInfo> member
From the description it seems you want to have one-to-one relationship. That is the project entity should have its very own status not shared by any other project. You could achieve this by using #OneToOne as below.
#Entity
#Table(name = "KM_PROJECT")
public class KM_ProjectInfo implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfo;
#OneToOne
#JoinColumn(name = "idProjectInfoStatus")
private KM_ProjectInfoStatus status;
}
#Entity
#Table(name = "KM_PROJECT_INFO_STATUS")
public class KM_ProjectInfoStatus implements Serializable {
#Id
#GeneratedValue
private Long idProjectInfoStatus;
#OneToOne(mappedBy="idProjectInfoStatus")
private KM_ProjectInfo project;
}
This way you can have specific status for the KM_PROJECT.
Coming back to #ManyToOne, you will want to have this if you want to share the same status with multiple projects, but that's not what you want in your case. I have tried to explain mappings in simple way here One-to-One mapping.

How configure for a (Optional) OneToOne Composite Primary Key?

I am using Hibernate and have two tables, STUDENTS and DORM_ROOMS, that are related with a composite key:
STUDENTS table:
CAMPUS(String) Part of Composite Key
STUDENT_ID (int) Part of Composite Key
NAME (String)
...
DORM_ROOMS table:
CAMPUS(String) Part of Composite Key
STUDENT_ID (int) Part of Composite Key
ROOM_NUMBER(int)
...
The relationship is one to one because a student can be associated with exactly one dorm room and and a dorm room is associated with one student (wow - a private room!). However, not all students have a dorm room.
My initial code (stripped down) looks like:
FOR STUDENTS:
#Embeddable
public class StudentsPK implements Serializable {
#Column(name = "CAMPUS")
private String Campus;
#Column(name = "STUDENT_ID")
private String StudentID;
...
}
#Entity
#Table(name = "STUDENTS")
public class Students implements Serializable {
#EmbeddedId
private StudentsPK studentsPK;
...
}
FOR DORM_ROOMS:
#Embeddable
public class DormRoomsPK implements Serializable {
#Column(name = "CAMPUS")
private String Campus;
#Column(name = "STUDENT_ID")
private String StudentID;
...
}
#Entity
#Table(name = "DORM_ROOMS")
public class DormRooms implements Serializable {
#EmbeddedId
private DormRoomsPK dormRoomsPK;
...
}
Assume that the database schema is already defined and created. In particular, CAMPUS+STUDENT_ID is a PK for STUDENTS and CAMPUS+STUDENT_ID is a FK for DORM_ROOMS that serves as the PK in that table. At this point I can successfully insert a row into STUDENTS and a row into DORM_ROOMS. I can also retrieve any student from STUDENTS even if the student does not have a dorm room. However, I have not yet "informed" Hibernate about the relationship between the two tables. That is where I am confused.
I tried to "relate" the two tables by using a "JOIN" annotation but I discovered that this causes any attempt to fetch a student that has no dorm room to return an empty result set. I suppose that makes since if "JOIN" states that the tables are to always be viewed as joined then joining a student having no dorm room with no matching rows in the DORM_ROOMS table would result in an empty result set.
Since using a "JOIN" annotation doesn't work, how do I modify my code to describe the relationship between the two tables but still allow me to fetch students that have no matching dorm rooms?
Thank you.
It sounds like you are looking for the #OneToOne annotation, which also has the ability to specify if the relationship is optional. There are some examples described in the JBoss JPA 2.1 docs, here is one of them.
Example 3: One-to-one association from an embeddable class to another entity.
#Entity
public class Employee {
#Id int id;
#Embedded LocationDetails location;
...
}
#Embeddable
public class LocationDetails {
int officeNumber;
#OneToOne ParkingSpot parkingSpot;
...
}
#Entity
public class ParkingSpot {
#Id int id;
String garage;
#OneToOne(mappedBy="location.parkingSpot") Employee assignedTo;
}
Found the problem! I discovered that in a #OneToOne relationship with a composite key, using a separate FK class to manage the composite key in both entities causes the error. The problem is shown in my original posting where I define and use StudentsPK and DormRoomsPK! Once I changed to use a single "PK" class instead of these two my problem was eliminated. (This doesn't appear to be a well documented requirement!)

How to define unidirectional OneToMany relationship in JPA

I have a following problem with entity mapping in JPA. I have two entities, first one is Lookup and the second is Text which represents translations for entities. Now I need to bound Lookup to the Text but I don't want Text to have reference to Lookup. To make this more complicated, Text does not use its primary key in this relationship but a metacode defined in a TXTHEAD_CODE column.
Lookup.java
#Entity
#Table(name = "DATREG")
public class Lookup implements PersistableEntity {
#Id
#Column(name = "DATREG_META_CODE")
private String metaCode;
#OneToMany
#JoinTable(name="TXT",
joinColumns=#JoinColumn(name="DATREG_META_CODE", referencedColumnName="TXTHEAD_CODE"),
inverseJoinColumns=#JoinColumn(name="DATREG_META_CODE"))
private List<Text> text;
Text.java
#Entity
#Table(name = "TXT")
public class Text {
#Id
#Column(name = "TXT_ID")
private Long id;
#Column(name = "TXTHEAD_CODE")
private String code;
So I have tried this (and few other variations) but with no result. I also can't create join table in the DB and I don't want bound Lookup to my Text class. So can anyone please tell me if there is some other way?
My bible for JPA work is the Java Persistence wikibook. It has a section on unidirectional OneToMany which explains how to do this with a #JoinColumn annotation. In your case, i think you would want:
#OneToMany
#JoinColumn(name="TXTHEAD_CODE")
private Set<Text> text;
I've used a Set rather than a List, because the data itself is not ordered.
The above is using a defaulted referencedColumnName, unlike the example in the wikibook. If that doesn't work, try an explicit one:
#OneToMany
#JoinColumn(name="TXTHEAD_CODE", referencedColumnName="DATREG_META_CODE")
private Set<Text> text;

Categories

Resources