mapping ManyToOne bidirectional relation where one side has a composite primary key - java

I have an entity for storing Workshops where I am using a composite primary key.
I do not like the concept of auto-generated keys and until now have mostly used business derived keys, such as email id etc for entities. However over here, the workshop entity did not appear to have a natural candidate for primary key, so I went for a composite key.I created a WorkshopIdType which is an enum containing the three possible workshops that will be organised
public enum WorkshopIdType implements Serializable {
FOUNDATION("FND"), INTERMEIDATE("IMT"), ADVANCED("ADV");
private final String name;
private WorkshopIdType(String name) {
this.name = name;
}
#Override
public String toString() {
return this.name;
}
public boolean equals(String otherName) {
return (otherName == null) ? false : name.equals(otherName);
}
}
And then I have an Embeddable class for primary key; the combination of workshop type and date appears to me as the best fit for primary key in this scenario
#Embeddable
#Access(AccessType.FIELD)
public class WorkshopId implements Serializable {
private static final long serialVersionUID = -7287847106009163526L;
private String workshopIdType;
private Date date;
#Column(name = "id", nullable = false)
public String getWorkshopIdType() {
return workshopIdType;
}
public void setWorkshopIdType(WorkshopIdType workshopIdType) {
this.workshopIdType = workshopIdType.toString();
}
#Temporal(TemporalType.DATE)
#Column(name = "date", nullable = false)
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
The entity also has a ManyToOne relationship with Venue, here again, the Venues are actually 5 of the pre-designated centres across three cities
#Entity
public class Workshop implements Serializable {
private static final long serialVersionUID = -5516160437873476233L;
private WorkshopId id;
private Venue venue;
private Integer seatsAvailable;
#EmbeddedId
public WorkshopId getId() {
return id;
}
public void setId(WorkshopId id) {
this.id = id;
}
#ManyToOne
#JoinTable(name = "workshop_venue", joinColumns = { #JoinColumn(name = "workshop_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "venue_name", referencedColumnName = "name") })
public Venue getVenue() {
return venue;
}
public void setVenue(Venue venue) {
this.venue = venue;
}
#Column(name = "seats_available", nullable = false)
public Integer getSeatsAvailable() {
return seatsAvailable;
}
public void setSeatsAvailable(Integer seatsAvailable) {
this.seatsAvailable = seatsAvailable;
}
}
Problem is mapping this ManyToOne with a JoinTable in case of a composite key
#ManyToOne
#JoinTable(name = "workshop_venue", joinColumns = { #JoinColumn(name = "workshop_id", referencedColumnName = "id") }, inverseJoinColumns = { #JoinColumn(name = "venue_name", referencedColumnName = "name") })
public Venue getVenue() {
return venue;
}
This won't work as I had suspected, it cannot find a column with logical name "id". I am going with ManyToOne with JoinTable because there can be a scenario where users should get to know is there a training scheduled for the given Venue. How do I specify the referencedColumnName in this case?
Either that or I got it all wrong in the way I am modelling this?

You have a composite Primary Key represented by the embeddable WorkshopId
embedded in Workshop. Both fields of the Primary Key need to be joined to the target entity in the link table – therefore you need 2 joincolumns and one inversejoin. You just need to add the missing join;
#ManyToOne
#JoinTable(name = "workshop_venue",
joinColumns =
{ #JoinColumn(name = "workshop_id", referencedColumnName = "id"),
/* Add this joincolumn */
#JoinColumn(name = "date_id", referencedColumnName = "date") },
inverseJoinColumns =
{ #JoinColumn(name = "venue_name", referencedColumnName = "name") })
public Venue getVenue() {
return venue;
Of course you need to ensure that you link table has these three fields. I guess you are missing the date_id column.

Related

OneToOne mapping not working: EntityA.entityB referencing EntityB not mapped to a single property

OneToOne mapping not working: EntityA.entityB referencing EntityB not mapped to a single property
I have two entities, EntityA and EntityB.
EntityA has a composite primary key on two fields: id and flag.
EntityB also has a composite primary key on two fields: id and flag.
The two entities are linked with a common field / column: entityBKey. (This is a foreign key but not explicitly defined as such at the database level, if that makes a difference. Old design can't really change much)
This is how I am calling EntityA:
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<EntityA> criteriaQuery = builder.createQuery(EntityA.class);
Root<EntityA> from = criteriaQuery.from(EntityA.class);
criteriaQuery.select(from);
But I get this error on application boot:
EntityA.entityB referencing EntityB not mapped to a single property
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:203)
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:104)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processEndOfQueue(InFlightMetadataCollectorImpl.java:1750)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processFkSecondPassesInOrder(InFlightMetadataCollectorImpl.java:1694)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1623)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:295)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:86)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:479)
at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:85)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:709)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:746)
In some places I have read it says that non-primary keys can be referenced by using the referencedColumnName attribute but it is clearly not working.
Any ideas what I can do here? Much appreciated.
EntityA:
#Entity
#Table(name = "EntityA")
#IdClass(KeyClass.class)
public class EntityA implements Serializable {
#Id
#Column(name = "id", nullable = false)
protected Integer id = null;
#Id
#Column(name = "flag", nullable = false)
protected Integer flag = null;
#Column(name = "EntityAKey", nullable = false)
private String entityAKey = null;
#Column(name = "EntityBKey", nullable = false)
private String entityBKey = null;
#OneToOne(mappedBy="entityA")
private EntityB entityB;
public EntityA() {
super();
}
}
EntityB:
#Entity
#Table(name = "EntityB")
#IdClass(KeyClass.class)
public class EntityB implements Serializable {
#Id
#Column(name = "id", nullable = false)
protected Integer id = null;
#Id
#Column(name = "flag", nullable = false)
protected Integer flag = null;
#Column(name = "EntityBKey", nullable = false)
private String entityBKey = null;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "entityBKey", referencedColumnName = "entityBKey", insertable = false, updatable = false),
#JoinColumn(name = "flag", referencedColumnName = "flag", insertable = false, updatable = false)}
)
#Where(clause = "flag=1")
private EntityA entityA;
public EntityB() {
super();
}
}
KeyClass:
public class KeyClass implements Serializable {
private Integer id = null;
private Integer flag = null;
public KeyClass() {
}
public KeyClass(Integer id, Integer flag) {
this.id = id;
this.flag = flag;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getFlag() {
return flag;
}
public void setFlag(Integer flag) {
this.flag = flag;
}
#Override
public boolean equals(Object o) {
...
}
#Override
public int hashCode() {
...
}
}

Hibernate Map ID automatically from field

I have something similar to this:
#Entity
#Table(name = "claim", schema = "test")
public class Claim implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idClaim", unique = true, nullable = false)
private Integer idClaim;
#OneToOne(mappedBy = "claim", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JsonManagedReference
private ClaimReturnInfo claimReturnInfo;
#Column(name = "notes")
private String notes;
// Getters and setters
}
#Entity
#Table(name = "claim_returninfo", schema = "test")
public class ClaimReturnInfo implements Serializable {
#Id
#Column(name = "Claim_idClaim")
private Integer id;
#MapsId("Claim_idClaim")
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Claim_idClaim")
#JsonBackReference
private Claim claim;
#Column(name = "description")
private String description;
// Getters and setters
}
ClaimReturnInfo Id is not autogenerated because we want to propagate the Id from its parent (Claim). We are not able to do this automatically and we are getting this error: ids for this class must be manually assigned before calling save() when 'cascade' is executed in ClaimReturnInfo .
Is it possible to map Claim Id into ClaimReturnInfo Id or should we do this manually?
Even if we set this ID manually on claimReturnInfo and we can perform updates, we still get this error when trying to create a new Claim:
// POST -> claimRepository.save() -> Error
{
"notes": "Some test notes on a new claim",
"claimReturnInfo": {
"description": "Test description for a new claimReturnInfo"
}
}
In the ServiceImplemetation:
#Override
#Transactional
public Claim save(Claim claim) throws Exception {
if(null != claim.getClaimReturnInfo()) {
claim.getClaimReturnInfo().setId(claim.getIdClaim());
}
Claim claimSaved = claimRepository.save(claim);
return claimSaved;
}
I have tried using the following mappings and from your comments it was apparent that Json object is populated correctly.
I have noticed that the annotation #MapsId is the culprit.If you check the documentation of #MapsId annotation it says
Blockquote
The name of the attribute within the composite key
* to which the relationship attribute corresponds. If not
* supplied, the relationship maps the entity's primary
* key
Blockquote
If you change #MapsId("Claim_idClaim") to #MapsId it will start persisting your entities.
import javax.persistence.*;
#Entity
#Table(name = "CLAIM")
public class Claim {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idClaim", unique = true, nullable = false)
private Long idClaim;
#Column(name = "notes")
private String notes;
#OneToOne(mappedBy = "claim", cascade = CascadeType.ALL, optional = false)
private ClaimReturnInfo claimReturnInfo;
public Long getIdClaim() {
return idClaim;
}
public String getNotes() {
return notes;
}
public void setNotes(String notes) {
this.notes = notes;
}
public ClaimReturnInfo getClaimReturnInfo() {
return claimReturnInfo;
}
public void setClaimReturnInfo(ClaimReturnInfo claimReturnInfo) {
if (claimReturnInfo == null) {
if (this.claimReturnInfo != null) {
this.claimReturnInfo.setClaim(null);
}
} else {
claimReturnInfo.setClaim(this);
}
this.claimReturnInfo = claimReturnInfo;
}
}
package com.hiber.hiberelations;
import javax.persistence.*;
#Entity
#Table(name = "CLAIM_RETURN_INFO")
public class ClaimReturnInfo {
#Id
#Column(name = "Claim_idClaim")
private Long childId;
#Column(name = "DESCRIPTION")
private String description;
#MapsId
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Claim_idClaim")
private Claim claim;
public Long getChildId() {
return childId;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Claim getClaim() {
return this.claim;
}
public void setClaim(Claim claim) {
this.claim = claim;
}
}

Get a column value from different table without PK/FK relation in Spring Data - JPA

I have a Entity class named as Employee
#Entity
#Table(name = "employee")
#DynamicUpdate
public class Employee {
private String empId;
private String name;
private String employeeDefaultCountry;
#Id
#Column(name="emp_id", nullable=false, insertable=true, updatable=true, length=8)
public String getEmpId() {
return empId;
}
public void setEmpId(String empId) {
this.empId= empId;
}
...
}
employeeDefaultCountry is defined in other table "system_defaults" and there is no primary and foreign key relation between these two tables(employee and system_defaults).
However a constant value will be provided for system_defaults id(PK) that is constant 1. How can i achieve it?
Achieving via Native SQL:
select empId,name,(select employee_default_country from system_defaults where id=1) as DefaultCountry from employee.
Output:
empId,Name,DefaultCountry
101,Ahmed,India
103,Parkash,India
...
I want to achieve this in JPA.
Note: This DefaultCountry field should be readable, when saving to DB it should not write.
employee Table fields(emp_id(VARCHAR2),name(VARCHAR2)) only two fields
system_parameters Table fields(id(INT),default_country(VARCHAR2),default_designation(VARCHAR2)) only three fields.
Entity of System_defaults:
#Entity
#Table(name = "system_defaults")
#DynamicUpdate
public class SystemDefaults {
Integer id;
String defaultCountry;
#Id
#Column(name = "id", nullable = false, insertable = false, updatable = false)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
#Basic
#Column(name = "default_country", nullable = false, insertable = false, updatable = false, length = 40)
public String getDefaultCountry() {
return defaultCountry;
}
public void setDefaultCountry(String defaultCountry) {
this.defaultCountry = defaultCountry;
}
}

Hibernate composite foreign keys share column

I'm working on a legacy database which I cannot change. This database regularly synches with secondary instances, which create new entries. To make an entity's primary key unique for all instances, all entities have composite primary keys consisting of the surrogate key generated by their original database and a server ID which identifies the original instance.
Composite primary keys are no Problem for Hibernate / JPA, it looks like this:
#Embeddable
public class ID implements Serializable {
private Long autoin; // Surrogate key
private Integer serverId; // instance identifier
#Column(name = "autoin_fix")
public Long getAutoin() {
return this.autoin;
}
#Column(name = "servdat_fk")
public Integer getServerId() {
return this.serverId;
}
// ... setter, equals, hashCode ...
}
Now consider the following Entity:
#Entity
#Table(name = "LEADS")
public class Request {
private Long id;
private Article article;
private Location location;
#Id
#Column(name = "autoin_fix")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "auto-increment")
#GenericGenerator(name = "auto-increment", strategy = "sequence",
parameters = #org.hibernate.annotations.Parameter(name = "sequence", value = "AUTOINCREMENT"))
public Long getId() {
return this.id;
}
#ManyToOne
#JoinColumns({
#JoinColumn(name = "artikel_fk", referencedColumnName = "autoin_fix",
insertable = false, updatable = false), // Here is the problem!
#JoinColumn(name = "servdat_fk", referencedColumnName = "servdat_fk",
insertable = false, updatable = false)
})
public Article getArticle() {
return this.article;
}
#ManyToOne
#JoinColumns({
#JoinColumn(name = "standort_fk", referencedColumnName = "autoin_fix"),
#JoinColumn(name = "servdat_fk", referencedColumnName = "servdat_fk")
})
public Location getLocation() {
return this.location;
}
// Setters omitted
}
The Request entity has references to two other entities which both use the composite primary keys to identify themselves. But since from a business logic point of view, the serverId of both of these entities (Article and Location) always have to be the same, there is only one column for the serverId in the database:
TABLE leads
-------------
autoin_fix
artikel_fk
standort_fk
servdat_fk // This only exists once, but is part of the association to both Article and Location!
To make the application start at all, I had to add insertable = false, updatable = false to the Article association, which is not what I want. If I try to persist a Request object, the field "artikel_fk" is not written, leaving a null in the database, since I told hibernate that it is read-only. And using insertable = false, updatable = false on just one of the join columns is not allowed either.
// This is illegal and the application won't start
#JoinColumns({
#JoinColumn(name = "artikel_fk", referencedColumnName = "autoin_fix"),
#JoinColumn(name = "servdat_fk", referencedColumnName = "servdat_fk", insertable = false, updatable = false)
})
Does anyone know a solution to this problem? Or is this impossible to map using Hibernate / JPA?
EDIT: The (simplified) definitions of Article and Location (really not that interesting, all that counts is that they use the composite primary key):
#Entity
#Table(name = "ARTIKEL")
public class Article {
private ID id;
private String headline;
private String description;
#EmbeddedId
public ID getId() {
return this.id;
}
#Column(name = "artbesch")
public String getDescription() {
return this.description;
}
#Column(name = "artueschr")
public String getHeadline() {
return this.headline;
}
// Setters omitted
}
#Entity
#Table(name = "STANDORT")
public class Location {
private ID id;
private GeoCoordinates geoCoordinates;
private Name name;
#EmbeddedId
public ID getId() {
return this.id;
}
#Embedded
public GeoCoordinates getGeoCoordinates() {
return this.geoCoordinates;
}
#Embedded
public Name getName() {
return this.name;
}
// Setters omitted
}
I think that if the derived identifier of a dependent entity is in the form of an embedded id class, then each attribute of that id class that represents a relationship should be referred to by a #MapsId annotation on the corresponding relationship attribute.
For instance:
#Entity
public class Request {
// ...
#Id
int id;
#MapsId
#ManyToOne
private Article article;
// ...
}
I hope that works, because I cannot try it in this moment.

