How to define the composite primary keys? - java

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

Related

What is the best practise to model a one-field-entity using Hibernate and JPA?

In my project I have Driver->Component one-to-many relationship which I successfully mapped to my PostgreSQL database. But now I realized both Driver and Component entities have only one "meaninful business" property — String name, and now my project looks overengineered, for example:
#Entity(name = "Component")
#Table(name = "Components", schema = "poll_db")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Component {
#Id
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "comp_seq"
)
#SequenceGenerator(
name = "comp_seq",
allocationSize = 1,
schema = "poll_db"
)
private Long id;
#Column(
name = "name",
unique = true
)
#NotBlank
private String name;
#ManyToOne
#JoinColumn(name = "driver_id")
#NotNull
private Driver driver;
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Component other))
return false;
return id != null &&
id.equals(other.getId());
}
#Override
public int hashCode() {
return getClass().hashCode();
}
}
Does this look clean or there is any other reasonable implementation of such ORM?
For example, I could use ENUM for Component and class for Driver.
Or make String name #NaturalId and get rid of numerical #Id . Or just keep components as a List attribute in Driver entity.
What stops me from mentioned steps, is the fact that both entities are used in other parts of the project, such as Poll->Driver many-to-one relationship. How would you design these two entities?

Hibernate ManyToOne n+1 select with id

I am facing a weird problem with n+1 select queries. My mapping looks like that:
#Entity
#IdClass(MyTablePK.class)
#Table(name = "my_table", schema = "schema")
public class MyTable {
#Id
#Column(name = "name", nullable = false, length = 12)
private String name="";
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "myStringValue", referencedColumnName = "myStringValue")
private AdditionalData data;
... (other fields, getters, setters)
}
public class MyTablePK implements Serializable {
private String name;
private AdditionalData data;
(getters,setters)
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyTablePK that = (MyTablePK) o;
if (name!= null ? !name.equals(that.name) : that.name!= null) return false;
return !(data!= null ? !data.equals(that.data) : that.data!= null);
}
#Override
public int hashCode() {
int result = name!= null ? name.hashCode() : 0;
result = 31 * result + (data!= null ? data.hashCode() : 0);
return result;
}
}
#Entity
#Table(name = "info", schema = "schema")
public class AdditionalData implements Serializable {
#Id
#Column(name = "recno")
private Long recno;
#Column(name = "info1", length = 3)
private String info1;
#Column(name = "info2", length = 3)
private String info2;
... (getters, setters)
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AdditionalData data = (AdditionalData) o;
return recno.equals(data.recno);
}
#Override
public int hashCode() {
return recno.hashCode();
}
}
Now, I select all the values from MyTable. As expected I get n+1 selects, for every MyTable row a new AdditionalData query arrives. In order to fight that I wrote a join fetch query:
FROM MyTable mytab join fetch mytab.data
That however... did not change anything.
Now, the interesting thing is, that if I ignore for a moment business requirements, and remove #IdClass making name the only #Id - everything works correctly, all the data is got with a single query. Why is that? Can't I fight n+1 selects with a part of composite id?
In case it's relevant - I use Hibernate 4.3.5.Final with Oracle database
This might be related to this known issue here: https://hibernate.atlassian.net/browse/HHH-10292
Try to map the myStringValue column twice. Once as being part of the id and as String and another time as AdditionalData with insertable = false, updatable = false in the join column.

Hibernate - Insert transient child entities with generated composite key

