Ch04 Object Relational Mapping - skatscher/pro_jpa2_book GitHub Wiki
This chapter describes the Object Relational Mapping.
Rules:
- each entity needs to be annotated with @Entity
- there has to be one field/property of the entity annotated with @Id to mark the primary key of the corresponding table
Example of the simplest entity:
@Entity
class Employee {
@Id int id;
}
There are 3 modes describing how entities instance state is accessed by the persistence provider runtime: Field, Property and Mixed Access.
- Annotating the fields of an entity will cause the provider to use field access
- Getter or setter might or might not be present. They will be ignored by provider
- all fields must be declared as either protected, package or private. Public is disallowed
Example:
@Entity
public class Employee {
@Id private int id;
private String name;
private long salary;
// public getters and setted omitted
}
- the @Id annotation is used for the id field, so the provider uses field access
- fields name and salary will be mapped to table columns NAME and SALARY
- in property access mode the Java Bean conventions apply
- there must be getter and setter methods for persistent properties
- the type of property is determined by return type of the getter method and must be the same type passed into the setter method
- methods must be either protected or public
- the mapping annotation must be on the getter method
Example:
@Entity
public class Employee {
private int id;
private String name;
private long wage;
@Id public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public long getSalary() { return wage; }
public void setSalary(long salary) { this.wage = salary; }
}
- the @Id is on the getId() getter method, so the provider will use property access
- the name and salary properties will be made persistent to table columns NAME and SALARY (Note: salary property is backed by wage field)
- it is possible to combine field access and property access within the same entity
- @Access modifier on the entity marks default access mode for the class
- @Access modifier on fields/properties marks exceptions from default access mode
Example:
@Entity
@Access(AccessType.FIELD)
public class Employee {
...
@Transient private String phoneNum;
...
@Access(AccessType.PROPERTY) @Column(name="PHONE")
protected String getPhoneNumberForDb() {...}
...
}
- provider uses field access for Employee entity as default
- provider uses property access for phoneNumberForDb with column PHONE
- backed field phoneNum for this property has to be marked as transient to not persist the same state twice
- the default table name is the unqualified name of the entity class
- use @Table annotation to specify a different table name
Example:
@Entity
@Table (name="EMP")
public class Employee {...}
- Note: most databases are not case-sensitive to names
- @Table annotation also allows to specify schema and catalog
@Table(name="EMP", schema="HR")
Persistable Types:
- Primitive Java types: byte, int, short, long, boolean, char, float, double
- Wrapper classes of primitive types: Byte, Integer, Short, Long, Boolean, Character, Float, Double
- Byte and character array types: byte[], Byte[], char[], Character[]
- Large numeric types: java.math.BigInteger,, java.math.BigDecimal
- Strings: java.lang.String
- Java temporal types: java.util.Date, java.util.Calendar
- JDBC temporal types: java.sql.Date, java.sql.Time, java.sql.Timestamp
- Enumerated types: any system or user-defined enumerated type
- Serializable objects: any system or user-defined serializable type
- Specifying the @Column annotation on an attribute indicates specific characteristics of the physical database column that the object model is less concerned about
- the name element of @Column allows to override the default column name
@Column(name="SAL")
private long salary;
- the @Basic annotations allows to define a FetchType to LAZY or EAGER
- EAGER is the default FetchType of al basic mappings
- LAZY means that the provider might defer loading the state of that attribute until it is referenced
@Basic(fetch=FetchType.LAZY)
private String comments;
- LOB = character or byte-based objects than can be very large (CLOB/BLOB)
- @Lob annotation signals the provider to threat the object as LOB
- @Lob acts just as marker and might appear in conjunction with @Basic and @Column
- Java types mapped to BLOB columns: byte[], Byte[], Serializable
- Java types mapped to CLOB columns: char[], Character[], String
@Basic(fetch=FetchType.LAZY)
@Lob @Column(name="PIC")
private byte[] picture;
- values of enum types are constants and have an implicit ordinal assignment that is determined by the order in which they were declared. This ordinal does not change at runtime and can be used to store the enum value in the database.
- default: mapping ordinal of enum value to integer type column
- if enum type changes there is the problem that persisted ordinal data does not longer apply to the correct value. Therefore it is also possible to store the enum values as STRING type columns. This is done by adding @Enumerated annotation to the attribute with value STRING (in contrast to ORDINAL).
public enum EmployeeType {
FULL_TIME_EMPLOYEE, PART_TIME_EMPLOYEE, CONTRACT_EMPLOYEE
}
...
@Enumerated(EnumType.STRING)
private EmployeeType type;
- the types java.sql.Date, java.sql.Time and java.sql.Timestamp are included automatically and need no further annotations
- the types java.util.Date and java.util.Calendar need to be annotated with @Temporal and specifying the JDBC type as value of TemporalType (DATE, TIME, TIMESTAMP)
@Temporal(TemporalType.DATE)
private Calendar startDate;
- attributes that are part of a persistent entity but not intended to be persistent can either be modified with the transient modifier in Java or be annotated with the @Transient annotation
transient private String cachedValue;
Rules:
- the @Id annotation indicates the identifier of the entity
- Primary Key are assumed to be insertable, but not nullable or updatable. nullable and updatable should not be overriden, insertable can be overriden in certain circumstances.
Types:
- Ptimitive Java types: byte, int, short, long, char
- Wrapper classes: Byte, Integer, Short, Long, Char
- String: java.lang.String
- Large numeric type: java.math.BigInteger
- Temporal types: java.util.Date, java.sql.Date
- float, double, Float, Double, BigDecimal permitted but discouraged due to rounding errors
- @GeneratedValue specifies that the persistence provider generates the identifier
- the application cannot rely on being able to access the identifier until either a flush has occured or the transaction has completed
- 4 strategies: AUTO, TABLE, SEQUENCE, IDENTITY
Automatic Id Generation:
- means that provider will use any stategy it wants to generate identifiers
- suited for development or prototyping
@Id @GeneratedValue(strategy=GenerationType.AUTO)
private int id;
Id Generation using a Table:
- most flexible and portable
- an id generation table should have two columns:
- string type column to identify the particular generator sequence
- integer type column that stores the actual id sequence. The value stored is the last id that was allocated in the sequence
@Id @GeneratedValue(strategy=GenerationType.TABLE)
private int id;
- it is possible to explicitely define the table for id storage by a table generator with @TableGenerator annotation. The table generator's name it refered to by the @GeneratedValue annotation
- the table generator can be defined on any attribute and is available in the entire persistence unit
@TableGenerator(name="Emp_Gen", table="ID_GEN", pkColumnName="GEN_NAME", valueColumnName="GEN_VAL")
@Id @GeneratedValue(generator="Emp_Gen")
private int id;
- @TableGenerator has optional elements
- initialValue - the start value, which is by default 0
- allocationSize - the value of a preallocated block of ids, which is by default 50
Id Generation using a Database Sequence:
- many databases support internal id generation by sequences
- if not concerning a particular database sequence the following is sufficient. The provider will use a default sequence of its own choice
@Id @GeneratedValue(strategy=GenerationType.SEQUENCE)
private int id;
- to ensure which sequence to be used it has to be named
@SequenceGenerator(name="Emp_Gen", sequenceName="Emp_Seq")
@Id GeneratedValue(generator="Emp_Gen")
private int id;
Id Generation using Database Identity:
- some databases support primary key identity columns - whenever a row is inserted into the table the identity column will get a unique id assigned to it
- less efficient for OR id generation:
- no block allocation
- id not available before commit. Provider has to reread inserted row after insert
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
- no generator annotation available for IDENTITY, because it is defined as part of the database schema
- Roles: In every relationship there are two entities that are related to one another and each entity is said to play a role in the relationship.
- Directionality
- bidrectional: if each entity points to the other
- unidrectional: if only one entity has a pointer to the other. The referring entity is also called the source and the referred-to entity is called the the target.
- Cardinality: captures how many entities exist on each side of the same relationship
- one
- many
- Ordinality: determines whether a target entity needs to be specified when the source entity was created
- optional: the target might not be present
- mandatory: the target has to be present, otherwise invalid state
- Many-to-one
- One-to-one
- One-to-many
- Many-to-many
= target is "one"
Many-to-one Mapping
- defined by annotating the attribute in the source entity with the @ManyToOne annotation
@Entity
public class Employee {
@ManyToOne
private Department department;
}
Join Columns
- a join column in JPA refers to a foreign key column in database terms
- @JoinColumn can be used to specify the name of the join column on the owning side
@Entity
public class Employee {
@Id private int id;
@ManyToOne @JoinColumn(name="DEPT_ID")
private Department department;
}
- foreign key column name is DEPT_ID instead of defaulted DEPARTMENT_ID
One-to-one Mappings
- defined by annotating the attribute in the source entity with the @OneToOne annotation
- almost the same like many-to-one mappings, except that only one instance of the source entity can refer to the same target entity instance
@Entity
public class Employee {
@OneToOne @JoinColumn(name="PSPACE_ID")
private ParkingSpace parkingSpace;
}
Bidirectional One-to-one Mappings
- both entities are source and target, so for bidirectional mappings we say there is an owning side and a referred-to side. The owning side "owns" the association and has the join column.
- mappedBy element in the one-to-one mapping on the referred-to side contains the name of the referring attribute on the owning side
@Entity
public class ParkingSpace {
@OneToOne(mappedBy="parkingSpace")
private Employee employee;
}
Rules:
- @JoinColumn annotation goes on the owning side with the join column
- mappedBy element goes on the inverse side without join column
= target is "many"
One-to-Many Mappings
- almost always bidirectional
- usually the target entity table has a foreign key pointing to the source entity table
- @OneToMany annotation with mappedBy element (inverse side)
@Entity
public class Department {
@OneToMany(mappedBy="department")
private Collection<Employee> employees;
}
- Note that Employee has join column and many-to-one association (@JoinColumn, @ManyToOne)
- the example above uses generics to specify the type of collection. If no generics, use targetEntity instead to specify the type of target.
@OneToMany(tagetEntity=Employee.class, mappedBy="department")
private Collection employees;
Rules for bidirectional one-to-many relationships:
- the man-to-one side is the owning side with the join column
- the one-to-many side is the inverse side, so the mappedBy element must be used
Many-to-Many Mappings
- one or more entities are associated with a collection of other entities and the entities have overlapping associations with the same target entity
- @ManyToMany association on the collection attributes on both sides
- one side has to be picked as the owner of the association, mappedBy identifies the owning attribute, in example "projects" in Employee entity.
@Entity
public class Employee {
@ManyToMany
private Collection<Project> projects;
}
@Entity
public class Project {
@ManyToMany(mappedBy="projects")
private Collection<Employee> employees;
}
Join Tables
- each many-to-many relationship must have a join table, consisting of two foreign key columns
- @JoinTable annotation is used to configure the join table for the relationship. joinColumns element defines the owning side and inverseJoinColumns the inverse side join column.
@ManyToMany
@JoinTable(name="EMP_PROJ", joinColumns=@JoinColumn(name="EMP_ID"),
inverseJoinColumns=@JoinColumn(name="PROJ_ID"))
private Collection<Project> projects;
- default naming if no @JoinTable is used:
- join table name: <Owner>_<Inverse> (Owner and Inverse are the entity names), e.g. EMPLOYEE_PROJECT
- join columns name: <Owner>S_ID, <Inverse>S_ID, e.g. EMPLOYEES_ID, PROJECTS_ID
Unidirectional Collection Mapping
- consider a one-to-many mapping to a target but the @OneToMany annotation does not include mappedBy element, it is assumed to be in a unidirectional relationship with the target entity
- there is no join column, so we join table with @JoinTable
@Entity
public class Employee {
@OneToMany
@JoinTable(name="EMP_PHONE", joinColumns=@JoinColumn(name="EMP_ID"),
inverseJoinColumns=@JoinColumn(name="PHONE_ID"))
private Collection<Phone> phones;
}
- default naming of join columns slightly different in unidirectional case: <Owner>_ID (EMPLOYEE_ID) and <Inverse>S_ID (PHONES_ID)
- at relationship level lazy loading can enhance performance. It reduces the amount of SQL that gets executed.
- fetch mode can be specified on any of the four relationship mapping types
- If not specified
- on a single-valued relationship, the default is eager loading
- on a collection-valued relationship, the default is lazy loading as hint for the provider
@OneToOne(fetch=FetchType.LAZY)
private ParkingSpace parkingSpace;
- an embedded object depends on an entity and has no own identity. It is part of the entities identity and state.
- In Java embedded objects are refenced by an entity and appear like the target of an relationship. In database embedded objects are stored like the rest of the entity in the same row.
- @Embeddable annotation for the class definition of the embedded object, @Embedded annotation for the attribute of the entity where it is embedded to.
@Embeddable
public class Adress {
private String street;
private String city;
private String state;
private string zip;
}
@Entity
public class Employee {
@Embedded
private Address address;
}
- @AttributeOverride annotation can be used for each attribute of the embedded object that we want to override. If we want to override multiple attributes we can use @AttributeOverrides and nest multiple @AttributeOverride in it.
@Embedded
@AttributeOverrides({
@AttributeOverride(name="state", column=@Column(name="PROVINCE")),
@AttributeOverride(name="zip", column=@Column(name="POSTAL_CODE"))
})
private Address address;