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.
Related
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
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 (?, ?)
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.
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.
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).