Composite Key in Hibernate - java

Java Persistence with Hibernate shows how to create a composite key:
#Entity
#Table(name = "CATEGORIZED_ITEM")
public class CategorizedItem {
#Embeddable
public static class Id implements Serializable {
#Column(name = "CATEGORY_ID")
private Long categoryId;
#Column(name = "ITEM_ID")
private Long itemId;
public Id() {}
public Id(Long categoryId, Long itemId) {
this.categoryId = categoryId;
this.itemId = itemId;
}
public boolean equals(Object o) {
if (o != null && o instanceof Id) {
Id that = (Id)o;
return this.categoryId.equals(that.categoryId) &&
this.itemId.equals(that.itemId);
} else {
return false;
}
}
public int hashCode() {
return categoryId.hashCode() + itemId.hashCode();
}
}
#EmbeddedId
private Id id = new Id();
#Column(name = "ADDED_BY_USER")
private String username;
#Column(name = "ADDED_ON")
private Date dateAdded = new Date();
Is the approach of making the Id static common when making a composite key? If so, why?
Why is it necessary to instantiate Id in CategorizedItem?
private Id id = new Id();

There are actually two ways you can specify composite keys, one being #EmbeddedId and the other being an ID class: #IdClass. This is a pretty good tutorial showing you the options and providing suggestions on how to work on both styles of composite key specifier: http://www.objectdb.com/java/jpa/entity/id
I haven't personally seen the primary key class be embedded inside the class using that key, but if the key class is not required to be used anywhere else, than it makes sense. A public static nested class is basically the same as a root level class, it just shows the intent that the class has a tight association with its enclosing class.
As for creating an instance of the class, I think most examples do it via the constructor. You must obviously provide values for the primary key components before trying to persist the entity to the database. Here's a typical example of that: http://www.java2s.com/Tutorial/Java/0355__JPA/EmbeddedCompoundPrimaryKey.htm

Related

How to define the composite primary keys?

I have database table. DDL for the table is:
CREATE TABLE `acsblts` (
`usrnm` varchar(64) NOT NULL,
`rl` varchar(64) NOT NULL,
UNIQUE KEY `acsblts_idx_1` (`usrnm`,`rl`),
CONSTRAINT `acsblts_ibfk_1` FOREIGN KEY (`usrnm`) REFERENCES `lgn_crdntls` (`usrnm`)
)
Now I want to create Java class for this table. What I have done is:
#Entity
#Table(name = "acsblts", uniqueConstraints = { #UniqueConstraint(columnNames = { "rl", "usrnm" }) })
public class Acsblts {
#NotNull
#Column(name = "rl")
private String rl;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH })
#JoinColumn(name = "usrnm", nullable = false)
#JsonIgnore
private LgnCrdntls usrnm;
// Constructors, Getters, Setters
}
When I try to run the application, it shows the ERROR:
No identifier specified for entity: com.example.mngmntsstm.entity.user.Acsblts
What I understand is: the absence of #Id is causing the ERROR. How can I create a Composite Primary Key using rl and usrnm.
Is it a good idea to use the following id as a primary_key instead of composite_primary_key?
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
I think that the simplest way in your case will be using composite identifiers with associations.
#Entity
#Table(name = "acsblts")
public class Acsblts implements Serializable
{
#Id
#Column(name = "rl")
private String rl;
#Id
#ManyToOne
#JoinColumn(name = "usrnm", nullable = false)
private LgnCrdntls usrnm;
public Acsblts()
{}
public Acsblts(String rl, String usrnm)
{
this.rl = rl;
this.usrnm = new LgnCrdntls(usrnm);
}
// getters, setters
#Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass() ) return false;
Acsblts that = (Acsblts) obj;
return Objects.equals(rl, that.rl) &&
Objects.equals(usrnm, that.usrnm);
}
#Override
public int hashCode()
{
return Objects.hash(rl, usrnm);
}
}
Please note as there’s no separation between the entity instance and the actual identifier you should pass an instance of Acsblts as the primaryKey parameter to the find method.
Acsblts dat = session.find(Acsblts.class, new Acsblts("CRD2", "RL5"));
You can also use composite identifiers with #EmbeddedId
In this case, you should declare the AcsbltsPK class in the following way:
#Embeddable
public class AcsbltsPK implements Serializable
{
#Column(name = "rl")
private String rl;
#ManyToOne
#JoinColumn(name = "usrnm")
private LgnCrdntls usrnm;
#Override
public boolean equals(Object obj)
{
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass() ) return false;
AcsbltsPK pk = (AcsbltsPK) obj;
return Objects.equals(rl, pk.rl) &&
Objects.equals(usrnm, pk.usrnm);
}
#Override
public int hashCode()
{
return Objects.hash(rl, usrnm);
}
}
And then use it in the Acsblts entity:
#Entity
#Table(name = "acsblts")
public class Acsblts
{
#EmbeddedId
private AcsbltsPK pk;
// ...
}
You can also use composite identifiers with #IdClass.
Is it a good idea to use the following id as a primary_key instead of composite_primary_key?
You should correct your existing sсhema for that. Sometimes, this is not acceptable.
Using Long ID as primary key will help to search for results faster due to indexing but if you still want to use primary composite key refer to the following links and try to apply them to your problem
https://vladmihalcea.com/the-best-way-to-map-a-composite-primary-key-with-jpa-and-hibernate/
https://www.baeldung.com/jpa-composite-primary-keys

