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

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.

Related

JPA/Hibernate Spring boot-primary key one entity referred as an instance to other entity not working

I have generated master tables using liquibase. I have created the corresponding models in spring boot now I want to maintain a relation ship between those models.
I have one table called Vehicle_Type, it is already pre-populated using liquibase.
#Data
#Entity
#Table(name="VEHCILE_TYPE")
public class VehicleType {
#Id
private int id;
#Column(name="DISPLAY_NAME")
private String displayName;
#Column(name="TYPE")
private String type;
#Column(name="CREATED_DATE")
private LocalDateTime createdDate;
#Column(name="UPDATED_DATE")
private LocalDateTime updateDate;
}
now what I want to achieve is, I have one child entity, I have refer the VehicleType instance inside that entity as depicted below
#Data
#Entity
#EqualsAndHashCode(callSuper = true)
#Table(name = "NON_MSIL_VEHICLE_LAYOUT")
public class NonMsilVehicleLayout extends BaseImagesAndLayout {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "NMV_SEQ")
#SequenceGenerator(sequenceName = "NON_MSIL_VEH_SEQUENCE", allocationSize = 1, name = "NMV_SEQ")
private int id;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
public interface VehType {
String getVehType();
}
}
The problem is when I tries to save entity NonMsilVehicleLayout, then it tries to first insert the data in VEHICLE_TYPE table also. which should not going to be happen.
I don't want that, I want JPA will pick the correct ID from VEHICLE_TYPE table and place it inside the corresponding table for NonMsilVehicleLayout, because the id of VEHICLE_TYPE table is act as foreign key in Non_Msil_Vehicle_Layout table.
log.info("Inside saveLayout::Start preparing entity to persist");
String resourceUri = null;
NonMsilVehicleLayout vehicleLayout = new NonMsilVehicleLayout();
VehicleType vehicleType=new VehicleType();
vehicleType.setType(modelCode);
vehicleLayout.setVehicleType(modelCode);
vehicleLayout.setFileName(FilenameUtils.removeExtension(FilenameUtils.getName(object.key())));
vehicleLayout.setS3BucketKey(object.key());
I know I missed something, but unable to figure it out.
You are creating a new VehicleType instance setting only the type field and set the vehicleType field of NonMsilVehicleLayout to that new instance. Since you specified CascadeType.ALL on NonMsilVehicleLayout#vehicleType, this means to Hibernate, that it has to persist the given VehicleType, because the instance has no primary key set.
I guess what you rather want is this code:
vehicleLayout.setVehicleType(
entitManager.createQuery("from VehicleType vt where vt.type = :type", VehicleType.class)
.setParameter("type", typeCode)
.getSingleResult()
);
This will load the VehicleType object by type and set that object on NonMsilVehicleLayout#vehicleType, which will then cause the foreign key column to be properly set to the primary key value.
Finally, after some workaround, I got the mistake, the column name attribute was incorrect, so I made it correct and remove the referencedColumn and Cascading.
Incorrect:
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "VEH_TYPE", referencedColumnName = "id")
private VehicleType vehicleType;
Correct:
#OneToOne
#JoinColumn(name = "VEHICLE_TYPE")
private VehicleType vehicleTypes;
also I have added the annotation #Column in the referende entity VehicleImage
public class VehicleType {
#Id
#Column(name = "ID") // added this one
private int id;
}
That bit workaround solved my problem, now I have achieved what I exactly looking for.

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 stop Hibernate from eagerly fetching a relationship when it is mapped using a column (referencedColumnName) different than the primary key?

I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.

Hibernate ForeignKey mapping annotations