How to create join table with JPA annotations?

I need to create a join table in my database using JPA annotations so the result will be this:
So far I just implemented 2 entities:
#Entity
#Table(name="USERS", schema="ADMIN")
public class User implements Serializable {
private static final long serialVersionUID = -1244856316278032177L;
#Id
#Column(nullable = false)
private String userid;
#Column(nullable = false)
private String password;
public String getUserid() {
return userid;
}
public void setUserid(String userid) {
this.userid = userid;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
#Entity
#Table(name="GROUPS", schema="ADMIN")
public class Group implements Serializable {
private static final long serialVersionUID = -7274308564659753174L;
#Id
#Column(nullable = false)
private String groupid;
public String getGroupid() {
return groupid;
}
public void setGroupid(String groupid) {
this.groupid = groupid;
}
}
Should i create another entity called USER_GROUP or i can just add some annotations, so the join table will be created automatically when i run create tables from entities(ORM)?
How should i annotate my entities to achieve the same as in the image?
You definitely shouldn't create User_Group entity as it's more the underlying database representation than the object oriented one.
You can achieve the join table by defining something like:
#Entity
#Table(name="USERS", schema="ADMIN")
public class User implements Serializable {
//...
#ManyToOne
#JoinTable(name="USER_GROUP")
Group group;
#Entity
#Table(name="GROUPS", schema="ADMIN")
public class Group implements Serializable {
//...
#OneToMany(mappedBy="group")
Set<User> users;
Edit: If you want to explicitly set the names of the columns you could use #JoinColumn elements as shown below:
#ManyToOne
#JoinTable(name="USER_GROUP",
joinColumns = #JoinColumn(name = "userid",
referencedColumnName = "userid"),
inverseJoinColumns = #JoinColumn(name = "groupid",
referencedColumnName = "groupid"))
Group group;
I would implement it this way:
#Entity
#Table(name="GROUPS", schema="ADMIN")
public class Group implements Serializable {
#OneToMany
#JoinTable(name = "USER_GROUP",
joinColumns = #JoinColumn(name = "groupid"),
inverseJoinColumns = #JoinColumn(name = "userid"))
private List<User> users;
}
Solution suggested by #PedroKowalski should work too, but then you'll have to keep a reference to Group entity in your User entity which is not always possible.
To have the same annotations like in your diagram you can do this in your User class:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "USER_GROUP",
joinColumns = { #JoinColumn(name = "userid") },
inverseJoinColumns = { #JoinColumn(name = "groupid") })
private List<Group> grups;
in your group class
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "USER_GROUP",
joinColumns = { #JoinColumn(name = "groupid") },
inverseJoinColumns = { #JoinColumn(name = "userid") })
private List<User> users;
I'm wondering what is the point to create a Join Table in this way, considering that we can't access directly for queries?
JPA doesn't allow to make queries directly to the Join Table, so if the user want to do an operation on USER_GROUP, he has to creare a normal join query between users and groups; due to this, the join table USER_GROUP is useless.

Categories