#Formula not working in hibernate with object

I have a enum of few status value
NEW, REVIEWD, PUBLISHED, PENDING, UPDATED, SPAM, DUPLICATE, IRRELEVANT, UNPUBLISHED
I don't want to use them as enumerated so created one entity for that. For convenient I want to keep a column in entity to initialize status from enum and convert that enumerated value to a Object of status entity. for this..
I have two entity. I want to refer a column with value from another entity.
Basically I want to initialize a object with formula.
Entities are
#Entity
#Table(name = "event_status")
public class EventStatus {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="eventStatusId")
private Integer eventStatusId;
#Enumerated(EnumType.STRING)
#Column(unique = true,name="eventStatusType")
private EventStatusType eventStatusType;
public EventStatus() {
this(EventStatusType.NEW);
}
public EventStatus(EventStatusType eventStatusType) {
super();
this.eventStatusType = eventStatusType;
}
public Integer getEventStatusId() {
return eventStatusId;
}
public EventStatusType getEventStatusType() {
return eventStatusType;
}
public void setEventStatusId(Integer eventStatusId) {
this.eventStatusId = eventStatusId;
}
public void setEventStatusType(EventStatusType eventStatusType) {
this.eventStatusType = eventStatusType;
}
}
I have another entity in which I am referring object of this entity
#Entity
#Table(name = "event_")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class Event implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id_")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Transient
public EventStatusType eventStatusType = EventStatusType.NEW;
#ManyToOne(fetch = FetchType.EAGER, targetEntity = EventStatus.class)
#Formula("select * from event_status where eventStatusId= 1")
private EventStatus status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public EventStatus getStatus() {
System.out.println("Event.getStatus() " + status);
return status;
}
public void setStatus(EventStatus status) {
System.out.println("Event.setStatus()");
this.status = status;
}
}
This is not giving any exception but not initializing this value.
Is it possible to initialize this EntityStatus with value of eventStatusType in Event entity
I would like to explain that based on the documentation:
5.1.4.1.5. Formula
Sometimes, you want the Database to do some computation for you rather than in the JVM, you might also create some kind of virtual column. You can use a SQL fragment (aka formula) instead of mapping a property into a column. This kind of property is read only (its value is calculated by your formula fragment).
#Formula("obj_length * obj_height * obj_width")
public long getObjectVolume()
The SQL fragment can be as complex as you want and even include subselects.
...
5.1.7.1. Using a foreign key or an association table
...
Note
You can use a SQL fragment to simulate a physical join column using the #JoinColumnOrFormula / #JoinColumnOrformulas annotations (just like you can use a SQL fragment to simulate a property column via the #Formula annotation).
#Entity
public class Ticket implements Serializable {
#ManyToOne
#JoinColumnOrFormula(formula="(firstname + ' ' + lastname)")
public Person getOwner() {
return person;
}
...
}
Also, we should use insertable = false, updatable = false, because such mapping is not editable