I want to have hibernate generate some tables with foreign keys and so on. Ill give you an example of the query i want hibernate to generate:
create table RealtimeCost(id INTEGER not null primary key Autoincrement,
mnemonic varchar(50)not null references Exchange(mnemonic),
sid int not null references License(sid),
price numeric(10,2) not null)
so this query should be generated by hibernate via Annotations. The corresponding class to this is:
#Entity
#Table
public class RealtimeCost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#MapsId("mnemonic")
#JoinColumn(referencedColumnName="sid")
private String mnemonic;
#MapsId("sid")
#JoinColumn(referencedColumnName="sid")
private Integer sid;
#Column
private Double price;
Example for what the mnemonic in RealtimeCost should be mapped to (each mnemonic in RealtimeCost has exactly 1 value in Exchange):
#Entity
#Table
public class Exchange {
#Id
#Column(name="mnemonic")
private String exchange;
#Column
private String description;
As you can see I've tried a bit with the help of the docs, but I was not able to have the foreign keys be generated by hibernate. It would be really kind, if anyone could tell me the needed annotations and values for this class, so i can do it myself for the other classes as well. Also please tell me if i need to change anything in the Exchange class for the mapping to work. Thanks in advance
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "accommodation_type", unique = true, nullable = false)
private AccommodationType accommodationType;
#ManyToOne()creates a relationship according to #JoinColumn()
name in #JoinColumn() is the table name that you want to make a connection.
Then when you create a class that is going to be connected to main class, you first need to give it a table name below #Entity e.g #Table(name="accommodation_types")
Then you create your variable.
//bi-directional many-to-one association to Accommodation
#OneToMany(mappedBy="accommodationType", fetch=FetchType.EAGER)
private List<Accommodation> accommodations;
value of mappedByis the variable name in main class.
I'm not an expert but we let hibernate do all the work with the javax.persistence annotations for joining entities.
#javax.persistence.ManyToOne( fetch = javax.persistence.FetchType.EAGER, optional = true )
#javax.persistence.JoinColumn( name = "VIEWTYPE_ID", nullable = true, unique = false, insertable = true, updatable = true )
private com.company.other.subproject.ViewType viewType;
Maybe this is what you need. Since this let's hibernate care about the tables that have to be created or not and the foreignKeys get created automatically with the dialect of the database you communicate with.
You should set up the association in one entity and use the mappedBy in the other. You don't need #MapsId because you are not using embedded entities (read the docs). Take a look at the #OneToMany and #ManyToOne relationships:
#Entity
#Table
public class RealtimeCost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany
#JoinColumn(name="mnemonic")
private Exchange exchange;
...
}
#Entity
#Table
public class Exchange {
#Id
#Column(name="mnemonic")
private String mnemonic;
#Column
private String description;
#ManyToOne(mappedBy="exchange")
private RealtimeCost realtimeCost;
...
}
Every answer posted here got an upvote from me, because everyone was kinda right, but it was not 100% what i was searching for, yet it helped me solving my problem by myself. For the example i posted, the solution i was seeking is as follows (i also added not nullable):
#Entity
#Table
public class RealtimeCost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name = "mnemonic",nullable=false)
private Exchange exchange;
#ManyToOne
#JoinColumn(name = "sid",nullable=false)
private License license;
#Column(nullable=false)
private Double price;
these are the annotations i was seeking for RealtimeCost class. I did not need any special annotations in Exchange class. #Nico answer was closest to what i need, therefore his answer will be accepted

Persist an entity that have associated a list of entities that use #idclass

