-
Notifications
You must be signed in to change notification settings - Fork 47
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
@PrimaryKey
field 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
LoadingPolicy
asEAGER
(which is default), you'll get an infinite loop. One of them should beLAZY
and it should be binded usingtargetBindingField
property of@OneToOne
annotation.
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
dept
field ofEntity
, we just use@ManyToOne
annotation. It hasLoadingPolicy.EAGER
by default. That means when we query an employee, the department will also be automatically queried andDepartment
instance will be set todept
field. - On
employees
field ofDepartment
, we have@OneToMany
annotation. It declares the target type (Employee
in 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
@OneTypeMany
employees
field 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
@OneToMany
field), you should initialize theEntityList
next 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
EntityList
operations are immediately reflected to the database. In the entity that hasEntityList
field, if you do updates onEntityList
you 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 useaddAll
method.
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
taggedPosts
field onKeyword
class. 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
@ManyToMany
relationships a join table generated (most likely) as follows: Tableblog_post_keyword
has fieldsblog_post_id
andkeyword_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
Flight
s,Pilot
s and you want to keep a salary for each pilot specific to that flight. Then create a new entity whose properties areFlight
,Pilot
with many-to-one relationship and add a field for salary.