-
Notifications
You must be signed in to change notification settings - Fork 45
Managing relationships & using other entities as fields
By default, you can use the following types in your entity fields and they are directly mapped to corresponding data fields in by DBMS adapters:
-
int,float,char,long,double -
java.util.Date,String - Enum types (as integers)
However, it is obvious that we might need referencing other kinds of entities within our entities. There are four types of relationship cardinalities (types) between entities:
- One-to-one: Every X corresponds to a Y.
- One-to-many: Many Y entities has a field set to X, in other word, an X has many Y's. e.g. a city might have many airports.
- Many-to-one: Many X entities has a field set to Y. e.g. many airports can be in NYC.
- Many-to-many: an X entity might have many Y's and a Y entity might have many X's as well. e.g. a student might have enrolled to many courses and a course might have many students.
ORMAN can manage all of these relationships. Let's see how we can manage each.
It is mandatory to have a auto-increment
@PrimaryKeyfield on the referenced entity.
Our example is a flight ticket system consists of two entities, Ticket and Payment. Every payment belongs to only a ticket and every ticket has only a payment.
In this framework, it is recommended to have foreign entity reference on only one entity in one-to-one situation, otherwise redundant column will be generated.
Our Payment class is like:
@Entity
public class Payment extends Model<Payment>{
@PrimaryKey(autoIncrement=true)
public int transactionId;
public float amount;
}
and Ticket class is like:
@Entity
public class Ticket extends Model<Ticket>{
@PrimaryKey(autoIncrement=true)
public long id;
public String seat;
public Payment payment;
}
By using Payment type field in Ticket, we have created a 1:1 relationship. Let's see how can we use it (we'll learn how to use these methods later, just learn the idea.):
Payment p = new Payment();
p.amount = 150;
p.insert();
Ticket t = new Ticket();
t.seat = "33C"
t.payment = p;
t.insert();
Using @OneToOne annotation is not compulsory, however you can set a LoadingPolicy (LAZY or EAGER loading) or a target binding field (which we don't actually recommend you to use) on the annotation parameters.
Note that if you can use @OneToOne annotation on both entities and set their
LoadingPolicyasEAGER(which is default), you'll get an infinite loop. One of them should beLAZYand it should be binded usingtargetBindingFieldproperty of@OneToOneannotation.
To explain this, let's change our example: We have two entities, Employee and Department i.e. an employee can work only in one department. Here's how our Employee class is declared:
@Entity
public class Employee extends Model<Employee>{
@PrimaryKey(autoIncrement=true)
public long id;
public String name;
@ManyToOne
public Department dept;
}
and here's how our Department is declared:
@Entity
public class Department extends Model<Department>{
@PrimaryKey(autoIncrement=true)
public long id;
public String title;
@OneToMany(toType = Employee.class, onField = "dept")
public EntityList<Department, Employee> employees = new EntityList(Department.class, Employee.class, this);
}
Here you must be noticed a few things:
- On
deptfield ofEntity, we just use@ManyToOneannotation. It hasLoadingPolicy.EAGERby default. That means when we query an employee, the department will also be automatically queried andDepartmentinstance will be set todeptfield. - On
employeesfield ofDepartment, we have@OneToManyannotation. It declares the target type (Employeein this case), and the field that will be queried on target entity (you should write exactly the same name of the field here). - The type of
@OneTypeManyemployeesfield isEntityList<A, B>this is a special entity set holder implements the traditional Java List interface (java.util.List<B>).
EntityList allows you a safe way to manage relationships easily. In its constructor, you should specify the holder type as first parameter, target type as second parameter, and holder instance (usually this) as third parameter.
On the holder entity (which has a
@OneToManyfield), you should initialize theEntityListnext to the declaration (as above) or inside the constructor.
Now let's see a few examples:
Department d1 = new Department();
d1.title = "Engineering";
d1.insert();
Department d2 = new Department();
d2.title = "Operations";
d2.insert();
Employee e1 = new Employee();
e1.name = "Ahmet";
e1.insert();
d1.employees.add(e1); // now e1.dept is d1 (on both instance and db)
Employee e2 = new Employee();
e2.name = "Berker";
e2.insert();
d1.employees.add(e1); // now e2.dept is d1 (on both instance and db)
// or you can set a department to the employee (convenient way)
Employee e3 = new Employee();
e3.name = "Ozgur";
e3.dept = d2;
e3.insert(); // now d2 has e3 as an element.
You can use a EntityList<A, B> as a List<B> since it implements the List<B> interface. As you seen above, add() method is invoked on employees field. You can also use traditional List methods
remove(int index)remove(Object o)size()contains(Object o)addAll(Collection<B> c)removeAll(Collection<B> c)-
add(int index, B element): index parameter is not used. isEmpty()indexOf(Object o)-
iterator()...
methods on EntityList. An @OneToMany-annotated field has LoadingPolicy.LAZY by default, it can improve performance in especially large datasets and it comes without much cost on small datasets.
Note that
EntityListoperations are immediately reflected to the database. In the entity that hasEntityListfield, if you do updates onEntityListyou don't need to update parent entity.
That means, if you need to add many items at once to an
EntityList, in order to avoid query overhead, you should useaddAllmethod.
In many-to-many relationships, we need a "join table" to keep record of which type X has which type Y's. Simply we use @ManyToMany annotation and, again, EntityList.
A framework limitation: An entity can have at most one
@ManyToMany-annotated field.
In this example, we'll have two entities, BlogPost and Keyword, a blog post might have many keywords and a keyword might be used in many posts. Our Keyword entity class is declared as:
@Entity
public class Keyword extends Model<Keyword>{
@PrimaryKey(autoIncrement=true)
public long id;
public String word;
@ManyToMany(toType = BlogPost.class, load=LoadingPolicy.LAZY)
public EntityList<Keyword, BlogPost> taggedPosts = new EntityList<Keyword, BlogPost>(Keyword.class, BlogPost.class, this)
}
and BlogPost class is:
@Entity
public class BlogPost extends Model<BlogPost>{
@PrimaryKey(autoIncrement=true)
public long pid;
public String title;
@ManyToMany(toType = Keyword.class)
public EntityList<BlogPost, Keyword> keywords = new EntityList<BlogPost, Keyword>(BlogPost.class, Keyword.class, this);
}
Actually, is not necessary to have
taggedPostsfield onKeywordclass. But we'll use it for querying. Let's do a few examples with that:
Keyword k1 = new Keyword("general"); k1.insert();
Keyword k2 = new Keyword("linux"); k2.insert();
Keyword k3 = new Keyword("amd"); k3.insert();
Keyword k4 = new Keyword("intel"); k4.insert();
BlogPost p1 = new BlogPost();
p1.title = "Linux on AMD"; // keywords will be k1,k2,k3
p1.insert();
BlogPost p2 = new BlogPost();
p2.title = "Linux on Intel"; // keywords will be k1,k2,k4
p2.insert();
// for p1, add keywords on post.
p1.keywords.add(k1);
p1.keywords.add(k2);
p1.keywords.add(k3);
// for p2, tag it via taggedPosts of keywords.
k1.taggedPosts.add(p2);
k2.taggedPosts.add(p2);
k4.taggedPosts.add(p2);
When you would like to get keywords of p1, you can use p1.keywords field, and
when you would like to get tagged posts with intel(k4), you can use k4.taggedPosts field.
Remember that on
@ManyToManyrelationships a join table generated (most likely) as follows: Tableblog_post_keywordhas fieldsblog_post_idandkeyword_id.
If you want to use a join table generated with custom name you specified, use joinTable property on both @ManyToMany annotations:
@ManyToMany(joinTable = "myMany2ManyTable")
If you want to have properties on many-to-many relation you should create your own many-to-many holder entities and use many-to-one relationships in it. Example scenario: You have
Flights,Pilots and you want to keep a salary for each pilot specific to that flight. Then create a new entity whose properties areFlight,Pilotwith many-to-one relationship and add a field for salary.