I have an Evaluation entity that has an associated list of EvaluationEvaluator. I need to explicitly create that entity because it required an extra column "STATUS". Before I continue evaluation. I do: evaluation.setEvaluationEvaluator(listEvaluator) where listEvaluator is a list of EvaluationEvaluator type. Then persist(evaluation). When I run this, it does not throw any kind of exception. But in the database, it inserts in the Evaluation table, and not inserted into the EvaluationEvaluator table.
Below my Evaluation entity.
#Entity
public class Evaluation implements Serializable{
#Id
#GeneratedValue
private Long id;
//MORE FIELDS
#OneToMany(mappedBy="evaluation")
private List<EvaluationEvaluator> evaluators;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvalutionEvaluator Entity:
#Entity
#Table(name= "EVALUATION_EVALUATOR")
#IdClass(EvaluationEvaluatorId.class)
public class EvaluationEvaluator implements Serializable{
#Id
#Column(name="EMPLOYEE_ID", insertable=false , updatable=false)
private Long EmployeeID;
#Id
#Column(name="EVALUATION_ID", insertable=false, updatable=false)
private Long EvaluationID;
#ManyToOne
#JoinColumn(name"EMPLOYEE_ID")
private Employee employee;
#ManyToOne
#JoinColumn(name"EVALUATION_ID")
private Evaluation evaluation;
#NotNull
private String status;
//CONSTRUCTORS
//GETTER AND SETTERS
}
This is my EvaluationEvaluatorId class
public class EvaluationEvaluatorId implements Serializable{
private Long employeeID;
private Long evaluationID;
//CONSTRUCTOR
//GETTER AND SETTERS
}
And finally, this is my EvaluationBean class
#Stateful
#Named
#LocalBean
#ConversationScoped
public class EvaluationBean {
#PersistentContext(type= PersistenceContextType.EXTENDED)
private EntityManager em;
#Inject
Conversation conversation;
private Evaluation evaluation;
//IN MY WEBPAGE I IMPLEMENT PRIMEFACES PICKLIST AND IT REQUIRE DUALIST TO HANDLE
private DualListModel<Employe> evaluators;
private EvaluationEvaluator evaluationEvaluator;
private List<EvaluationEvaluator> listEvaluators;
#Inject
private EmployeeList employeeList;
//GETTER AND SETTERS
public String begin(){
if (conversation.isTransient()){
converstaion.begin();
}
evaluationEvaluator = new EvaluationEvaluator();
listEvaluators = new ArrayList<EvaluationEvaluator>();
evaluation = new Evaluation();
List<Employee> source = employeeList.findAll();
target = new ArrayList<Employee>();
evaluators = new DualListModel<Employee>(source, target);
return "/evalution/evaluationAsig.xhtml"
}
public String save(){
Iterator<Employee> iterator = evaluators.getTarget().iterator();
while (iterator.hasNext()){
EvaluationEvaluator ev = new EvaluationEvaluator();
ev.setEmployee(iterator.next());
listEvaluators.add(ev);
}
evalution.setEvaluationEvaluators(listEvaluators);
if(evaluation.getId()==null){
em.persist(evalution);
} else{
em.merge(evalution);
}
if(!conversation.isTransient()){
convesation.end();
}
return "/evalution/evaluationsAsig.xhtml"
}
}
When I debug my application,apparently everything is correct, but I mentioned above, doesn't persist in EvaluationEvaluator table.
Your #OneToMany association is missing cascading configuration.
Add cascade = CascadeType.ALL or cascade = {CascadeType.PERSIST, CascadeType.MERGE} to the #OneToMany annotation. JPA assumes no cascading by default so you would need to persist each EvaluationEvaluator by yourself explicitely otherwise.
UPDATE
There is another thing wrong with the code - the Ids of EvaluationEvaluators are never assigned. You have a complex key made of two Long columns. Both are marked not insertable nor updatable which tells to JPA that the id is going to be somehow generated on database level and it should not care about it. There is however no sequence configured explicitely in your entity (although it is not necessarily required) and also from your comment:
I did what you recommended but it throws the following exception. "A different object with same identifier was already associated with the session"
I assume that this is not the case and both id column values default to null or zero and are same for all EvaluationEvaluators you are trying to persist. If you'd like the database to generate the id for you automatically use #GeneratedValue - Configure JPA to let PostgreSQL generate the primary key value - here you can find explanation how to do this (the database part is database dependent, this is for PostgreSQL). The most common use case however, is to configure the sequence but let hibernate pick the next value, instructions here - Hibernate sequence on oracle, #GeneratedValue(strategy = GenerationType.AUTO)

Categories

Resources