I am having trouble using Hibernate to insert a bunch of transient child entities which may have composite keys composed of other transient child entities by saving the detached parent entity. I am pretty sure I have my composite key class set up properly, but every time I try to save the parent entity which has transient entities (no generated ID yet), I get this error:
org.hibernate.id.IdentifierGenerationException: null id generated for:class org._.website.data.entity.Mean
So Hibernate never generates the Composite key which I figured it should be able to given the properties being referenced. However, since the properties that the composite key is referencing are also transient, there is no ID to manually set the composite key with. So I was hoping that Hibernate would be smart enough to do the generation itself.
Is there a way to get Hibernate to handle saving/inserting of transient child entities with composite keys which reference other transient child entities?
Here is the code I am working with. If fails on projectDao.save(project);
Variable variable = new Variable();
variable.setProject(project);
variable.setName("x");
Belief belief = new Belief();
belief.setProject(project);
belief.setName("model-1");
Mean mean = new Mean();
mean.setVariable(variable);
mean.setBelief(belief);
// I can't do this because variable and belief are transient and have no ID yet
//MeanPK meanPk = new MeanPK(variableId, beliefId);
//mean.setPk(meanPk);
belief.getMeans().add(mean);
project.getVariables().add(variable);
project.getBeliefs().add(belief);
projectDao.save(project);
If it helps, here is the Embeddable MeanPK class
#Embeddable
public static class MeanPK implements Serializable {
private static final long serialVersionUID = 341373316515655834L;
#GeneratedValue
#Column(name = "belief_id", nullable = false, updatable = false)
protected Integer beliefId;
#GeneratedValue
#Column(name = "variable_id", nullable = false, updatable = false)
protected Integer variableId;
// getters/setters excluded for brevity
#Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof MeanPK)) {
return false;
}
MeanPK other = (MeanPK) obj;
return beliefId.equals(other.beliefId) && variableId.equals(other.variableId);
}
#Override
public int hashCode() {
return new HashCodeBuilder().append(beliefId).append(variableId).toHashCode();
}
}
If I absolutely have to, I can save the transient entities referenced by the composite key first to get the IDs and manually construct the MeanPK composite key, but I was hoping that Hibernate was able to handle that on its own with a single call to projectDao.save(...);
Thanks for your help!
I figured out the answer to my question and I thought I would post it in case anyone found it useful.
What I did was store the referenced Variable and Belief entities in the MeanPK class itself when they are set to the Mean entity. I added some logic to the ID getters in the MeanPk class so that when they are called by hibernate, it will first check to set the ids from the objects stored in the MeanPK class. This works because hibernate will insert and persist the transient Variable and Belief entities before it gets to the Mean entity since it is the bottom-most child. And I have CascadeType.ALL for all my collections, so I don't need to worry about manually saving each entity, and Hibernate will cascade the save operation from parent to child.
Here is the updated MeanPK class and Mean entity class:
#Entity
#Table(name = "mean")
public class Mean implements Serializable {
private static final long serialVersionUID = -5732898358425089380L;
// composite key
#EmbeddedId
private MeanPK pk = new MeanPK();
#ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH })
#JoinColumn(name = "belief_id", insertable = false, nullable = false, updatable = false)
private Belief belief;
#ManyToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH })
#JoinColumn(name = "variable_id", insertable = false, nullable = false, updatable = false)
private Variable variable;
// more attributes excluded
public MeanPK getPk() {
return pk;
}
protected void setPk(MeanPK pk) {
this.pk = pk;
}
public Belief getBelief() {
return belief;
}
public void setBelief(Belief belief) {
pk.setBelief(this.belief = belief);
}
#XmlTransient
public Variable getVariable() {
return variable;
}
public void setVariable(Variable variable) {
pk.setVariable(this.variable = variable);
}
#Embeddable
public static class MeanPK implements Serializable {
private static final long serialVersionUID = 341373316515655834L;
#Access(AccessType.PROPERTY)
#Column(name = "belief_id", nullable = false, updatable = false)
protected Integer beliefId;
#Access(AccessType.PROPERTY)
#Column(name = "variable_id", nullable = false, updatable = false)
protected Integer variableId;
#Transient
private Belief belief;
#Transient
private Variable variable;
public Integer getBeliefId() {
if (beliefId == null && belief != null) {
beliefId = belief.getId();
}
return beliefId;
}
protected void setBeliefId(Integer beliefId) {
this.beliefId = beliefId;
}
public Belief getBelief() {
return belief;
}
void setBelief(Belief belief) {
this.belief = belief;
if (belief != null) {
beliefId = belief.getId();
}
}
public Integer getVariableId() {
if (variableId == null && variable != null) {
variableId = variable.getId();
}
return variableId;
}
protected void setVariableId(Integer variableId) {
this.variableId = variableId;
}
public Variable getVariable() {
return variable;
}
void setVariable(Variable variable) {
this.variable = variable;
if (variable != null) {
variableId = variable.getId();
}
}
#Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof MeanPK)) {
return false;
}
MeanPK other = (MeanPK) obj;
return getBeliefId().equals(other.getBeliefId()) && getVariableId().equals(other.getVariableId());
}
#Override
public int hashCode() {
return new HashCodeBuilder().append(getBeliefId()).append(getVariableId()).toHashCode();
}
}
}
look into using cascading annotation on your entity classes... The save operation will try to save project before Mean has an ID. Try doing projectDao.save(mean); first or use cascade annotation on a Mean collection on the Project class....
like so...
Mean mean = new Mean();
mean.setVariable(variable);
mean.setBelief(belief);
**projectDao.save(mean);** //first option
// I can't do this because variable and belief are transient and have no ID yet
//MeanPK meanPk = new MeanPK(variableId, beliefId);
//mean.setPk(meanPk);
belief.getMeans().add(mean);
project.getVariables().add(variable);
project.getBeliefs().add(belief);
projectDao.save(project);
//second option
//or in your method declaration section in your Project class remove
getVariables().add(variable) &
getBeliefs().add(belief)
//as well as their associated variable declarations and add
// mappedBy foreign key constraint meanId
#OneToMany(cascade = CascadeType.ALL, mappedBy = "meanId")
//add to variable declarations section
private Collection<Means> meansCollection;
//and add the following method under getter/setter section
#XmlTransient
public Collection<Mean> getMeansCollection() {
return meansCollection;
}
//In the Project class constructor do the following initialization of the MeanCollection
meansCollection = new ArrayList();
//now your call simply becomes
Mean mean = new Mean();
mean.setVariable(variable);
mean.setBelief(belief);
Project project = new Project();
project.getMeansCollection().add(means);
projectDao.save(project);
// Also it looks like you should be using #JoinColumns for the variable_id &
// belief_id fields where each variable is actually a class variable
// representation and not an Integer. In this case you will have mean_id as
// the single primary key and class Variable & Belief each as a #JoinColumn
// foreign key constraint
//4 spaces
Yeah its #Transient
so your making your call like so?
MeanPK meanPk = new MeanPK(variableId, beliefId);
does this work for you when making this call
Mean mean = new Mean(variableId, beliefId);

