I'm having a hard time creating the optimal JPA representation of a relationship. Seems like there is a bunch of ways to do it and I'm not sure which is most optimal. Without JPA I might represent the structure as a PERSON table and a FRIEND table. Let's say the PERSON table just has an ID and NAME. The FRIEND table has an OWNER_ID, a PERSON_ID, and a settable boolean IS_ACTIVE field. OWNER_ID and PERSON_ID both refer to the PERSON table. A person can have many friends so the unique primary key would be on OWNER_ID and PERSON_ID. In my code I would like the PersonEntity to control the relationship. Operations in Java might look like this:
person1.getFriends().add( new Friend( person2, isActive ) );
Friend friend = person1.findFriend( person2ID );
Something like that. Note that I would like JPA to implicitly assign the OWNER of the friend relationship. In the above code I do not pass it in the Friend constructor, I only pass the PERSON part of the composite key.
Here was my first guess at this:
#Entity
public class Person {
#Id
#GeneratedValue
private Long id;
#OneToMany( mappedBy="key.owner", cascade=CascadeType.ALL, orphanRemoval = true)
private Set<Friend> friends = new HashSet<>();
// getter and setter for Friends
...
}
#Entity
public class Friend {
#EmbeddedId
private Key key = new Key();
private boolean isActive;
...
#Embeddable
public static class Key implements Serializable {
#ManyToOne
private Person owner;
#OneToOne
private Person person;
...
}
}
But when I use this code I get stack overflow errors. I'm assuming it is confused by the OneToOne relationship.
What is the best way to model this sort of relationship in JPA 2.0? I suppose the simplest thing would be to introduce a generated long key in Friend. But that seems like it will make the query more complex for "find friends for john" since I will be introducing a third mapping table, basically mapping the relationship twice in SQL. Another approach is to make it a unidirectional OneToMany relationship and not even specify OWNER explicitly in Friend. That would introduce another table for the mapping and make queries a little convoluted. Using ElementCollection instead of OneToMany seems straight forward but then my understanding is that I could not manage Friend as an entity?
Let me know if you guys need anymore detail or requirements. BTW, I tried putting a MapsId in Friend for the Owner part of the PKEY but that gave me runtime errors.
Any help is appreciated.
NOTE: Even if I add a generated key to Friend I would like to enforce the uniqueness on the combination of {owner,person} in the Friend entity.
I would indeed add an auto-generated ID to the Friend entity. It would make things much simpler (no composite key containing associations). I don't see how adding such an ID (and thus such an additional column in the Friend table) would make any query more complex, or introduce a new table. It won't. You'll simply have an additional column, being the primary key of the friend table.
Once you have that, you'll need to fix your mapping:
the association between Friend and Person is not a OneToOne, but a ManyToOne, since several persons can be the friends of another one.
mappedBy is supposed to hold the name of the field, in the other entity, which represents the other side of the association. In your case, it's thus "key.owner". If you stop using a composite key, it will simply be "owner".
And finally, in a bidirectional OneToMany association, the owner is always the Many side (i.e. Friend, in this case). So you must initialize the friend.owner field in order to persist the association. But if you encapsulate the list of friends rather than letting it accessible from the oustide, it will be simple. Instead of doing
person1.getFriends().add( new Friend( person2, isActive ) );
simply do
person1.addFriend( new Friend( person2, isActive ) );
and make sure that the addFriend() method contains the following instruction:
friendToAdd.setOwner(this);
Alright, so if you want a solution to this problem that creates the sort of table one would want to in pure SQL - that is no artificial surrogate key and application data as a compound key - you can definitely do it. However, using JPA makes this a little bit messier than you might want it to be.
Thanks to #JB Niznet for his great input, you can definitely also do this by introducing a surrogate key as he suggests. The solution below just removes that need and as far as I can tell has no drawbacks.
#Entity
public class Person {
#Id
#GeneratedValue
private Long id;
#OneToMany( mappedBy="owner", cascade=CascadeType.ALL, orphanRemoval = true)
private Set<Friend> friends = new HashSet<>();
// getter and setter for Friends
...
}
#Entity
public class Friend {
#EmbeddedId
private Key key = new Key();
#ManyToOne
#Maps("ownerId")
private Person owner;
#ManyToOne
#MapsId("personId")
private Person person;
private boolean isActive;
...
#Embeddable
public static class Key implements Serializable {
private Long ownerId;
private Long personId;
...
}
}
You have to think about the Design: Entities will be classes and have tables. Relations will ONLY have tables, since relations are just connecting 2 Entities! (except a relation has attributes, like "friend-rating" in this case etc..)
While Person is clearly a Entity, friend is a relation. More than that, friend is a bidirectional relation. (A is friend of B => B is friend of A)
So, you simple need to extend your Person Object, by a List of Persons - called friends:
#Entity
public class Person {
#Id
#GeneratedValue
private Long id;
#OneToMany
#JoinTable(name="friends")
#JoinColumn(name="person_A_id", referencedColumnName="id"), #JoinColumn(name="person_B_id", referencedColumnName="id"))
private Set<Person> friends = new HashSet<>();
}
This is untestet, but should give you a table like this:
person_a_id | person_b_id
1 2
1 6
2 7
then you simple can work with your entity like
Person A = new Person();
Person B = new Person();
A.friends.add(B);
However, keep in Mind that JPA will not be able to thread this bidirectional: So if you add B to A'S friends, you wont find A in the List of Friends of B. You have to take care for that!
Related
I'm working in a project which involves a one to many relation in the database.
A simple example of this would be a teacher that teaches many courses but a course can be taught by just one teacher.
My question is what would be the best way to model this?
The first object is Teacher:
class Teacher{
public int id;
public String name;
public String lastName;
...
}
The thing is how would the course class look like?
Option 1:
class Course{
public int idCourse;
public String courseDescription;
**public int teacherId;**
...
}
Option 2:
class Course{
public int idCourse;
public String courseDescription;
**public Teacher teacher;**
...
}
Option 2 (having a reference to the teacher object) seems to be more functional from an OOP perspective.
In a usual flow, you would create a course object like :
Teacher t = new Teacher('first_name', 'last_name');
Course c = new Course('Math course');
c.setTeacher(t);
Your option 1 highlights what the relational database schema behind this might look like. Here's a good read on that :
https://www.lifewire.com/one-to-many-relationships-1019756#:~:text=A%20teacher%20can%20teach%20multiple,one%20teacher%20to%20multiple%20courses.
Once the code above is committed, it could fire two insert queries underlying to add the new teacher in teachers table and add the new course (along with the teacher ID), in the course table.
In database we have a "parent" table with a discriminator column, and depending on this discriminator column some columns are stored in specific tables or some relationships (many/one-to-many) are allowed.
In our mapping we wanted to implement it like
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class A {
#Id
#Column
private int id;
}
#Entity
public class B extends A {
#Column
private String specificField;
}
#Entity
public class C extends A {
#OneToMany
private List<OtherEntity> otherEntities;
}
Unfortunately Hibernate wants to join on table C, which does not exist since it would only contain the FK to A. Is there a way to keep this strategy but tell that no join is necessary?
Otherwise, what is the cleanest solution? Following this question I thought using the SINGLE_TABLE strategy with the #SecondaryTable annotation on child entities that require it but it seems heavier to configure (having to declare, for each column, the source table).
I have a mant-to-many relationship modeled in the database (with a bridge table) between Student and Professor (_students_selected) , in my entites i have modeled it as a one-to-many relationship i.e. a Professor has one Student.
HasManyToMany<Student>(Reveal.Member<Professor>("_students"))
.Table("_students_selected").ChildKeyColumn("student_key").ParentKeyColumn("professor_key");
public class Professor
{
private IList<Students> _students;
public virtual Student Student
{
get { return _students.FirstOrDefault(); }
}
}
The above works when getting the data however when querying over the Professors i am unable to add a where condition on the students because the actual data is mapped to the private backing field _students. How do i query this? code below does not work.
_unitOfWork.Session.QueryOver<Professor>().Where(i => i.Student.Id == 24).List();
NHibernate can't translate your C# code inside the property to SQL, it can only work with mapped properties. Either use the collection in the statement (which needs to be public/internal then of course) or filter the results in memory (but be careful with select n + 1 problems then).
Supposing the following entities :
public class AppUser
{
public virtual int Id { get; set; }
public virtual string Login { get; set; }
}
// Mapped as joined-subclass
public class Person : AppUser
{
public virtual int Age { get; set; }
}
If I create 1 AppUser, and save it like this
var user = new AppUser() { Login = "test" };
session.Save( user ); // let's say Id = 1
How can I cast/convert/"promote" it to a Person, keeping the same ID ?
Now, i'm stuck with a row in my AppUser table, with Id = N. How can I populate the Person table with the same Id ? I can't delete the AppUser and recreate it as a Person, as AppUser may be referenced by foreign keys.
I could issue a "manual" SQL INSERT, but it's kind of ugly...
This is definitively a NHibernate question. I understand that from an OOP point of view, this makes little sense, hence the absence of other tags than nhibernate.
I don't believe nHibernate is going to be able to solve this problem for you. nHibernate is dealing with your data as an object and, especially with joined-subclass I don't believe there is anything built in that allows you to change the subclass type on the fly, or at least change the type and retain the original ID.
I think your best bet is to write a stored procedure that, given an ID and a NEW type, removes all entries from subclass tables and adds a new entry to the correct subclass table.
Once that proc runs, then reload the object in nHibernate (and make sure you have thrown away any cached data relating to it), it should now be of the correct type you want to work with, set its NEW properties and save it.
That way you have a relatively generic stored proc that just changes your subclass types, but you dont need to add all the crazy logic to handle various properties on your subclasses.
This has been discussed on SO before and I am quoting Jon Skeet for posterity:
No. A reference to a derived class
must actually refer to an instance of
the derived class (or null). Otherwise
how would you expect it to behave?
For example:
object o = new object();
string s = (string) o;
int i = s.Length; // What can this sensibly do?
If you want to be able to convert an
instance of the base type to the
derived type, I suggest you write a
method to create an appropriate
derived type instance. Or look at your
inheritance tree again and try to
redesign so that you don't need to do
this in the first place.
In Skeet's example, string's are objects and objects are not strings. So the "upcasting" would not work.
I'd like one of my entities to have a one-to-one relationship with a class hierarchy. Think of it like a Strategy pattern, where each strategy needs different parameters to be persisted. I tried using a combination of OneToOne and JoinedBase/JoinedKey, but I've come across a problem.
With this combination, the primary key of the main entity also appears as the primary key of the table representing the root class in the hierarchy, and as the primary key of the subclass:
Order --------------- TaxCalculator
([PrimaryKey]Id = 1234) ([PrimaryKey(PrimaryKeyType.Foreign)]OrderId = 1234)
^
|
|
UkTaxCalculator
([JoinedKey]UkTaxCalculatorId = 1234)
I can persist this fine, but then I can't change which subclass of TaxCalculator I have. When I do something like:
order.TaxCalculator = new OverseasTaxCalculator(order);
then try to flush, then ActiveRecord/NHibernate (understandably) gets unhappy that there are now two TaxCalculators with Id = 1234.
I can get around this by replacing the OneToOne with a HasMany/BelongsTo, and hiding the multiplicity from users of the Order object, but I'm interested to know if it's possible to do this with OneToOne.
There's a full code example on github. This code throws an exception when the second SessionScope is disposed. If you clone the project, it should run out-of-the-box.
first of all i am sorry, but i did not tried my solution. It is to late and i really need my sleep ;-). I think the only way the one-to-one could work would be a 'table-per-hierarchy'-approach using a discriminator column instead of table-per-subclass. Maybe this will enable you to morph the existing object to another subclass. An other way, something like a polymorphic delete-orphan unfortunately is not supported as you stated. So i'll guess this would be your (very) last option.
But if this fails why don't you map it as a one-to-many instead of many-to-one with a foreign key in the order table, reusing the TaxCalculators? I would imagine them as quite static.
Interesting idea though: polymorphic delete-orphan.
We do something very similar to what you are trying to do. I think it's your combination of one-to-one and the joined key that's causing the problem. Try this:
[ActiveRecord, JoinedBase]
public class TaxCalculator
{
protected int TaxCalculatorId;
[PrimaryKey]
public virtual int Id
{
get { return TaxCalculatorId; }
set { TaxCalculatorId = value; }
}
// common tax calculation fields, methods etc...
}
[ActiveRecord]
public class OverseasTaxCalculator : TaxCalculator
{
[JoinedKey]
public override int Id
{
get { return TaxCalculatorId; }
set { TaxCalculatorId = value; }
}
// overseas tax calculation specific methods, properties etc...
}