I have 2 java classes, Relation and Person, which both are present in my database.
Person:
#Entity
#Table(name = "persons")
public class Person {
#Id
#Column
private int id;
#Column
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumns({
#JoinColumn(name = "slave_id", referencedColumnName="id"),
#JoinColumn(name = "master_id", referencedColumnName="id")
})
private List<Relation> relations;
//Getters and setters
}
Relation:
#Entity
#Table(name = "relations")
public class Relation {
#Id
#Column
private int id;
#Column
private int child_id;
#Column
private int parent_id;
#Column
private String type;
//Getters and setters
}
Each Person has a list of relations (or not), the relation should be added to the list when the child_id or the parent_id of the relation is equal to the id of the person.
TL;DR:
When relation.child_id OR relation.parent_id = person.id => add relation to list of relations to the person
The issue I am facing is that this annotation:
#JoinColumns({
#JoinColumn(name = "child_id", referencedColumnName="id"),
#JoinColumn(name = "parent_id", referencedColumnName="id")
})
creates following SQL (just the necessary part):
relations relations6_
on this_.id=relations6_.slave_id
and this_.id=relations6_.master_id
What is the correct annotation in Java Hibernate to generate an SQL statement saying OR instead of AND
Some of the options that you could utilize:
Database views. Create the view that does custom join for you and map the entity to the view.
Join formula. I managed to make them work only on many-to-one associations. Nevertheless, you could make the association bidirectional and apply the formula in the Relation entity.
#Subselect. This is a kind of Hibernate view, suitable if you can't afford to create a real database view or change the db schema to better suit the entity model structure.
This and this answer could also be helpful.
Also, you can always use two separate associations for slaves and masters:
public class Person {
#OneToMany
#JoinColumn(name = "slave_id"),
private List<Relation> slaves;
#OneToMany
#JoinColumn(name = "master_id"),
private List<Relation> masters;
public List<Relation> getRelations() {
List<Relation> result = new ArrayList<>(slaves);
result.addAll(masters);
return result;
}
}
However, keep in mind that joining all of them in a single query requires full Cartesian product between masters and slaves.
You can use #FilterDef and #Filter annotations.
Related
I'm curious if it is possible to have several #ManyToOne relations of same entity in parent entity with JPA/Hibernate.
Example:
I have bank transactions, each transaction has a BankPartner in tow roles Creditor and Debtor. The point is, I want to edit data only once. BankPartner with nickName "mistress" is only one :), doesn't matter if in role creditor or debtor. Once, it will be renamed to wife, so I don't want to change separately. Also, the Balance is SUM of all transactions for BankPartner in both roles.
#Entity
public class Transaction {
..
#ManyToOne(fetch = FetchType.LAZY, cascade={CascadeType.ALL}, orphanRemoval=true, targetEntity = PartnerEntity.class)
#JoinColumn(name = "CREDITOR_ID")
private BankPartner creditor
#ManyToOne(fetch = FetchType.LAZY, targetEntity = PartnerEntity.class)
#JoinColumn(name = "DEBTOR_ID")
private BankPartner debtor
..
}
#Entity
public class BankPartner {
...
private String name;
private String nickName;
private String description;
...
}
I can imagine that from "Transaction" direction in can somehow work, but can't find a way to work from BankPartner direction.
I can see two different approaches
create #ManyToMany with "ROLE_TYPE" in intersection table between BankPartner and Transaction
create two separate entities Debtor an Creditor from the same table.
But, as I said, I'm curios about the first approach ..
You can use a multi column join as such:
#Entity
public class Transaction {
#ManyToOne(fetch = FetchType.LAZY, cascade={CascadeType.ALL}, orphanRemoval=true, targetEntity = PartnerEntity.class)
#JoinColumns({
#JoinColumn(name = "DEBTOR_ID"),
#JoinColumn(name = "CREDITOR_ID")
})
private BankPartner partner
}
in my spring data application i have two TABLE VIEW mapped:
the first view
#Entity
#Immutable
#Table(name="VD_CONT")
#NamedQuery(name="VdContr.findAll", query="SELECT d FROM VdContr d")
public class VdContr {
#Id
#Column(name="CONTR_ID")
private Long id;
#Column(name="CF")
private String cf;
#OneToMany(fetch=FetchType.LAZY, cascade = CascadeType.ALL, mappedBy="vdcontr")
private List<VdArr> vdArr;
}
and the second view
#Entity
#Immutable
#Table(name="VD_ARR")
#NamedQuery(name="VdArr.findAll", query="SELECT v FROM VdArr v")
public class VdArr {
#Id
#Column(name="ARR_ID")
private Long id;
#Column(name="FK_CONTR_ID")
private Long fkContrId;
#ManyToOne(fetch=FetchType.LAZY)
public VdContr vdcontr;
}
If i put a relationship "OneToMany" and "ManyToOne" (1, first view : many, second view), i receive errors.
My question is: is it possibile create a relationship between two table view?
you need to add a #JoinColumn to VdContr.
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "vdcontr_id", nullable = false)
In general, views are mapped in the same way as tables.
By looking at your classes, the problem is that Hibernate cannot find the correct join column. You need to specify it.
Also, in your VdArr you should delete the fkContrId, because hibernate will need to use this column to map the VdContr relationship.
By looking at your code, the join column is FK_CONTR_ID, so you need to specify it by using #JoinColumn.
#Entity
#Immutable
#Table(name = "VD_ARR")
#NamedQuery(name = "VdArr.findAll", query = "SELECT v FROM VdArr v")
public class VdArr {
#Id
#Column(name = "ARR_ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_CONTR_ID")
public VdContr vdcontr;
}
I have 2 tables that may be related to each other through non-PK secondary columns. Moreover, the column names for this match are different in each table. That is,
#Entity
#Table(name = "PLANS_T")
public class Plans {
private Integer id; // PK
//...
private String secondaryIdentifier; // Should be matched with TRAINEES.auxiliaryIdentifier
//...
}
#Entity
#Table(name = "TRAINEES_T")
public class Trainee {
private Integer id; // PK
//...
private String auxiliaryIdentifier; // Should be matched with PLANS.secondaryIdentifier
}
The relationship between PLANS and TRAINEE is Many-to-One: You can have many Plans for a Trainee.
I need to annotate these properly to indicate that PLANS_T.secondaryIdentifier should be used with TRAINEES_T.auxiliaryIdentifier for JOINs (such as in the Criteria API, which needs a Join Path from one table to the other).
But I can't use the typical examples e.g.
#Entity
class Trainee {
#OneToMany(mappedBy = "plan")
private Collection<Plans> plans = new ArrayList<Plans>();
}
#Entity
class Plans {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="auxiliary_identifier") // Where do I specify "secondaryIdentifier", a non-PK column?
private Trainee trainee;
}
I need a way to specify both of the non-PK columns in the annotations. When using Criteria API, these annotations provide the path to create Join paths.
You should correct your mapping in the following way:
#Entity
class Trainee {
#OneToMany(mappedBy = "trainee")
private List<Plans> plans = new ArrayList<Plans>();
}
#Entity
class Plans {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="secondary_identifier", referencedColumnName = "auxiliary_identifier")
private Trainee trainee;
}
The mappedBy of the #OneToMany is the name of the field that owns the relationship. This is trainee field of the Plans entity.
The referencedColumnName of the #JoinColumn is the name of the column referenced by this foreign key column.
I have a question regarding ManyToOne relationship.
Assume I have 2 beans:
#Entity
#Table(name = "accounts")
public class Account {
#Id
#Column(name = "account_id")
private int account_id;
}
#Entity
#Table(name = "broker_account")
public class BrokerAccount {
#Id
#Column(name = "broker_account_id")
private int broker_account_id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="account_id", referencedColumnName = "account_id")
private Account account;
}
I am querying the entity below (plain get all query)
entityManager.createQuery("from BrokerAccount", BrokerAccount.class)
I thought that if I query BrokerAccount entity the account_id column will be populated by default on the Account object, since it exists in the BrokerAccount table as well, however all the Account fields are empty.
Am I missing something, should I define this field/column on the BrokerAccount entity itself as well to get its value?
You have defined the Account association as #ManyToOne(fetch = FetchType.LAZY). This means that while performing the entityManager.createQuery("from BrokerAccount", BrokerAccount.class), there will be no join on the Account and its data will not be fetched at that time.
In order to make the persistence provider fetch the Account data you would need to interact with the reference while being in the same transactional method, f.e.: brokerAccount.getAccount().getAccountId();
If you want to simply have a repeated column for the fk you can do:
#Column(name = "account_id", insertable=false, updatable=false)
private int account_id;
I have these two class(table)
#Entity
#Table(name = "course")
public class Course {
#Id
#Column(name = "courseid")
private String courseId;
#Column(name = "coursename")
private String courseName;
#Column(name = "vahed")
private int vahed;
#Column(name = "coursedep")
private int dep;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "student_course", joinColumns = #JoinColumn(name = "course_id"), inverseJoinColumns = #JoinColumn(name = "student_id"))
private Set<Student> student = new HashSet<Student>();
//Some setter and getter
and this one:
#Entity
#Table(name = "student")
public class Student {
#Id
#Column(name="studid")
private String stId;
#Column(nullable = false, name="studname")
private String studName;
#Column(name="stmajor")
private String stMajor;
#Column(name="stlevel", length=3)
private String stLevel;
#Column(name="stdep")
private int stdep;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "student_course"
,joinColumns = #JoinColumn(name = "student_id")
,inverseJoinColumns = #JoinColumn(name = "course_id")
)
private Set<Course> course = new HashSet<Course>();
//Some setter and getter
After running this code an extra table was created in database(student_course), now I wanna know how can I add extra field in this table like (Grade, Date , and ... (I mean student_course table))
I see some solution but I don't like them, Also I have some problem with them:
First Sample
If you add extra fields on a linked table (STUDENT_COURSE), you have to choose an approach according to skaffman's answer or another as shown bellow.
There is an approach where the linked table (STUDENT_COURSE) behaves like a #Embeddable according to:
#Embeddable
public class JoinedStudentCourse {
// Lets suppose you have added this field
#Column(updatable=false)
private Date joinedDate;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="STUDENT_ID", insertable=false, updatable=false)
private Student student;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="COURSE_ID", insertable=false, updatable=false)
private Course course;
// getter's and setter's
public boolean equals(Object instance) {
if(instance == null)
return false;
if(!(instance instanceof JoinedStudentCourse))
return false;
JoinedStudentCourse other = (JoinedStudentCourse) instance;
if(!(student.getId().equals(other.getStudent().getId()))
return false;
if(!(course.getId().equals(other.getCourse().getId()))
return false;
// ATT: use immutable fields like joinedDate in equals() implementation
if(!(joinedDate.equals(other.getJoinedDate()))
return false;
return true;
}
public int hashcode() {
// hashcode implementation
}
}
So you will have in both Student and Course classes
public class Student {
#CollectionOfElements
#JoinTable(
table=#Table(name="STUDENT_COURSE"),
joinColumns=#JoinColumn(name="STUDENT_ID")
)
private Set<JoinedStudentCourse> joined = new HashSet<JoinedStudentCourse>();
}
public class Course {
#CollectionOfElements
#JoinTable(
table=#Table(name="STUDENT_COURSE"),
joinColumns=#JoinColumn(name="COURSE_ID")
)
private Set<JoinedStudentCourse> joined = new HashSet<JoinedStudentCourse>();
}
remember: #Embeddable class has its lifecycle bound to the owning entity class (Both Student and Course), so take care of it.
advice: Hibernate team suppports these two approachs (#OneToMany (skaffman's answer) or #CollectionsOfElements) due some limitations in #ManyToMany mapping - cascade operation.
regards,
The student_course table is there purely to record the association between the two entities. It is managed by hibernate, and can contain no other data.
The sort of data you want to record needs to be modelled as another entity. Perhaps you could a one-to-many association between Course and StudentResult (which contains the grade, etc), and then a many-to-one association between StdentResult and Student.
Drop the many-to-many, create a class called StudentCourseRelationship and set up one to manys on Student and Course to the StudentCourseRelationship.
You can put all sorts of things on it, like DateEnrolled, DateKickedOut etc. etc.
IMO the many-to-many mapping is a bit of a con.
The accepted answer unfortunately doesn't work for me, hibernate generates the join table in a weird way (all join columns are duplicated). However the variant with dedicated entity for the join table works fine. Here it is described in great detail: http://www.mkyong.com/hibernate/hibernate-many-to-many-example-join-table-extra-column-annotation/