saving references to other entities

In a spring mvc application using hibernate over a MySQL database, I have a Document entity with each document object containing different types of codes that are all instances of a Code entity. The Code entity contains a distinct list of possible values that are defined by a combination of code.pk.code and code.pk.codesystem, with pk being a reference to a composite primary key. And each code object might be referenced by many document objects.
How do I map this in hibernate and MySQL?
The way I have things laid out now, I keep getting errors about violating primary key constraints for Code every time I try to store a Document containing any code that is already stored in the codes table of the underlying MySQL database. Specifically, when my dao runs the following line of code:
this.entitymanager.persist(document)
The following error is immediately thrown:
Hibernate: insert into codes (displayname, code, codesystem) values (?, ?, ?)
WARN SqlExceptionHelper - SQL Error: 1062, SQLState: 23000
ERROR SqlExceptionHelper - Duplicate entry 'somecode-somecodesystem' for key 'PRIMARY'
The program crashes at that point, so I don't know if it would otherwise next save the document to the documents table in the database. Why does it try to save to the codes table? Is there some way I can set things up so that it just saves the references to the code to the documents table? It would be a nuisance to remove the relationship and just store the keys as strings in the documents table without joining codes to document.
Here is my java:
#Entity
#Table(name = "documents")
public class Document {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Integer id;
#ManyToOne(cascade = {CascadeType.ALL}, fetch=FetchType.EAGER)
#JoinColumns({ #JoinColumn(name = "confidentiality_code", referencedColumnName = "code"),
#JoinColumn(name = "confidentiality_codesystem", referencedColumnName = "codesystem"),
})
public Code conftype;
//other stuff, then getters and setters
}
#Entity
#Table(name = "codes")
public class Code implements Serializable{
private static final long serialVersionUID = 1L;
#EmbeddedId
public EmbedCodePK codePk;
#Column(name="displayname")
private String displayname;
public EmbedCodePK getCodePk(){return codePk;}
public void setCodePk(EmbedCodePK mypk){codePk = mypk;}
public String getDisplayname(){return displayname;}
public void setDisplayname(String dspnm){displayname=dspnm;}
}
#Embeddable
public class EmbedCodePK implements Serializable {
private static final long serialVersionUID = 652312726451130179L;
#Column(name="code", nullable=false)
public String code;
#Column(name="codesystem", nullable=false)
private String codesystem;
public EmbedCodePK() {}
public EmbedCodePK(String c, String cs) {
this.code = c;
this.codesystem = cs;
}
/** getters and setters **/
public String getCode(){return code;}
public void setCode(String c){code=c;}
public void setCodesystem(String cs) {this.codesystem = cs;}
public String getCodesystem() {return codesystem;}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
final EmbedCodePK other = (EmbedCodePK) obj;
if (code == null) {
if (other.code != null) return false;
} else if (!code.equals(other.code)) return false;
if (codesystem == null) {
if (other.codesystem != null) return false;
} else if (!codesystem.equals(other.codesystem)) return false;
return true;
}
#Override
public int hashCode() {
int hash = 3;
hash = 53 * hash + ((code == null) ? 0 : code.hashCode());
hash = 53 * hash + ((codesystem == null) ? 0 : codesystem.hashCode());
return hash;
}
}
The solution was to remove the CascadeType.ALL from the ManyToOne relationship, so that it now looks as follows:
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumns({ #JoinColumn(name = "confidentiality_code", referencedColumnName = "code"),
#JoinColumn(name = "confidentiality_codesystem", referencedColumnName = "codesystem"),
})
public Code conftype;
This problem has now been successfully resolved. Question answered.

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

Categories

Resources