Hibernate - Insert transient child entities with generated composite key - java

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

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

How to do unidirectional many-to-many relationships with already existing registers?

I'm new to Spring/JPA and I'm trying to create a relationship pretty much like this post from Vlad, but with one difference. My Tags already exist in another table.
So, if I do just like Vlad does in its post, creating a post, adding some tags to it and then persisting it, everything works as expected. I get a register on Post, two on Tag and two on PostTag.
Post newPost = new Post("Title");
newPost.addTag(new Tag("TagName"));
newPost.addTag(new Tag("TagName2"));
this.postRepository.save(newPost);
But, if I try to create a tag and save it before creating a post, I get an error.
Tag tag = new Tag("TagAlreadyCreated");
this.tagRepository.save(tag);
Post newPost = new Post("Title");
newPost.addTag(tag);
this.postRepository.save(newPost);
// Error: detached entity passed to persist: com.***.***.Tag
I get it that I don't want to create the Tag if it already exists and that the detached message means my Tag already has an ID, so I tried to change the CascadeType to MERGE, but then I don't get a register created on PostTag. Code for the classes:
Post
#Entity(name = "Post")
#Table(name = "post")
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostTag> tags = new ArrayList<>();
public Post() {
}
public Post(String title) {
this.title = title;
}
public void addTag(Tag tag) {
PostTag postTag = new PostTag(this, tag);
tags.add(postTag);
}
public void removeTag(Tag tag) {
for (Iterator<PostTag> iterator = tags.iterator();
iterator.hasNext(); ) {
PostTag postTag = iterator.next();
if (postTag.getPost().equals(this) &&
postTag.getTag().equals(tag)) {
iterator.remove();
postTag.setPost(null);
postTag.setTag(null);
}
}
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
Post post = (Post) o;
return Objects.equals(title, post.title);
}
#Override
public int hashCode() {
return Objects.hash(title);
}
public Long getId() {
return id;
}
}
Tag
#Entity(name = "Tag")
#Table(name = "tag")
#NaturalIdCache
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Tag {
#Id
#GeneratedValue
private Long id;
public Long getId() {
return id;
}
#NaturalId
private String name;
public Tag() {
}
public Tag(String name) {
this.name = name;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
Tag tag = (Tag) o;
return Objects.equals(name, tag.name);
}
#Override
public int hashCode() {
return Objects.hash(name);
}
}
PostTag
#Entity(name = "PostTag")
#Table(name = "post_tag")
public class PostTag {
#EmbeddedId
private PostTagId id;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("postId")
private Post post;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("tagId")
private Tag tag;
#Column(name = "created_on")
private Date createdOn = new Date();
private PostTag() {}
public void setPost(Post post) {
this.post = post;
}
public void setTag(Tag tag) {
this.tag = tag;
}
public PostTag(Post post, Tag tag) {
this.post = post;
this.tag = tag;
this.id = new PostTagId(post.getId(), tag.getId());
}
//Getters and setters omitted for brevity
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
PostTag that = (PostTag) o;
return Objects.equals(post, that.post) &&
Objects.equals(tag, that.tag);
}
public Post getPost() {
return post;
}
public Tag getTag() {
return tag;
}
#Override
public int hashCode() {
return Objects.hash(post, tag);
}
}
PostTagId
#Embeddable
public class PostTagId
implements Serializable {
#Column(name = "post_id")
private Long postId;
#Column(name = "tag_id")
private Long tagId;
private PostTagId() {}
public PostTagId(
Long postId,
Long tagId) {
this.postId = postId;
this.tagId = tagId;
}
//Getters omitted for brevity
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass())
return false;
PostTagId that = (PostTagId) o;
return Objects.equals(postId, that.postId) &&
Objects.equals(tagId, that.tagId);
}
#Override
public int hashCode() {
return Objects.hash(postId, tagId);
}
}
I think I found the answer.
First think to know is that if you have open-in-view set to true, Spring will keep the JPA/Hibnernate Session open for the whole lifespan of the request. This means that your code (which is let's say inside a service method) should work without problems. Of course this is not very efficient (see here why):
https://vladmihalcea.com/the-open-session-in-view-anti-pattern/
Now, if you set open-in-view to false, it seems that Spring opens a new Session for each repository call. So a new session for fetching the Tags and then a new session for saving the Post. That is why the Tags entities are detached when saving the Post.
To solve this, you need to annotate your service call with #Transactional. Spring will try to reuse the same session inside a transaction so the entities will not become detached. See here for example:
How does #Transactional influence current session in Hibernate?
The final thing to know which is crucial is that the service methods must be public!
If you have any other visibility on your method the #Transactional is ignored, no errors are thrown:
https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#transaction-declarative-annotations (see Method visibility and #
The spring-data-jpa is a layer on top of JPA. Each entity has its own repository and you have to deal with that. I've seen that tutorial mentioned above and it's for JPA and it's also setting ID's to null which seems off a bit and probably the cause of your error. I didn't look that close. For dealing with the issue in spring-data-jpa you need a separate repository for the link table.
#Entity
public class Post {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostTag> tags;
#Entity
public class Tag {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "tag", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostTag> posts;
#Entity
public class PostTag {
#EmbeddedId
private PostTagId id = new PostTagId();
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("postId")
private Post post;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("tagId")
private Tag tag;
public PostTag() {}
public PostTag(Post post, Tag tag) {
this.post = post;
this.tag = tag;
}
#SuppressWarnings("serial")
#Embeddable
public class PostTagId implements Serializable {
private Long postId;
private Long tagId;
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
PostTagId that = (PostTagId) o;
return Objects.equals(postId, that.postId) && Objects.equals(tagId, that.tagId);
}
#Override
public int hashCode() {
return Objects.hash(postId, tagId);
}
And to use it, as show above:
#Transactional
private void update() {
System.out.println("Step 1");
Tag tag1 = new Tag();
Post post1 = new Post();
PostTag p1t1 = new PostTag(post1, tag1);
tagRepo.save(tag1);
postRepo.save(post1);
postTagRepo.save(p1t1);
System.out.println("Step 2");
Tag tag2 = new Tag();
Post post2 = new Post();
PostTag p2t2 = new PostTag(post2, tag2);
postRepo.save(post2);
tagRepo.save(tag2);
postTagRepo.save(p2t2);
System.out.println("Step 3");
tag2 = tagRepo.getOneWithPosts(2L);
tag2.getPosts().add(new PostTag(post1, tag2));
tagRepo.save(tag2);
System.out.println("Step 4 -- better");
PostTag p2t1 = new PostTag(post2, tag1);
postTagRepo.save(p2t1);
}
Note there are few changes. I don't explicitly set the PostTagId id's. These are handled by the persistence layer (hibernate in this case).
Note also that you can update PostTag entries either explicity with its own repo or by adding and removing them from the list since CascadeType.ALL is set, as shown. The problem with using the CascadeType.ALL for spring-data-jpa is that even though you prefetch the join table entities spring-data-jpa will do it again anyway. Trying to update the relationship through the CascadeType.ALL for new entities is problematic.
Without the CascadeType neither the posts or tags lists (which should be Sets) are the owners of the relationship so adding to them wouldn't accomplish anything in terms of persistence and would be for query results only.
When reading the PostTag relationships you need to specifically fetch them since you don't have FetchType.EAGER. The problem with FetchType.EAGER is the overhead if you don't want the joins and also if you put it on both Tag and Post then you will create a recursive fetch that gets all Tags and Posts for any query.
#Query("select t from Tag t left outer join fetch t.posts tps left outer join fetch tps.post where t.id = :id")
Tag getOneWithPosts(#Param("id") Long id);
Finally, always check the logs. Note that creating an association requires spring-data-jpa (and I think JPA) to read the existing table to see if the relationship is new or updated. This happens whether you create and save a PostTag yourself or even if you prefetched the list. JPA has a separate merge and I think you can use that more efficiently.
create table post (id bigint generated by default as identity, primary key (id))
create table post_tag (post_id bigint not null, tag_id bigint not null, primary key (post_id, tag_id))
create table tag (id bigint generated by default as identity, primary key (id))
alter table post_tag add constraint FKc2auetuvsec0k566l0eyvr9cs foreign key (post_id) references post
alter table post_tag add constraint FKac1wdchd2pnur3fl225obmlg0 foreign key (tag_id) references tag
Step 1
insert into tag (id) values (null)
insert into post (id) values (null)
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)
Step 2
insert into post (id) values (null)
insert into tag (id) values (null)
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)
Step 3
select tag0_.id as id1_2_0_, posts1_.post_id as post_id1_1_1_, posts1_.tag_id as tag_id2_1_1_, post2_.id as id1_0_2_, posts1_.tag_id as tag_id2_1_0__, posts1_.post_id as post_id1_1_0__ from tag tag0_ left outer join post_tag posts1_ on tag0_.id=posts1_.tag_id left outer join post post2_ on posts1_.post_id=post2_.id where tag0_.id=?
select tag0_.id as id1_2_1_, posts1_.tag_id as tag_id2_1_3_, posts1_.post_id as post_id1_1_3_, posts1_.post_id as post_id1_1_0_, posts1_.tag_id as tag_id2_1_0_ from tag tag0_ left outer join post_tag posts1_ on tag0_.id=posts1_.tag_id where tag0_.id=?
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)
Step 4 -- better
select posttag0_.post_id as post_id1_1_0_, posttag0_.tag_id as tag_id2_1_0_ from post_tag posttag0_ where posttag0_.post_id=? and posttag0_.tag_id=?
insert into post_tag (post_id, tag_id) values (?, ?)

Recursive look up parent entity's value

I have a category entity which uses a hierarchical structure, each entity can have a parent or children.
The entity holds "enabled" value, if marked as false, all child entities should be marked as "false" too.
I created a simple recursion method as below to achieve this, but it gives me very strange results (by strange I mean the current entity always returns the correct value for "enabled" however, all parent entities default to "false"
Here is the code snippet:
#Entity
#Table(name = "category")
#SQLDelete(sql ="UPDATE category SET active = 0 WHERE pk = ? AND version = ?")
#Where(clause = "active = 1")
public class CategoryEntity extends AbstractEntity{
private static final long serialVersionUID = -2285380147654080016L;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_category")
private CategoryEntity parentCategory;
#OneToMany(fetch = FetchType.LAZY, mappedBy="parentCategory", cascade = CascadeType.ALL)
private List<CategoryEntity> childCategories = new ArrayList<CategoryEntity>();
#Column(name = "source_id", unique=true)
private String sourceId;
#Column(name = "enabled", nullable = false, columnDefinition = "boolean default true")
private boolean enabled;
public CategoryEntity getParentCategory() {
return parentCategory;
}
public void setParentCategory(CategoryEntity parentCategory) {
this.parentCategory = parentCategory;
}
public List<CategoryEntity> getChildCategories() {
return childCategories;
}
public void setChildCategories(List<CategoryEntity> childCategories) {
this.childCategories = childCategories;
}
public boolean getEnabled() {
return isEnabled(this);
}
private boolean isEnabled(CategoryEntity cat){
if(cat != null && cat.enabled){
if(cat.getParentCategory() == null) return true;
return isEnabled(cat.getParentCategory());
}
return false;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
The culprit is the isEnabled() method, hopefully you guys can help here? Maybe a better question would be, whether this is even legal in JPA?
I think the reason behind that is that you use the 'enabled' property directly in the parent objects. It is lazy loaded object and it is not initialized yet (when you access the parent). And when you access that parent category object it is basically a proxy to the object with all primitives set to their default values (for enabled it would be false).
Try changing it to:
public boolean getEnabled() {
return this.enabled;
}
public boolean getCalculatedEnabled() {
return getCalculatedEnabled(this);
}
private boolean getCalculatedEnabled(CategoryEntity cat){
if(cat != null && cat.getEnabled()){ //this will trigger the lazy initialization of the object
if(cat.getParentCategory() == null) return true;
return getCalculatedEnabled(cat.getParentCategory());
}
return false;
}
You will have two fields like (enabled and calculatedEnabled) but still it won't break the java bean convention and it will hopefully work ;)
In your method isEnabled your are returning true if the entity has a parent, else false. Hence all children will be true and the ones without parent will be false.

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