Hibernate Exception: Unable to find properties ... in entity annotated with #IdClass

I have this composite key class:
#Embeddable
public class TaskEntityId implements Serializable {
private static final long serialVersionUID = 1L;
public String objectUuid;
#Column(name="DOMAIN_OBJECT_UUID", nullable=false, length=36)
public String getObjectUuid() { return objectUuid; }
public void setObjectUuid(String uuid) { this.objectUuid = uuid; }
public String taskName;
#Column(name = "TASK_NAME", nullable=false, length=32)
public String getTaskName() { return taskName; }
public void setTaskName(String taskName) { this.taskName = taskName; }
public Date createdTimestamp = new Date();
#Column(name = "CREATED_TS", nullable=false, updatable=false)
#Temporal(TemporalType.TIMESTAMP)
public Date getCreatedTimestamp() { return createdTimestamp; }
public void setCreatedTimestamp(Date createdTimestamp) { this.createdTimestamp = createdTimestamp; }
#Override
public boolean equals(Object o) {...}
#Override
public int hashCode() {...}
#Override
public String toString() {...}
}
Which is used in this entity class:
#Entity
#Table(name = "TASK")
#IdClass(TaskEntityId.class)
public class TaskEntity implements Serializable {
#EmbeddedId
TaskEntityId id;
public TaskEntityId getId() { return id; }
public void setId(TaskEntityId id) { this.id = id; }
...
I also added to this class some extra getters based on suggestions to other similar questions:
public String getObjectUuid() { return id.getObjectUuid(); }
public String getTaskName() { return id.getTaskName(); }
public Date getCreatedTimestamp() { return id.getCreatedTimestamp(); }
Still, I get this exception:
Error in custom provider, org.hibernate.AnnotationException: Unable to find properties (createdTimestamp, objectUuid, taskName) in entity annotated with #IdClass:c.m.d.p.TaskEntity
The solution is to remove the
#IdClass(TaskEntityId.class)
annotation from the TaskEntity class. And I also needed an empty default constructor.
To map a composite key, you can use the EmbeddedId or the IdClass annotations. I know this question is not strictly about JPA but the rules defined by the specification also applies. So here they are:
2.1.4 Primary Keys and Entity Identity
...
A composite primary key must correspond to either a single persistent field or property or to a set of such fields or properties as described below. A primary key class must be defined to represent a composite primary key. Composite primary keys typically arise when mapping from legacy databases when the database key is comprised of several columns. The EmbeddedId and IdClass annotations are used to denote composite primary keys. See sections 9.1.14 and 9.1.15.
...
The following rules apply for composite primary keys:
The primary key class must be public and must have a public no-arg constructor.
If property-based access is used, the properties of the primary key class must be public or protected.
The primary key class must be serializable.
The primary key class must define equals and hashCode methods. The semantics of value equality for these methods must be consistent with the database equality for the database types to which the key is mapped.
A composite primary key must either be represented and mapped as an embeddable class (see Section 9.1.14, “EmbeddedId Annotation”) or must be represented and mapped to multiple fields or properties of the entity class (see Section 9.1.15, “IdClass Annotation”).
If the composite primary key class is mapped to multiple fields or properties of the entity class, the names of primary key fields or properties in the primary key class and those of the entity class must correspond and their types must be the same.
With an IdClass
The class for the composite primary key could look like (could be a static inner class):
public class TimePK implements Serializable {
protected Integer levelStation;
protected Integer confPathID;
public TimePK() {}
public TimePK(Integer levelStation, String confPathID) {
this.id = levelStation;
this.name = confPathID;
}
// equals, hashCode
}
And the entity:
#Entity
#IdClass(TimePK.class)
class Time implements Serializable {
#Id
private Integer levelStation;
#Id
private Integer confPathID;
private String src;
private String dst;
private Integer distance;
private Integer price;
// getters, setters
}
The IdClass annotation maps multiple fields to the table PK.
With EmbeddedId
The class for the composite primary key could look like (could be a static inner class):
*
#Embeddable
public class TimePK implements Serializable {
protected Integer levelStation;
protected Integer confPathID;
public TimePK() {}
public TimePK(Integer levelStation, String confPathID) {
this.id = levelStation;
this.name = confPathID;
}
// equals, hashCode
}
*
And the entity:
#Entity
class Time implements Serializable {
#EmbeddedId
private TimePK timePK;
private String src;
private String dst;
private Integer distance;
private Integer price;
//...
}
The #EmbeddedId annotation maps a PK class to table PK.
Differences:
From the physical model point of view, there are no differences
EmbeddedId somehow communicates more clearly that the key is a composite key and IMO makes sense when the combined pk is either a meaningful entity itself or it reused in your code.
#IdClass is useful to specify that some combination of fields is unique but these do not have a special meaning.
They also affect the way you write queries (making them more or less verbose):
with IdClass
select t.levelStation from Time t
with EmbeddedId
select t.timePK.levelStation from Time t

how to handle Set of not yet persisted entitys? Java JPA

i have two tables mapped by JPA with One to Many relationship. I want to add Set to the Blog entity, but since BlogNodes entry did not persisted yet, they havent Id field so i have nulpointer exception when i try to add second element to Collection. I've tried to use GenerationType.TABLE for id generator, but it doesn't help. Id is still null. Here are my entity classes with some fields ometted.
The Blog.java
#Entity
#Table(name = "t_blog")
public class Blog extends VersionedEntity{
(Identified id generation)
private static final Logger log = LoggerFactory.getLogger(Blog.class);
//#ToDo: pass actual value to serialVersionUID
//private static final long serialVersionUID = 1882566243377237583L;
...
#OneToMany(mappedBy = "parentBlog", fetch = FetchType.LAZY, orphanRemoval=true, cascade = { CascadeType.PERSIST, CascadeType.MERGE})
private Set<BlogNode> blogNodes;
The BlogNode.java
#Entity
#Table(name = "t_blog_node")
public class BlogNode{
/***************************************************************************************/
#TableGenerator(name="tab", initialValue=0, allocationSize=5)
#GeneratedValue(strategy=GenerationType.TABLE, generator="tab")
#Column(name = "id", nullable = false, updatable = false)
#Id
private Long id;
public Long getId() {
return id;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof BlogNode)) return false;
BlogNode that = (BlogNode) o;
return that.id.equals(id);
}
#Override
public int hashCode() {
return id == null ? 0 : id.hashCode();
}
/*************************************************************************************/
#OneToOne
#JoinColumn(name="parent_blog_fk", referencedColumnName="id", nullable = true)
private Blog parentBlog;
Main class
public List<Blog> createBlog(int n){
params.put("BlogName","SampleBlogName");
params.put("BlogAlias","defaultAlias");
params.put("BlogDescription","defaultBlog description");
List<Blog> newBlogs = new ArrayList<Blog>();
while(n-->0){
Blog entry = new Blog();
entry.setBlogName(params.get("BlogName")+n);
entry.setBlogAlias(params.get("BlogAlias")+n);
entry.setBlogDescription(params.get("BlogDescription")+n);
entry = blogDAO.save(entry);
entry.setBlogNodes(createBlogNodes(entry, NUM_OF_NODES));
entry = blogDAO.save(entry);
newBlogs.add(entry);
}
return newBlogs;
}
private Set<BlogNode> createBlogNodes(Blog blog, int numOfNodes) {
params.put("nodeTitle","SamplenodeName");
params.put("nodeAlias","defaultAlias");
params.put("nodeTeaser","default node teaser");
params.put("nodeText","default node text");
Set<BlogNode> nodes = new HashSet<BlogNode>();;
while (numOfNodes-->0){
BlogNode node = new BlogNode();
node.setNodeTitle(params.get("nodeTitle")+numOfNodes);
node.setNodeAlias(params.get("nodeAlias")+numOfNodes);
node.setNodeText(params.get("nodeText")+numOfNodes);
node.setParentBlog(blog);
node.setNodeTeaser(params.get("nodeTeaser")+numOfNodes);
//Exception raises on the second iteration
nodes.add(node);
}
return nodes;
}
Can i beat this the other way, than persist single entitys of BlogNode separately?
You are adding the Node to a plain HashSet. The only way this causes an NPE is if it's coming from the hashCode or equals methods. Again, I'll point you to the Hibernate manual on that subject. In short, those methods should not use the persistent ID for just this reason (among others).

Hibernate failing by prepending fully qualified class name to property name on ManyToMany association

I'm trying to map two objects to each other using a ManyToMany association, but for some reason when I use the mappedBy property, hibernate seems to be getting confused about exactly what I am mapping. The only odd thing about my mapping here is that the association is not done on a primary key field in one of the entries (the field is unique though).
The tables are:
Sequence (
id NUMBER,
reference VARCHAR,
)
Project (
id NUMBER
)
Sequence_Project (
proj_id number references Project(id),
reference varchar references Sequence(reference)
)
The objects look like (annotations are on the getter, put them on fields to condense a bit):
class Sequence {
#Id
private int id;
private String reference;
#ManyToMany(mappedBy="sequences")
private List<Project> projects;
}
And the owning side:
class Project {
#Id
private int id;
#ManyToMany
#JoinTable(name="sequence_project",
joinColumns=#JoinColumn(name="id"),
inverseJoinColumns=#JoinColumn(name="reference",
referencedColumnName="reference"))
private List<Sequence> sequences;
}
This fails with a MappingException:
property-ref [_test_local_entities_Project_sequences] not found on entity [test.local.entities.Project]
It seems to weirdly prepend the fully qualified class name, divided by underscores. How can I avoid this from happening?
EDIT:
I played around with this a bit more. Changing the name of the mappedBy property throws a different exception, namely:
org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: test.local.entities.Project.sequences
So the annotation is processing correctly, but somehow the property reference isn't correctly added to Hibernate's internal configuration.
I have done the same scenario proposed by your question. And, as expected, i get the same exception. Just as complementary task, i have done the same scenario but with one-to-many many-to-one by using a non-primary key as joined column such as reference. I get now
SecondaryTable JoinColumn cannot reference a non primary key
Well, can it be a bug ??? Well, yes (and your workaround works fine (+1)). If you want to use a non-primary key as primary key, you must make sure it is unique. Maybe it explains why Hibernate does not allow to use non-primary key as primary key (Unaware users can get unexpected behaviors).
If you want to use the same mapping, You can split your #ManyToMany relationship into #OneToMany-ManyToOne By using encapsulation, you do not need to worry about your joined class
Project
#Entity
public class Project implements Serializable {
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy="project")
private List<ProjectSequence> projectSequenceList = new ArrayList<ProjectSequence>();
#Transient
private List<Sequence> sequenceList = null;
// getters and setters
public void addSequence(Sequence sequence) {
projectSequenceList.add(new ProjectSequence(new ProjectSequence.ProjectSequenceId(id, sequence.getReference())));
}
public List<Sequence> getSequenceList() {
if(sequenceList != null)
return sequenceList;
sequenceList = new ArrayList<Sequence>();
for (ProjectSequence projectSequence : projectSequenceList)
sequenceList.add(projectSequence.getSequence());
return sequenceList;
}
}
Sequence
#Entity
public class Sequence implements Serializable {
#Id
private Integer id;
private String reference;
#OneToMany(mappedBy="sequence")
private List<ProjectSequence> projectSequenceList = new ArrayList<ProjectSequence>();
#Transient
private List<Project> projectList = null;
// getters and setters
public void addProject(Project project) {
projectSequenceList.add(new ProjectSequence(new ProjectSequence.ProjectSequenceId(project.getId(), reference)));
}
public List<Project> getProjectList() {
if(projectList != null)
return projectList;
projectList = new ArrayList<Project>();
for (ProjectSequence projectSequence : projectSequenceList)
projectList.add(projectSequence.getProject());
return projectList;
}
}
ProjectSequence
#Entity
public class ProjectSequence {
#EmbeddedId
private ProjectSequenceId projectSequenceId;
#ManyToOne
#JoinColumn(name="ID", insertable=false, updatable=false)
private Project project;
#ManyToOne
#JoinColumn(name="REFERENCE", referencedColumnName="REFERENCE", insertable=false, updatable=false)
private Sequence sequence;
public ProjectSequence() {}
public ProjectSequence(ProjectSequenceId projectSequenceId) {
this.projectSequenceId = projectSequenceId;
}
// getters and setters
#Embeddable
public static class ProjectSequenceId implements Serializable {
#Column(name="ID", updatable=false)
private Integer projectId;
#Column(name="REFERENCE", updatable=false)
private String reference;
public ProjectSequenceId() {}
public ProjectSequenceId(Integer projectId, String reference) {
this.projectId = projectId;
this.reference = reference;
}
#Override
public boolean equals(Object o) {
if (!(o instanceof ProjectSequenceId))
return false;
final ProjectSequenceId other = (ProjectSequenceId) o;
return new EqualsBuilder().append(getProjectId(), other.getProjectId())
.append(getReference(), other.getReference())
.isEquals();
}
#Override
public int hashCode() {
return new HashCodeBuilder().append(getProjectId())
.append(getReference())
.hashCode();
}
}
}
I finally figured it out, more or less. I think this is basically a hibernate bug.
edit: I tried to fix it by changing the owning side of the association:
class Sequence {
#Id
private int id;
private String reference;
#ManyToMany
#JoinTable(name="sequence_project",
inverseJoinColumns=#JoinColumn(name="id"),
joinColumns=#JoinColumn(name="reference",
referencedColumnName="reference"))
private List<Project> projects;
}
class Project {
#Id
private int id;
#ManyToMany(mappedBy="projects")
private List<Sequence> sequences;
}
This worked but caused problems elsewhere (see comment). So I gave up and modeled the association as an entity with many-to-one associations in Sequence and Project. I think this is at the very least a documentation/fault handling bug (the exception isn't very pertinent, and the failure mode is just wrong) and will try to report it to the Hibernate devs.
IMHO what you are trying to achieve is not possible with JPA/Hibernate annotations. Unfortunately, the APIDoc of JoinTable is a bit unclear here, but all the examples I found use primary keys when mapping join tables.
We had the same issue like you in a project where we also could not change the legacy database schema. The only viable option there was to dump Hibernate and use MyBatis (http://www.mybatis.org) where you have the full flexibility of native SQL to express more complex join conditions.
I run into this problem a dozen times now and the only workaround i found is doing the configuration of the #JoinTable twice with swapped columns on the other side of the relation:
class Sequence {
#Id
private int id;
private String reference;
#ManyToMany
#JoinTable(
name = "sequence_project",
joinColumns = #JoinColumn(name="reference", referencedColumnName="reference"),
inverseJoinColumns = #JoinColumn(name="id")
)
private List<Project> projects;
}
class Project {
#Id
private int id;
#ManyToMany
#JoinTable(
name = "sequence_project",
joinColumns = #JoinColumn(name="id"),
inverseJoinColumns = #JoinColumn(name="reference", referencedColumnName="reference")
)
private List<Sequence> sequences;
}
I did not yet tried it with a column different from the primary key.

Categories

Resources