I have the following use case:
Parents contain Children and ParentTags.
Children contain ChildrenTags and their Parent node.
Both ParentTags and ChildrenTags are Tags with T being Parent or Child.
Now I want a derived field in Parent that map all tags relative to this parent or to one of its children.
The SQL query is straightforward but I can't make it work by annotating a given property.
Note that the #ManyToOne mapping works like a charm in order to find the Parent owner of a tag, cf my code below (this is a #OneToMany relation, Tag is maybe not the best name I could find for this example).
Parent.class
#Entity
#Audited
public class Parent {
#Id
private Long id;
#OneToMany(mappedBy = "parent")
private List<Child> children;
#OneToMany(mappedBy = "parent")
private List<ParentTag> tags;
// Does not work!
#OneToMany(mappedBy = "owner")
private List<Tag<?>> allTags;
}
Child.class
#Entity
#Audited
public class Child {
#Id
private Long id;
#ManyToOne
private Parent parent;
#OneToMany(mappedBy = "child")
private List<ChildTag> tags;
}
Tag.class
#Entity
#Audited
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Tag<T> {
#Id
private Long id;
protected String value;
#NotAudited // otherwise ClassCastException
#ManyToOne
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula = #JoinFormula(
value = "CASE WHEN parent_id IS NOT NULL THEN parent_id WHEN child_id IS NOT NULL THEN (SELECT child.parent_id FROM Child child WHERE child.id = child_id) end",
referencedColumnName="id"))
})
private Parent owner;
public abstract T getNode();
}
ChildTag.class
#Audited
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class ChildTag extends Tag<Child> {
#ManyToOne
private Child child;
#Override
public Child getNode() {
return child;
}
}
ParentTag.class
#Audited
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class ParentTag extends Tag<Parent> {
#ManyToOne
private Parent parent;
#Override
public Parent getNode() {
return parent;
}
}
I'd like to find a way to load allTags by a given query or formula.
The inverse mapping #ManyToOne works (as long as owner is not audited, it was hard to find that bug).
I already tried the following solutions without success:
#JoinFormula
#JoinColumnsOrFormulas({
#JoinColumnOrFormula(formula = #JoinFormula(
value = "CASE WHEN parent_id IS NOT NULL THEN parent_id WHEN child_id IS NOT NULL THEN (SELECT child.parent_id FROM Child child WHERE child.id = child_id) end",
referencedColumnName="id"))
})
private List<Tag<?>> allTags;
I get a ClassCastException, Formula cannot be cast into a Column
#Loader
#Entity
#Audited
#NamedNativeQuery(name = "loadAllTags",
query = "SELECT * FROM Tag tag WHERE "
+ "(tag.parent_id IS NOT NULL AND tag.parent_id = :id) OR "
+ "(tag.child_id IS NOT NULL AND EXISTS (SELECT 1 FROM Child child WHERE child.id = tag.child_id AND child.parent_id = :id))")
public class Parent {
...
#Loader(namedQuery = "loadAllTags")
private List<Tag<?>> allTags;
}
No exception, actually when debugging I can see that the loader find all appropriate tags, but does not initialize the collection with them.
I also tried to put the query in a hbm configuration file, as I understood that #Loader and #NamedNativeQuery do not get along well, but without success (maybe I did something wrong here). However I'd rather implement a solution without configuration file.
I could add another column (owner_id) but if I can find a way to solve that problem without changing the model, it would be better.
I don't know if generics have something to do with it. In addition my entities are audited and indexed.
Any help will be greatly appreciated!
Related
I have an "Organization" class that has "Organization parent" field. It's fetch type is lazy and relation is Many-To-One;
#Entity
#Table(name = "organization", uniqueConstraints = {#UniqueConstraint(columnNames = {"name",
"code"})})
public class Organization extends BaseDomain {
private String name;
private String baseUrlBackend;
private String baseUrlFrontend;
private String code;
private Organization parent;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
public Organization getParent() {
return parent;
}
.
.
.
when i am trying to get organization with it's id i'm getting this error;
as i can understand i am having this trouble because;
"Organization parent" is nullable but an "Organization" class has to have an "id" value.
But if there is no parent, there can't be any id value neither. Is there an annotation or config file to solve that problem.
So far i tried some solutions. When i change the FetchType.LAZY to FetchType.EAGER problem is solved but i don't want to change the FetchType so im looking for different solutions.
I have Parent entity Head and associated childs are Detail and Comment mapped using onetomany. I have only one DAO which is HeadDao. ProjectNumber PK in Head and FK in Detail and Comment. I'm trying to figure out how to delete the Child Comment using the projectNumber without writing the DAO for the child.
Scenario is, while updating the Head i want to first delete the existing (associated) Comments from the database (using the projectNumber) and then add the new comments which are coming in the request. Could any one help me with this.
Below are Entity classes (FYI Comment class has composite id, but i not pasted it here)
#Entity(name = "Head")
#Table(name = "HEAD")
public class Head {
#Id
#Column(name = "PRJ_NBR")
private Integer projNumber;
#JsonIgnore
#OneToMany(mappedBy = "head",cascade = CascadeType.ALL,orphanRemoval =
true,fetch = FetchType.LAZY)
private List<Detail> detailsList = new ArrayList<Detail>();
#JsonIgnore
#OneToMany(mappedBy = "head",cascade = CascadeType.ALL,orphanRemoval =
true,fetch = FetchType.LAZY)
private List<Comment> commentsList = new ArrayList<Comment>();
}
#Entity
#Table(name = "PRJ_CMT")
#JsonIgnoreProperties
public class Comment {
#Transient
private Integer projectNumber;
#JsonIgnore
#EmbeddedId
private CommentCompositeId id;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "PRJ_NBR", updatable=false, insertable=false)
private Head head;
}
#Transactional
#Repository
#PersistenceContext(type = PersistenceContextType.EXTENDED)
public abstract interface HeadDao extends JpaRepository<Head,
Serializable>
{
public abstract List<Head> findByProjNumber(Integer paramInteger);
public abstract List<Head> findAll();
#SuppressWarnings("unchecked")
public abstract Head saveAndFlush(Head paramHead);
public abstract List<Head> findByCusSysId(Integer paramInteger);
public abstract Integer deleteByProjNumber(Integer projNumber);
}
While adding i'm using below code
this.headDao.saveAndFlush(head);
Solved the problem by adding cascade = CascadeType.PERSIST to the ManytoOne (comment) side (cascade all should be present on the onetomany - head).
Now when i'm adding the list of comments, its deleting the comments (for the project number) which are not present the current list and updating other comments
How about simply by replacing commentsList for existing head ?
Create setter for head.commentsList.
Replace comments with newComments = new ArrayList<Comment>() that is populated with the new comments, then head.setCommentsList(newComments).
Persist head, this.headDao.saveAndFlush(head);.
Working with JPA 1 (hibernate-core version 3.3.0.SP1 and hibernate-entitymanager version 3.4.0.GA) :
I've some entities similar to those defined below, where ChildOne and ChildTwo extends from the Father entity.
#Entity
#Table(name = "TABLE_FATHER")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.INTEGER, name = Father.C_ID_CTG)
public class Father {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sq")
#Column(name = "ID_PK", nullable = false)
#BusinessId
private Long id;
...
}
#Entity
#Table(name = "TABLE_CHILD_ONE")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorValue(Categories.ID_CTG_ONE)
public class ChildOne extends Father {
...
}
#Entity
#Table(name = "TABLE_CHILD_TWO")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorValue(Categories.ID_CTG_TWO)
public class ChildTwo extends Element {
...
}
Let's say I've one entity having a Father element, and another having a collection of father elements. In both cases, should go the children entities.
#Entity
#Table(name = "TABLE_ONE")
public class OneTable {
#JoinColumn(name = "ID_PK", referencedColumnName = "ID_PK", nullable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private Father element;
...
}
#Entity
#Table(name = "TABLE_ANOTHER")
public class Another {
#Fetch(FetchMode.JOIN)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "id", fetch = FetchType.LAZY)
private Collection<Father> elementCollection;
...
}
I'm expecting to obtain always the children elements but when I get the element getElement() returns the father element
and, on the other hand, when I get the collection getElementCollection() the children elements are coming.
Apparently, the #JoinColumn is the cause of this behaviour, doing the join with the father table and forgetting the children elements.
The collection is working as expected.
How could I get the children element with a getElement() call? Any ideas or workarround?
Thanks in advance.
The problem is not caused by #JoinColumn.
The reason is Lazy Loading.
I manage to pinpoint your problem in simpler example.
Forgive me for changing convention from Father to Parent.
In the example below, uninitialized Element is type of jpa.inheritance.issue.Parent_$$_javassist_1. It is a Hibernate Proxy - dynamically created subclass of Parent.
You can "unproxy" it by invoking Hibernate proprietary API getHibernateLazyInitializer().getImplementation().
Collection of elementCollection is also Lazy Initialized. The type of the collection is org.hibernate.collection.PersistentBag which is being initilized with correct data at the time of first access.
Collection is initialized all at once.
Please see the test which successfully passed green with your exact version of Hibernate (3.3.0.SP1/3.4.0.GA).
#Test
public void test() {
Child c = new Child();
em.persist(c);
Another a = new Another();
a.setElement(c);
Collection<Parent> col = new ArrayList<Parent>();
col.add(c);
a.setElementCollection(col);
em.persist(a);
c.setAnother(a);
long idx = a.getId();
tx.commit();
// I'm cleaning the cache to be sure that call to a.getElement() will return proxy.
em.clear();
tx = em.getTransaction();
tx.begin();
a = em.find(Another.class, idx);
Assert.assertNotNull(a);
Parent p = a.getElement();
// At this point p is a type of jpa.inheritance.issue.Parent_$$_javassist_1
Assert.assertTrue(p instanceof Parent);
Assert.assertFalse(p instanceof Child);
// At this point a.elements is a not initialized (empty) collection of type org.hibernate.collection.PersistentBag
// When we access this collection for the first time, records are read from the database
Assert.assertEquals(1, a.getElementCollection().size());
if (p instanceof HibernateProxy) {
p =
(Parent) ((HibernateProxy) p).getHibernateLazyInitializer()
.getImplementation();
}
// At this point p is a type of jpa.inheritance.issue.Child
Assert.assertTrue(p instanceof Child);
}
#Entity
public class Another {
#JoinColumn(name = "element_id", referencedColumnName = "id", nullable = false)
#ManyToOne(fetch=FetchType.LAZY)
private Parent element;
public Parent getElement() {
return element;
}
public void setElement(Parent element) {
this.element = element;
}
#OneToMany(cascade = CascadeType.ALL, mappedBy = "another", fetch = FetchType.LAZY)
public Collection<Parent> elements;
public Collection<Parent> getElementCollection() {
return elements;
}
public void setElementCollection(Collection<Parent> elementCollection) {
this.elements = elementCollection;
}
// #Id ...
}
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Parent {
#ManyToOne
private Another another;
public Another getAnother() {
return another;
}
public void setAnother(Another another) {
this.another = another;
}
// #Id ...
}
#Entity
public class Child extends Parent {
}
You don't need #DiscriminatorColumn nor #DiscriminatorValue because those annotations are needed with InheritanceType.SINGLE_TABLE as an only resort to determine the type.
With InheritanceType.JOINED Hibernate is able to determine polymorphic type by checking if there is a record in both (Parent and Child) tables with the same Id.
You can turn on hibernate logging to see how the query to determine the type looks like. It works like this:
select
another0_.id as id0_1_,
another0_.element_id as element2_0_1_,
parent1_.id as id1_0_,
parent1_1_.name as name2_0_,
case
when parent1_1_.id is not null then 1
when parent1_.id is not null then 0
else -1
end as clazz_0_
from
Another another0_
inner join
Parent parent1_
on another0_.element_id=parent1_.id
left outer join
Child parent1_1_
on parent1_.id=parent1_1_.id
where
another0_.id=?
I am trying to configure this #OneToMany and #ManyToOne relationship but it's simply not working, not sure why. I have done this before on other projects but somehow it's not working with my current configuration, here's the code:
public class Parent {
#OneToMany(mappedBy = "ex", fetch= FetchType.LAZY, cascade=CascadeType.ALL)
private List<Child> myChilds;
public List<Child> getMyChilds() {
return myChilds;
}
}
public class Child {
#Id
#ManyToOne(fetch=FetchType.LAZY)
private Parent ex;
#Id
private String a;
#Id
private String b;
public Parent getParent(){
return ex;
}
}
At first, I thought it could be the triple #Id annotation that was causing the malfunction, but after removing the annotations it still doesn't work. So, if anyone have any idea, I am using EclipseLink 2.0.
I just try to execute the code with some records and it returns s==0 always:
Parent p = new Parent();
Integer s = p.getMyChilds().size();
Why?
The problem most probably is in your saving because you must not be setting the parent object reference in the child you want to save, and not with your retrieval or entity mappings per se.
That could be confirmed from the database row which must be having null in the foreign key column of your child's table. e.g. to save it properly
Parent p = new Parent();
Child child = new Child();
p.setChild(child);
child.setParent(p);
save(p);
PS. It is good practice to use #JoinColumn(name = "fk_parent_id", nullable = false) with #ManyToOne annotation. This would have stopped the error while setting the value which resulted in their miss while you are trying to retrieve.
All entities need to have an #Id field and a empty constructor.
If you use custom sql scripts for initialize your database you need to add the annotation #JoinColumn on each fields who match a foreign key :
example :
class Parent {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
public Parent() {}
/* Getters & Setters */
}
class Child {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
/* name="<tablename>_<column>" */
#JoinColumn(name="Parent_id", referencedColumnName="id")
private int foreignParentKey;
public Child () {}
}
fetch= FetchType.LAZY
Your collection is not loaded and the transaction has ended.
I' using Hibernate 3.6.1 to map three entities
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Entry {
private Long id;
private Date publishedAt;
#Id
public getId() {...}
...
}
#Entity
public class Category {
private Long id;
List<Podcast> podcasts;
#Id
public getId() {...}
#OneToMany(mappedBy = "category", cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#OrderBy("publishedAt")
public List<Podcast> getPodcasts() {
return podcasts;
}
}
and
#Entity
public class Podcast extends Entry {
private Category category;
#ManyToOne(fetch = FetchType.EAGER)
public PodcastsCategory getCategory() {
return category;
}
}
If i try to fetch a Category instance, i get an Exception
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'podcasts0_.Entry.publishedAt' in 'order clause'
What causes this exception? Whats wrong with this mapping?
It's caused by the following bug: HHH-3577 Wrong SQL in order by clause when using joined subclasses.
As a workaround you can remove #OrderBy and fetch = FetchType.EAGER on podcasts and load category using the following query instead of get():
SELECT DISTINCT c
FROM Category c LEFT JOIN FETCH c.podcasts p
WHERE c.id = ?
ORDER BY p.publishedAt
You could try the annotation #MappedSuperClass. See section 2.2.4.4. Inherit properties from superclasses of the hibernate documentation.