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.
Related
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 access a EJB dao in an ejb container from a backing sessionscoped cdi bean. The dao execute a JQL query with join fetch and retrieve a entity with a #OneToMany reference. The #OneToMany collections are filled and I can use them inside EJB, but in the CDI backing bean, the collections are empty and cleared. My entities look like this:
#Entity
#NamedQuery(name = "order.with.items",
query = "select o from Order o inner join fetch o.item i where o.id=:orderNo")
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Version
#Column(name = "version")
private int version;
#Column
private String name;
#OneToMany(mappedBy = "order")
private Set<Item> item = new HashSet<>();;
... getters setters
}
and the referenced item's:
#Entity
public class Item implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Version
#Column(name = "version")
private int version;
#Column
private String name;
#ManyToOne
#JoinColumn(name = "order_id", referencedColumnName = "id")
private Order order;
//... getters setters
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass()) // UPDATE: don't do it
return false; // with getClass - use instanceof
Item other = (Item) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (order == null) {
if (other.order != null)
return false;
} else if (!order.equals(other.order))
return false;
return true;
}
}
and the dao:
#Stateless
#LocalBean
public class OrderDao {
#PersistenceContext(unitName = "jpa-persistence-unit")
protected EntityManager entityManager;
public Order getOrderWhithItems(Long orderId) {
Order order = entityManager.createNamedQuery("order.with.items",Order.class).setParameter("orderId", orderId).getSingleResult();
// Here is the size greater than zero
System.out.println("# of items: " + order.getItem().size());
return order;
}
}
and the backing bean:
#Named
#SessionScoped
public class BackingBean {
#EJB
private OrderDao orderDao;
public BackingBean() {
Order order = orderDao.getOrderWhithItems( 4L);
Set<Item> items = order.getItem();
// This will ouputs 0
System.out.println("# of items " + items.size());
}
}
The problem is, that the order contain all items in the method of the dao, but when the backing bean receive the order, the items are ripped and the set is empty. I wrote also an arquillian junit test to test the dao in it and it work perfectly and the order contain the items in the test. But not in the CDI session bean. When I extract the set in the dao like a DTO (data transfer object) I can receive the items in the backing bean.
The dao bean is in an ejb jar, in an ear enterprise archive. The CDI backing bean is in a war archive in the same ear. I simplified our problem case to the order item example. I could not find any resources of this stupid behavior. I use a wildfly 13 appserver and use the hibernate jpa orm.
The cause of that problem is a overwritten equals method. Hibernate us a big caching and proxiing mechanism and every Entity Class is handled by a proxy. The problem are the following test in the equals method:
if (getClass() != obj.getClass()) // This wouldn't work in JPA!!!
return false;
In the case of an entity these lines always return false, because obj has the type of the proxy class and not of the entity. The obj is stored in a set and the set is accessed by the proxy, while caching and so on.
Use never getClass in a equals method of an entity, use always the instanceof operator. In my case, this wrong implementation causes unpredictable behaviour while the instance of the entity travels from the dao to the backing bean.
The equals method should looks like:
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Item)) {
return false;
}
Item other = (Item) obj;
if (id != null) {
if (!id.equals(other.id)) {
return false;
}
}
return true;
}
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.
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);
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).