Enterprise Java Development@TOPIC@
Built on: 2014-03-07 00:01 EST
Copyright © 2014 jim stafford (jcstaff@apl.jhu.edu)
Abstract
This presentation provides information for mapping Java classes (without relationships) to the database using JPA.
Introduce mapping a single class (void of relationships) to the database
Introduce primary key generation for synthetic key usage
Introduce composite keys for natural key usage
Introduce multi-table mappings for individual Java classes
At the completion of this topic, the student shall
have an understanding of:
Object/Relational Mapping (ORM) based on the Java Persistence API (JPA)
Class mapping strategies
Field mapping strategies
Primary key strategies
be able to:
Be able to map a single Java class and attributes to the database using class annotations and ORM descriptors
Be able to define a specific primary key strategy for their mapped class
Be able to create, get, set, and delete information from the database using class mappings
Plain Old Java Objects
Instantiated just like any other Java object
Bike bike = new Bike(1);
bike.setMake("trek");
bike.setModel("2200");
bike.setSize(26);Mapped to database schema
Interact with EntityManager to perform persistence operations
em.persist(bike); Bike bike2 = em.find(Bike.class, 1L); em.remove(bike);
Must
Have a no-arg constructor (spec says non-private; but hibernate can work with private)
Be identified as an Entity (either using class annotations or ORM descriptors)
Have at least one (1) @Id property to be used as a primary key
Figure 1.1. Annotated Class Example
package ejava.examples.orm.core.annotated;
import javax.persistence.*; //brings in JPA Annotations@Entity
public class Bike {@Id //tells ORM that this property provides pk simple value
private long id;
private String make;
private String model;
private int size;
public Bike() {} //required non-private default ctor
public Bike(long id) { this.id = id; }
public long getId() {
return id;
}
public String getMake() { return make; }
public void setMake(String make) {
this.make = make;
}
...
}
![]() | Tells provider to include in persistence unit |
![]() | Tells provider to use this property as the primary key |
![]() | Default ctor required for provider to instantiate from DB |
Figure 1.2. Mapped Class Example
package ejava.examples.orm.core.mapped;
public class Bike {private long id; //orm.xml file will map this field to Identity
private String make;
private String model;
private int size;
public Bike() {}
public Bike(long id) { this.id = id; }
public long getId() {
return id;
}
public String getMake() { return make; }
public void setMake(String make) {
this.make = make;
}
...
}
![]() | Must have a property to later define as a primary key |
![]() | Must have a default ctor |
Figure 1.3. Mapped Class ORM.xml Example
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
version="2.0">
<entity class="ejava.examples.orm.core.mapped.Bike"access="FIELD"
metadata-complete="true"
name="MappedBike">
<table name="Bike"/>
<attributes>
<id name="id"/></attributes>
</entity>
</entity-mappings>
![]() | Identifies class as an entity |
![]() | Identifies @Id property for a primary key |
Figure 1.4. Mapped Class META-INF/persistence.xml Reference
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0"><persistence-unit name="ormCore">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<mapping-file>orm/Bike-orm.xml</mapping-file>
<class>ejava.examples.orm.core.annotated.Bike</class>
...
</persistence-unit>
</persistence>
![]() | Reference to orm.xml to map Java class(es) to database and make part of persistence unit |
![]() | Reference to class that may/may not be within local archive but should be part of persistence unit |
Figure 1.5. Example Directory Structure
src/ |-- main | |-- java | | `-- ejava ... | | |-- annotated | || `-- Bike.java | | `-- mapped | |
`-- Bike.java | `-- resources | |-- META-INF | | `-
- persistence.xml | `-- or
m | `-- Bike-orm.xml `-- test `-- resour
ces `-- hibernate.properties
![]() | Annotated class |
![]() | Pure-POJO class mapped by orm.xml |
![]() | persistence.xml defines reference to orm.xml |
![]() | orm.xml defines Java class mapping to database |
![]() | Properties file with database runtime properties |
Defaults to unqualified name of entity class (e.g., Bike)
Used to determine database table name
Bike entity -> BIKE table
create table Bike (
id bigint not null,
make varchar(255),
model varchar(255),
size integer not null,
primary key (id)
)Used to refer to entity in JPA-QL
select b from Bike b
Can be assigned optional name property
@Entity(name="AnnotatedBike")
class Bike {
<entity class="ejava.examples.orm.core.mapped.Bike"
name="MappedBike">
Table name can be specified separate from entity name
@Entity(name="AnnotatedBike")
@Table(name="COREORM_BIKE")
class Bike {
<entity class="ejava.examples.orm.core.mapped.Bike"
name="MappedBike">
<table name="COREORM_BIKE"/>
FIELD AccessDirect access to Java variable
PROPERTY AccessAccessed through Java set/get methods
Placement of @Id class annotation determines default choice when using annotations
Figure 1.6. Defining FIELD Access using Annotations
@Entity
public class Bike {
@Id
private long id;
private String make;
Figure 1.7. Defining PROPERTY Access using Annotations
@Entity
public class Bike {
@Id
public long getId() { return id; }
protected void setId(int id) {
this.id=id;
}
public String getMake() { return make; }
public void setMake(String make) {
this.make = make;
}
Per-property access override thru @Access
@Entity
public class Bike {
@Id
private long id;
private String make;
...
@Access(AccessType.PROPERTY)
public String getMake() { return make; }
public void setMake(String make) {
this.make = make;
}
Can be defined globally
<access>FIELD</access>
Can be defined per-class
<entity class="ejava.examples.orm.core.mapped.Bike"
access="FIELD">
Can be defined per-property
<entity class="ejava.examples.orm.core.mapped.Bike"
access="FIELD">
<attributes>
<id name="id"/>
<basic name="make" access="PROPERY">
</basic>
</attributes>
</entity>
Appropriate for quick prototypes
Most vendors provide tools to create database schema from entity classes
Generated schema will be consistent with Java class model and not optimized for any particular use
Less use of javax.persistence.* annotations because Java defaults mostly sufficient
Add more use of javax.persistence.* annotations to reach desired schema model
Common in most legacy and non-trivial environments
Most vendors provide tools to create entity classes from database schema (in addition to DAOs)
Generated entity classes will be consistent with database table design and not have any bearing on the business needs of the data tier
More use of javax.persistence.* annotations to get desired business objects mapped to desired database schema
@Entity.name - assigns entity name to entity class
@Table.name - assigns table name to entity class
@Column.name - assigns column name to entity property
Mostly used when generating schema
@Table
uniqueConstraints * - column groupings that will have a unique value
@Column
unique * - column will have unique value
nullable * - column may contain a null value/is optional
insertable - column will be included in SQL during initial insert
updatable - column will be included in SQL during follow-on updates
table - table for column in a multi-table mapping
length * - length of column for Strings
precision * - defines number of digits used for Numeric types (e.g., 100.02 is precision=5)
scale * - defines number of digits to use to the right of decimal place (e.g., 100.01 is scale=2)
columnDefinition * - custom DDL for column creation
* - Used only in schema generation
create table ORMCORE_CAR (
CAR_ID bigint not null,
CAR_COST decimal(7,2),
CAR_MAKE varchar(20) not null,
CAR_MODEL varchar(20) not null,
CAR_YEAR integer not null,
primary key (CAR_ID)
)@Entity@Table(name="ORMCORE_CAR")
public class Car {
@Id@Column(name="CAR_ID", nullable=false)
private long id;@Column(name="CAR_MAKE",
unique=false,
nullable=false,
insertable=true,
updatable=true,
length=20)
private String make;@Column(name="CAR_MODEL", nullable=false, length=20)
private String model;@Column(name="CAR_YEAR", nullable=false)
private int year;@Column(name="CAR_COST", precision=7, scale=2)
private BigDecimal cost;
...
<entity class="ejava.examples.orm.core.mapped.Car"
access="FIELD"><table name="ORMCORE_CAR"/>
<attributes>
<id name="id"><column name="CAR_ID" nullable="false"/>
</id>
<basic name="make"><column name="CAR_MAKE"
nullable="false"
insertable="true"
updatable="true"
length="20"/>
</basic>
<basic name="model"><column name="CAR_MODEL" nullable="false" length="20"/>
</basic>
<basic name="year"><column name="CAR_YEAR" nullable="false"/>
</basic>
<basic name="cost"><column name="CAR_COST" precision="7" scale="2"/>
</basic>
</attributes>
</entity>
Figure 2.1. More on Scale
//precision defined in ORM as precision=7, scale=2
car.setCost(new BigDecimal("12345.66"));
em.persist(car);
em.flush(); em.clear();
//get a fresh copy from the DB
Car car2 = em.find(Car.class, car.getId());
log.info("car.cost=" + car.getCost());
log.info("car2.cost=" + car2.getCost());
assertTrue("unexpectected value", car.getCost().equals(car2.getCost()));
//update beyond the scale values -- too many digits to right of decimal
car2.setCost(new BigDecimal("1234.666"));
em.flush(); em.clear();
Car car3 = em.find(Car.class, car.getId());
log.info("car2.cost=" + car2.getCost());
log.info("car3.cost=" + car3.getCost());
assertFalse("unexpected scale", car2.getCost().equals(car3.getCost()));
-car.cost=12345.66 //Value created within scale=2 -car2.cost=12345.66 //Value correctly retrieved from DB -car2.cost=1234.666 //Value goes beyond scale=2 -car3.cost=1234.67 //Value rounded down to scale from DB
Figure 2.2. More on Precision
//update beyond the precision values -- too many digits overall
car2 = car3;
car2.setCost(new BigDecimal("123456.66"));
try {
em.flush();
fail("database accepted too many digits");
} catch (PersistenceException ex) {
log.info("caught expected exception:" + ex);
}
-caught expected exception:javax.persistence.PersistenceException: org.hibernate.exception.DataException: could not execute statement
Entity using PROPERTY access
Entity class contains property that complies with default rules that should *not* be mapped to database
setters
getters
relations to other classes, including collections
Figure 2.3. Transient Example Entity Class
@Entity
@Table(name="ORMCORE_TANK")
public class Tank {
private long id;
private String make;
private String model;
public Tank() {}
public Tank(long id) { this.id = id; }
@Id
public long getId() { return id; }
protected void setId(long id) {
this.id = id;
}
@Transient //if you remove this, it will fail trying to locate setter
public String getMakeModel() {
return make + " " + model;
}
public String getMake() { return make; }
public void setMake(String make) {
this.make = make;
}
public String getModel() { return model; }
public void setModel(String model) {
this.model = model;
}
Figure 2.4. Transient Example orm.xml
<entity class="ejava.examples.orm.core.mapped.Tank" access="PROPERTY">
<table name="ORMCORE_TANK"/>
<attributes>
<id name="id"/>
<transient name="makeModel"/>
</attributes>
</entity>
Figure 2.5. Transient Example Database Schema
create table ORMCORE_TANK (
id bigint not null,
make varchar(255),
model varchar(255),
primary key (id)
)Figure 2.6. Transient Example Test
ejava.examples.orm.core.annotated.Tank tank = new Tank(1);
tank.setMake("acme");
tank.setModel("great guns");
//insert a row in the database
em.persist(tank);
log.info("created tank:" + tank);
-created tank:ejava.examples.orm.core.annotated.Tank@4eef4eb7make=acme, model=great guns
Storing large text document as CLOB
Storing large binary data as BLOB
Figure 2.7. CLOB Example Database Schema
create table ORMCORE_UMBRELLA (
id bigint not null,
make clob,
model varchar(255),
primary key (id)
)Figure 2.8. CLOB Example Entity Class
@Entity
@Table(name="ORMCORE_UMBRELLA")
public class Umbrella {
@Lob
@Basic(fetch=FetchType.LAZY) //ignored
public char[] getMake() {
return make.toCharArray();
}
public void setMake(char[] make) {
this.make = new String(make);
}
Figure 2.9. Temporal/Enum Example Schema
create table ORMCORE_VASE (
id bigint not null,
aDate date,
aTime time,
aTimestamp timestamp,
colorId integer,
colorName varchar(255),
primary key (id)
)
Figure 2.10. Temporal/Enum Example Entity Class
@Entity
@Table(name="ORMCORE_VASE")
public class Vase {
@Id
private long id;
@Temporal(TemporalType.DATE)
private Date aDate;
@Temporal(TemporalType.TIME)
private Date aTime;
@Temporal(TemporalType.TIMESTAMP)
private Date aTimestamp;
@Enumerated(EnumType.ORDINAL)
private ColorType colorId;
@Enumerated(EnumType.STRING)
private ColorType colorName;
Figure 2.11. Temporal/Enum Example orm.xml
<entity class="ejava.examples.orm.core.mapped.Vase" access="FIELD">
<table name="ORMCORE_VASE"/>
<attributes>
<id name="id"/>
<basic name="aDate">
<temporal>DATE</temporal>
</basic>
<basic name="aTime">
<temporal>TIME</temporal>
</basic>
<basic name="aTimestamp">
<temporal>TIMESTAMP</temporal>
</basic>
<basic name="colorId">
<enumerated>ORDINAL</enumerated>
</basic>
<basic name="colorName">
<enumerated>STRING</enumerated>
</basic>
</attributes>
</entity>
Figure 2.12. Temporal/Enum Example Test
@Test
public void testValues() {
log.info("testValues");
ejava.examples.orm.core.annotated.Vase vase = new Vase(1);
Date date = new Date();
vase.setADate(date);
vase.setATime(date);
vase.setATimestamp(date);
vase.setColorId(ColorType.RED);
vase.setColorName(ColorType.RED);
//insert a row in the database
em.persist(vase);
log.info("created case:" + vase);
//find the inserted object
em.flush();
em.clear();
Vase vase2 = em.find(Vase.class, 1L);
log.info("found vase:" + vase2);
-created case:ejava.examples.orm.core.annotated.Vase@7f68f33c,
id=1,
aDate=Mon Sep 23 00:11:10 EDT 2013,
aTime=Mon Sep 23 00:11:10 EDT 2013,
aTimestamp=Mon Sep 23 00:11:10 EDT 2013,
colorId=RED,
colorName=RED
-found vase:ejava.examples.orm.core.annotated.Vase@5f0d8b74,
id=1,
aDate=2013-09-23,
aTime=00:11:10,
aTimestamp=2013-09-23 00:11:10.296,
colorId=RED,
colorName=RED
select * from ORMCORE_VASE ID ADATE ATIME ATIMESTAMP COLORID COLORNAME -- ---------- -------- ----------------------------- ------- --------- 1 2006-09-23 14:08:22 2006-09-23 14:08:22.221000000 0 RED
Notice impact of temporal DB-mapping does effect until data saved and retrieved from database
Every entity must have a primary key
Primary keys must be unique
Map to one ("simple") or more ("composite") properties
Properties must be of type
Java primitive types -- including object wrappers
java.lang.String
Custom classes made up of legal property types
Persistence providers required to provide primary key generation
Specific type of generator specified through a strategy
Figure 3.1. Specifying Primary Key Generation thru Annotations
@Entity
@Table(name="ORMCORE_DRILL")
public class Drill {
@Id
@GeneratedValue( //AUTO is the default and could be left off here
strategy=GenerationType.AUTO)
private long id; //unassigned PK value must be zero
private String make;
...
Figure 3.2. Specifying Primary Key Generation thru orm.xml
<entity class="ejava.examples.orm.core.mapped.Drill" access="FIELD">
<table name="ORMCORE_DRILL"/>
<attributes>
<id name="id">
<generated-value strategy="AUTO"/>
</id>
</attributes>
</entity>
Three (+1) Types
SEQUENCEDatabase generates unique value from a global sequence
IDENTITYDatabase generates unique value on a per-table basis
TABLEAUTOProvider may choose any technique, including one not specified above
Figure 3.3. Entity with GenerationType.AUTO
@Entity
@Table(name="ORMCORE_DRILL")
public class Drill {
@Id
@GeneratedValue
private long id;
private String make;
...
Figure 3.4. AUTO (Success) Test
@Test
public void testAUTOGood() {
log.info("testAUTOGood");
//note that since PKs are generated, we must pass in an object that
//has not yet been assigned a PK value.
ejava.examples.orm.core.annotated.Drill drill = new Drill(0);
drill.setMake("acme");
//insert a row in the database
em.persist(drill);
log.info("created drill:" + drill);
assertFalse(drill.getId() == 0L);
}
-testAUTOGood
Hibernate:
insert
into
ORMCORE_DRILL
(id, make)
values
(null, ?)
-created drill:ejava.examples.orm.core.annotated.Drill@35853853, id=1, make=acmeFigure 3.5. AUTO (Failure) Test
@Test
public void testAUTOBad() {
log.info("testAUTOBad");
//he's not going to like they non-zero PK value here
ejava.examples.orm.core.annotated.Drill drill = new Drill(25L);
drill.setMake("BD");
//insert a row in the database
boolean exceptionThrown = false;
try {
assertFalse(drill.getId() == 0L);
log.info("trying to create drill with pre-exist pk:" + drill);
em.persist(drill);
}
catch (PersistenceException ex) {
log.info("got expected exception: " + ex);
exceptionThrown = true;
}
assertTrue(exceptionThrown);
}
-testAUTOBad
-trying to create drill with pre-exist pk:ejava.examples.orm.core.annotated.Drill@76160af2, id=25, make=BD
-got expected exception: javax.persistence.PersistenceException:
org.hibernate.PersistentObjectException:
detached entity passed to persist: ejava.examples.orm.core.annotated.Drill
Figure 3.6. Entity with GenerationType.IDENTITY
@Entity
@Table(name="ORMCORE_GADGET")
public class Gadget {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private long id;
private String make;
Figure 3.7. IDENTITY Database Schema
create table ORMCORE_GADGET (
id bigint generated by default as identity,
make varchar(255),
primary key (id)
)Figure 3.8. IDENTITY Test
@Test
public void testIDENTITY() {
ejava.examples.orm.core.annotated.Gadget gadget = new Gadget(0);
gadget.setMake("gizmo 1");
//insert a row in the database
em.persist(gadget);
log.info("created gadget (before flush):" + gadget);
em.flush();
log.info("created gadget (after flush):" + gadget);
assertFalse(gadget.getId() == 0L);
}
-testIDENTITY
Hibernate:
insert
into
ORMCORE_GADGET
(id, make)
values
(null, ?)
-created gadget (before flush):ejava.examples.orm.core.annotated.Gadget@7b61257e, id=1, make=gizmo 1
-created gadget (after flush):ejava.examples.orm.core.annotated.Gadget@7b61257e, id=1, make=gizmo 1
Figure 3.9. Follow-on IDENTITY Allocations
Hibernate:
insert
into
ORMCORE_GADGET
(id, make)
values
(null, ?)
-created gadget:ejava.examples.orm.core.annotated.Gadget@581e495d, id=2, make=gizmo 2
Hibernate:
insert
into
ORMCORE_GADGET
(id, make)
values
(null, ?)
-created gadget:ejava.examples.orm.core.annotated.Gadget@1f06d526, id=3, make=gizmo 3
Hibernate:
insert
into
ORMCORE_GADGET
(id, make)
values
(null, ?)
-created gadget:ejava.examples.orm.core.annotated.Gadget@199bdabd, id=4, make=gizmo 4
...The provider must obtain the next primary key value from the database each time. Notice in the first case above -- the provider has already flushed the INSERT to the database during the persist and before out manual call to flush()
Figure 3.10. Entity with GenerationType.SEQUENCE
@Entity
@Table(name="ORMCORE_FAN")
@SequenceGenerator(
name="fanSequence", //required logical name
sequenceName="FAN_SEQ", //name in database
initialValue=4, //start with something odd to be noticeable
allocationSize=3) //number of IDs to internally assign per-sequence value
public class Fan {
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, //use DB sequence
generator="fanSequence") //point to logical def
private long id;
private String make;
...
Figure 3.11. SEQUENCE Database Schema
create table ORMCORE_FAN (
id bigint not null,
make varchar(255),
primary key (id)
)
create sequence FAN_SEQFigure 3.12. SEQUENCE Test
@Test
public void testSEQUENCE() {
log.info("testSEQUENCE");
//note that since PKs are generated, we must pass in an object that
//has not yet been assigned a PK value.
ejava.examples.orm.core.annotated.Fan fan = new Fan(0);
fan.setMake("cool runner 1");
//insert a row in the database
em.persist(fan);
log.info("created fan (before flush):" + fan);
em.flush();
log.info("created fan (after flush):" + fan);
assertFalse(fan.getId() == 0L);
}
-testSEQUENCE
Hibernate:
call next value for FAN_SEQ
-created fan (before flush):ejava.examples.orm.core.annotated.Fan@e18a174, id=3, make=cool runner 1
Hibernate:
insert
into
ORMCORE_FAN
(make, id)
values
(?, ?)
-created fan (after flush):ejava.examples.orm.core.annotated.Fan@e18a174, id=3, make=cool runner 1Figure 3.13. Follow-on SEQUENCE Allocations
-created fan:ejava.examples.orm.core.annotated.Fan@6806da29, id=4, make=cool runner 2
-created fan:ejava.examples.orm.core.annotated.Fan@77d5a139, id=5, make=cool runner 3
-
call next value for FAN_SEQ
-created fan:ejava.examples.orm.core.annotated.Fan@1c0cf528, id=6, make=cool runner 4
-created fan:ejava.examples.orm.core.annotated.Fan@27c3a4a3, id=7, make=cool runner 5
-created fan:ejava.examples.orm.core.annotated.Fan@17f7ed6e, id=8, make=cool runner 6
-
call next value for FAN_SEQ
...The provider generates allocationSize primary key values before performing a flush or follow-on poll of the sequence. An allocationSize greater than one (1) is much less communication with the database than the IDENTITY strategy -- which would be somewhat analogous to a SEQUENCE allocationSize=1.
Figure 3.14. Entity with GenerationType.TABLE
@Entity
@Table(name="ORMCORE_EGGBEATER")
@TableGenerator( //note that all but name are optional if generating schema
name="eggbeaterGenerator", //logical name of generator
table="ORMCORE_EB_UID", //name of table storing seq
pkColumnName="UID_ID", //pk column for seq table
pkColumnValue="ORMCORE_EGGBEATER", //pk value in pk column
valueColumnName="UID_VAL", //column for seq value
allocationSize=5 //increment UID_ID after using this many
)
public class EggBeater {
@Id
@GeneratedValue(strategy=GenerationType.TABLE, //use DB table
generator="eggbeaterGenerator") //point to logical def
private long id;
private String make;
...
Figure 3.15. TABLE Database Schema
create table ORMCORE_EGGBEATER (
id bigint not null,
make varchar(255),
primary key (id)
)
create table ORMCORE_EB_UID (
UID_ID varchar(255),
UID_VAL integer
)Figure 3.16. TABLE Test
@Test
public void testTABLE() {
log.info("testTABLE");
log.debug("table id before=" + getTableId());
//note that since PKs are generated, we must pass in an object that
//has not yet been assigned a PK value.
ejava.examples.orm.core.annotated.EggBeater eggbeater = new EggBeater(0);
eggbeater.setMake("done right");
//insert a row in the database
em.persist(eggbeater);
log.info("created eggbeater (before flush):" + eggbeater);
em.flush();
log.info("created eggbeater (after flush):" + eggbeater);
assertFalse(eggbeater.getId() == 0L);
}
-testTABLE
Hibernate:
select
UID_VAL
from
ORMCORE_EB_UID
where
UID_ID='ORMCORE_EGGBEATER'
-table id before=null
Hibernate:
select
UID_VAL
from
ORMCORE_EB_UID
where
UID_ID = 'ORMCORE_EGGBEATER' for update
Hibernate:
insert
into
ORMCORE_EB_UID
(UID_ID, UID_VAL)
values
('ORMCORE_EGGBEATER', ?)
Hibernate:
update
ORMCORE_EB_UID
set
UID_VAL = ?
where
UID_VAL = ?
and UID_ID = 'ORMCORE_EGGBEATER'
-created eggbeater (before flush):ejava.examples.orm.core.annotated.EggBeater@4eb8b5a9, id=1, make=done right
Hibernate:
insert
into
ORMCORE_EGGBEATER
(make, id)
values
(?, ?)
-created eggbeater (after flush):ejava.examples.orm.core.annotated.EggBeater@4eb8b5a9, id=1, make=done right
Figure 3.17. Follow-on TABLE Allocations
-table id after=1
-created ehhbeater:ejava.examples.orm.core.annotated.EggBeater@3576465f, id=2, make=null
-table id after[2]=1
-created ehhbeater:ejava.examples.orm.core.annotated.EggBeater@306435cd, id=3, make=null
-table id after[3]=1
-created ehhbeater:ejava.examples.orm.core.annotated.EggBeater@35194a50, id=4, make=null
-table id after[4]=1
Hibernate:
select
UID_VAL
from
ORMCORE_EB_UID
where
UID_ID = 'ORMCORE_EGGBEATER' for update
Hibernate:
update
ORMCORE_EB_UID
set
UID_VAL = ?
where
UID_VAL = ?
and UID_ID = 'ORMCORE_EGGBEATER'
-created ehhbeater:ejava.examples.orm.core.annotated.EggBeater@288c819b, id=5, make=null
-table id after[5]=2
-created ehhbeater:ejava.examples.orm.core.annotated.EggBeater@10508cb2, id=6, make=null
-table id after[6]=2
-created ehhbeater:ejava.examples.orm.core.annotated.EggBeater@49250068, id=7, make=null
-table id after[7]=2
-created ehhbeater:ejava.examples.orm.core.annotated.EggBeater@372b2a85, id=8, make=null
-table id after[8]=2
-created ehhbeater:ejava.examples.orm.core.annotated.EggBeater@5e69cd5e, id=9, make=null
-table id after[9]=2
Hibernate:
select
UID_VAL
from
ORMCORE_EB_UID
where
UID_ID = 'ORMCORE_EGGBEATER' for update
Hibernate:
update
ORMCORE_EB_UID
set
UID_VAL = ?
where
UID_VAL = ?
and UID_ID = 'ORMCORE_EGGBEATER'
-created ehhbeater:ejava.examples.orm.core.annotated.EggBeater@1dbf9510, id=10, make=null
-table id after[10]=3
...As will SEQUENCE, the TABLE strategy allows each client to generate an allocationSize amount of primary key values before requiring a flush of the current batch or polling for a new table value.
Primary key consisting of multiple properties
Represented using a primary key class
Must implement Serializable
public class MowerPK implements Serializable {
Must have a public no-arg constructor
public MowerPK() {}
Must implement hashCode() and equals() methods
@Override
public int hashCode() { ... }
@Override
public boolean equals(Object obj) { ... }
Must define properties that match with the definition of the entity, including access
private String make; private String model;
Accessed using same strategy as entity using it
package ejava.examples.orm.core;
import java.io.Serializable;
public class MowerPK implements Serializable {
private static final long serialVersionUID = 1L;
private String make;
private String model;
public MowerPK() {}
public MowerPK(String make, String model) {
this.make = make;
this.model = model;
}
public String getMake() { return make; }
public String getModel() { return model; }
@Override
public int hashCode() { return make.hashCode() + model.hashCode(); }
@Override
public boolean equals(Object obj) {
try {
if (this == obj) return true;
return make.equals(((MowerPK)obj).getMake()) &&
model.equals(((MowerPK)obj).getModel());
} catch (Throwable ignored) { //catch NP & Cast Exceptions
return false;
}
}
...
}
A class separate from the entity that independently models PK properties
No reference to the @IdClass within the entity
Figure 4.1. Composite @IdClass Database Schema
create table ORMCORE_MOWER (
make varchar(255) not null,
model varchar(255) not null,
size integer not null,
primary key (make, model)
)Figure 4.2. Composite @IdClass Example Usage
@Entity
@Table(name="ORMCORE_MOWER")
@IdClass(MowerPK.class)
public class Mower {
@Id
private String make;
@Id
private String model;
private int size;
public Mower() {}
public Mower(String make, String model) {
this.make = make;
this.model = model;
}
public String getMake() { return make; }
public String getModel() { return model; }
Figure 4.3. Composite @IdClass Example orm.xml
<entity class="ejava.examples.orm.core.mapped.Mower" access="FIELD">
<table name="ORMCORE_MOWER"/>
<id-class class="ejava.examples.orm.core.MowerPK"/>
<attributes>
<id name="make"/>
<id name="model"/>
</attributes>
</entity>
Figure 4.4. Composite @IdClass Example Client
@Test
public void testIdClass() {
ejava.examples.orm.core.annotated.Mower mower =
new Mower("acme", "power devil2");
mower.setSize(21);
//insert a row in the database
em.persist(mower);
log.info("created mower:" + mower);
-created mower:ejava.examples.orm.core.annotated.Mower@2c137a23, make=acme, model=power devil2, size=21
Mower mower2 =
em.find(Mower.class, new MowerPK("acme", "power devil2"));
assertNotNull(mower2);
log.info("found mower:" + mower2);
assertEquals(mower.getSize(), mower2.getSize());
-found mower:ejava.examples.orm.core.annotated.Mower@2c137a23, make=acme, model=power devil2, size=21
A class that hosts the primary key properties
Contained/"embedded" within the entity class
Figure 4.5. @EmbeddedId Example Database Schema
create table ORMCORE_NAPSACK (
NAPSACK_MAKE varchar(255) not null,
NAPSACK_MODEL varchar(255) not null,
size integer not null,
primary key (NAPSACK_MAKE, NAPSACK_MODEL)
)
Figure 4.6. @Embeddable Primary Key Class
package ejava.examples.orm.core.mapped;
import java.io.Serializable;
import javax.persistence.*;
@Embeddable
public class NapsackPK implements Serializable {
private static final long serialVersionUID = 1L;
@Column(name="NAPSACK_MAKE") //maps field to column of containing class
private String make;
@Column(name="NAPSACK_MODEL")//maps field to column of containing class
private String model;
public NapsackPK() {}
public NapsackPK(String make, String model) {
this.make = make;
this.model = model;
}
public String getMake() { return make; }
public String getModel() { return model; }
public int hashCode() { return make.hashCode() + model.hashCode(); }
public boolean equals(Object obj) {
try {
if (this == obj) return true;
return make.equals(((NapsackPK)obj).getMake()) &&
model.equals(((NapsackPK)obj).getModel());
} catch (Throwable ignored) { //catch NP & Cast Exceptions
return false;
}
}
Figure 4.7. Composite @EmbeddedId Example Usage
@Entity
@Table(name="ORMCORE_NAPSACK")
public class Napsack {
@EmbeddedId
private NapsackPK pk;
private int size;
public Napsack() {}
public Napsack(String make, String model) {
this.pk = new NapsackPK(make, model);
}
public NapsackPK getPk() { return pk; }
...
Figure 4.8. Composite @EmbeddedId Example orm.xml
<entity class="ejava.examples.orm.core.mapped.Napsack" access="FIELD">
<table name="ORMCORE_NAPSACK"/>
<attributes>
<embedded-id name="pk"/>
</attributes>
</entity>
<embeddable class="ejava.examples.orm.core.mapped.NapsackPK">
<attributes>
<basic name="make">
<column name="NAPSACK_MAKE"/>
</basic>
<basic name="model">
<column name="NAPSACK_MODEL"/>
</basic>
</attributes>
</embeddable>
Supply or override mapping of primary key class by entity class
Figure 4.9. Example Overridden @Embeddable PK Class
@Embeddable
public class MakeModelPK implements Serializable {
private String make;
private String model;
Figure 4.10. Example Overridden @Embeddable Entity Schema
create table ORMCORE_PEN (
PEN_MAKE varchar(255) not null,
PEN_MODEL varchar(255) not null,
size integer not null,
primary key (PEN_MAKE, PEN_MODEL)
)Figure 4.11. Example Overridden @Embeddable Entity Class
@Entity
@Table(name="ORMCORE_PEN")
public class Pen {
@EmbeddedId
@AttributeOverrides({
@AttributeOverride(name="make", column=@Column(name="PEN_MAKE")),
@AttributeOverride(name="model", column=@Column(name="PEN_MODEL"))
})
private MakeModelPK pk;
private int size;
public Pen() {}
public Pen(String make, String model) {
this.pk = new MakeModelPK(make, model);
}
public MakeModelPK getPk() { return pk; }
Figure 4.12. Example Overridden @Embeddableorm.xml
<entity class="ejava.examples.orm.core.mapped.Pen" access="FIELD">
<table name="ORMCORE_PEN"/>
<attributes>
<embedded-id name="pk">
<attribute-override name="make">
<column name="PEN_MAKE"/>
</attribute-override>
<attribute-override name="model">
<column name="PEN_MODEL"/>
</attribute-override>
</embedded-id>
</attributes>
</entity>
<embeddable class="ejava.examples.orm.core.mapped.MakeModelPK">
<attributes>
<basic name="make"/>
<basic name="model"/>
</attributes>
</embeddable>
PKClass Requirements
Serializable
No-arg constructor
hashCode() and equals()
PK properties
@IdClass
Independent class containing copy of entity PK properties
@Embeddable
Class instance containing entity PK properties -- embedded within entity
DB Mapping
Primary in PKClass
Overrides in Entity class
@AttributeOverrides
Map a single Java class to multiple tables in database
Uses a join of two or more tables and a single Java class
One table is designated as primary
Secondary and primary must have a column to join
Three tables (one primary and two secondary) joined
Figure 5.1. Multi-table Mapping Example Database Schema
create table ORMCORE_WATCH (
id bigint not null,
make varchar(255),
model varchar(255),
primary key (id)
)create table ORMCORE_OWNER (
cardnum varchar(255),
NAME varchar(255),
OWNER_ID bigint not null,
primary key (OWNER_ID)
)create table ORMCORE_MAKER (
address varchar(255),
NAME varchar(255),
phone varchar(255),
MAKER_ID bigint not null,
primary key (MAKER_ID)
)alter table ORMCORE_MAKER
add constraint FKB91D15CEC0CE58E2
foreign key (MAKER_ID)
references ORMCORE_WATCH
alter table ORMCORE_OWNER
add constraint FKB943503D985CE28
foreign key (OWNER_ID)
references ORMCORE_WATCHFigure 5.2. Multi-table Mapping Example Entity Class
@Entity
@Table(name="ORMCORE_WATCH")
@SecondaryTables({
@SecondaryTable(name="ORMCORE_OWNER",
pkJoinColumns={
@PrimaryKeyJoinColumn(name="OWNER_ID")}),
@SecondaryTable(name="ORMCORE_MAKER",
pkJoinColumns={
@PrimaryKeyJoinColumn(name="MAKER_ID")})
})
public class Watch {
@Id
private long id;
private String make;
private String model;
@Column(name="NAME", table="ORMCORE_OWNER")
private String owner;
@Column(table="ORMCORE_OWNER")
private String cardnum;
@Column(name="NAME", table="ORMCORE_MAKER")
private String manufacturer;
@Column(table="ORMCORE_MAKER")
private String address;
@Column(table="ORMCORE_MAKER")
private String phone;
Figure 5.3. Multi-table Mapping Example orm.xml
<entity class="ejava.examples.orm.core.mapped.Watch" access="FIELD">
<table name="ORMCORE_WATCH"/>
<secondary-table name="ORMCORE_OWNER">
<primary-key-join-column name="OWNER_ID"/>
</secondary-table>
<secondary-table name="ORMCORE_MAKER">
<primary-key-join-column name="MAKER_ID"/>
</secondary-table>
<attributes>
<id name="id"/>
<basic name="owner">
<column name="NAME" table="ORMCORE_OWNER"/>
</basic>
<basic name="cardnum">
<column table="ORMCORE_OWNER"/>
</basic>
<basic name="manufacturer">
<column name="NAME" table="ORMCORE_MAKER"/>
</basic>
<basic name="address">
<column table="ORMCORE_MAKER"/>
</basic>
<basic name="phone">
<column table="ORMCORE_MAKER"/>
</basic>
</attributes>
</entity>
Figure 5.4. Multi-table Mapping Example Test
//leave a watch in DB to inspect
Watch watch3 = new Watch(3);
watch3.setMake("ontime3");
watch3.setModel("round-and-round3");
watch3.setOwner("john doe3");
watch3.setCardnum("123-45-67893");
watch3.setManufacturer("getter done3");
watch3.setAddress("12noon lane3");
watch3.setPhone("410-555-12123");
em.persist(watch3);select * from ORMCORE_WATCH ID MAKE MODEL 3 ontime3 round-and-round3
select * from ORMCORE_MAKER NAME PHONE ADDRESS MAKER_ID getter done3 410-555-12123 12noon lane3 3
select * from ORMCORE_OWNER CARDNUM NAME OWNER_ID 123-45-67893 john doe3 3
Similar to @EmbeddedId except embedded object is not a primary key
Embedded object has no primary key -- housed in parent entity table
Embedded object represents some abstraction
Parent entity represents an abstraction with a primary key and the embedded object
The primary key could be the only thing the parent entity is providing
Figure 6.1. Embedded Object Example Database Schema
create table ORMCORE_XRAY (
id bigint not null,
address varchar(255),
XRAY_MAKER varchar(255),
phone varchar(255),
model varchar(255),
primary key (id)
)Figure 6.2. Embedded Object Example Embedded Class
@Embeddable
public class Manufacturer {
private String name;
private String address;
private String phone;
Figure 6.3. Embedded Object Example Entity Class
@Entity
@Table(name="ORMCORE_XRAY")
public class XRay {
@Id
private long id;
@Embedded
@AttributeOverrides({
@AttributeOverride(name="name", column=@Column(name="XRAY_MAKER"))
//note that we are letting address and phone default
})
private Manufacturer maker;
private String model;
Figure 6.4. Embedded Object Example orm.xml
<entity class="ejava.examples.orm.core.mapped.XRay" access="FIELD">
<table name="ORMCORE_XRAY"/>
<attributes>
<id name="id"/>
<embedded name="maker">
<attribute-override name="name">
<column name="XRAY_MAKER"/>
</attribute-override>
<!-- address and phone will default to a column name -->
</embedded>
<!-- model will default to a column name -->
</attributes>
</entity>
<embeddable class="ejava.examples.orm.core.mapped.Manufacturer" access="FIELD"/>
Figure 6.5. Embedded Object Example Test
XRay xray3 = new XRay(3);
xray3.setModel("inside-counts");
xray3.setMaker(
new Manufacturer("hi-tech", "low valley", "410-555-1212"));
em.persist(xray3);
ID PHONE ADDRESS XRAY_MAKER MODEL -- ------------ ---------- ---------- ------------- 3 410-555-1212 low valley hi-tech inside-counts