990 likes | 1.02k Views
Java Persistence: Core Object/Relational Mapping. Goals. Be able to map a single class and properties to the database using class annotations and ORM descriptors Be able to get and set properties using get/set API calls instead of SQL. Objectives. Entities class annotations
E N D
Java Persistence: Core ORM Java Persistence:Core Object/RelationalMapping
Java Persistence: Core ORM Goals • Be able to map a single class and properties to the database using class annotations and ORM descriptors • Be able to get and set properties using get/set API calls instead of SQL
Java Persistence: Core ORM Objectives • Entities • class annotations • orm.xml descriptor • Primary Key Generation • Primary Keys • simple • composite • Fine Tuning Objects • Multi-table Mappings • Secondary Tables • Embedded Objects
Java Persistence: Core ORM Entities • Plain Old Java Object (POJO) Classes • Instantiated and used just like any other POJO Bike bike = new Bike(1); bike.setMake("trek"); bike.setModel("2200"); bike.setSize(26); • Mapped to the database schema • Interact with EntityManager to perform persistence operations em.persist(bike); Bike bike2 = em.find(Bike.class, 1); em.remove(bike2); • Must • have a no-arg constructor (spec says non-private; hibernate can work with private) • be denoted as an Entity (either within class or descriptor) • have at least 1 field/property labeled as Id
Java Persistence: Core ORM Annotated Entity Example: Bike.java package ejava.examples.orm.core.annotated; import javax.persistence.*; //brings in JPA Annotations @Entity //tells ORM that this class can be mapped public class Bike { private long id; private String make; private String model; private int size; public Bike() {} public Bike(long id) { this.id = id; } @Id //tells ORM that this property provides pk simple value public long getId() { return id; } @SuppressWarnings("unused") //ORM will use this to set pk value private void setId(long id) { this.id = id; } public String getMake() { return make; } public void setMake(String make) { this.make = make; } ... }
Java Persistence: Core ORM Non-Annotated Entity Example: Bike.java package ejava.examples.orm.core.mapped; public class Bike { private long id; private String make; private String model; private int size; public Bike() {} public Bike(long id) { this.id = id; } //orm.xml file will map this field to Identity public long getId() { return id; } @SuppressWarnings("unused") //ORM will use this to set pk value private void setId(long id) { this.id = id; } public String getMake() { return make; } public void setMake(String make) { this.make = make; } ... }
Java Persistence: Core ORM Entity Example: META-INF/persistence.xml <?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_1_0.xsd" version="1.0"> <persistence-unit name="ormCore"> <!-- These could have been rolled into a single file. META-INF/orm.xml is the default location for a single file approach. --> <mapping-file>orm/Bike-orm.xml</mapping-file> <mapping-file>orm/Car-orm.xml</mapping-file> ... <properties> <property name="hibernate.hbm2ddl.auto" value="create"/> ... </properties> </persistence-unit> </persistence>
Java Persistence: Core ORM Non-Annotated Entity Example: Bike-orm.xml <?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_1_0.xsd" version="1.0"> <entity class="ejava.examples.orm.core.mapped.Bike" access="PROPERTY" metadata-complete="true" name="MappedBike"> <!-- name used to avoid name collision with annotated Bike example --> <table name="Bike"/> <attributes> <id name="id"/> </attributes> </entity> </entity-mappings>
Java Persistence: Core ORM Example Directory Layout target/classes/ |-- META-INF | `-- persistence.xml |-- ejava | `-- examples | `-- orm | `-- core | |-- annotated | | |-- Bike.class | `-- mapped | |-- Bike.class `-- orm |-- Bike-orm.xml
Java Persistence: Core ORM Entity Name • Defaults to unqualified name of entity class • Used to determine default table name • Bike class -> Bike table • SQL from Hypersonic log... create table Bike (id bigint not null, make varchar(255), size integer not null, model varchar(255), primary key (id)) • Used to refer to class in EJB-QL • Can be assigned using optional name property • @Entity(name=”entity_name”) • Table name can be specified separate from entity name • @Table(name=”table_name”)
Java Persistence: Core ORM Field/Property Access using @Id • Labels fields/properties used for primary key • Determines access type for remaining values • field – direct access to fields within class @Entity public class Bike { @Id private long id; • property – access through getter/setter methods @Entity public class Bike { private long id; ... @Id //annotation must be on getter method; not setter public long getId() { return id; } private void setId(long id) { this.id = id; }
Java Persistence: Core ORM Field/Property Access using orm.xml • FIELD <entity class="ejava.examples.orm.core.mapped.Bike" access="FIELD" metadata-complete="true" name="MappedBike"> <table name="Bike"/> <attributes> <id name="id"/> </attributes> </entity> • PROPERTY <entity class="ejava.examples.orm.core.mapped.Bike" access="PROPERTY" metadata-complete="true" name="MappedBike"> <table name="Bike"/> <attributes> <id name="id"/> </attributes> </entity>
Java Persistence: Core ORM Getting Started • Java First • appropriate for quick prototypes • most vendors provide tools to create database schema from Java classes • database schema maps closely to Java classes and has not been optimized • less use of javax.persistence metadata because defaults sufficient for functionality of prototype • Database Schema First • common in legacy environments and large developments • vendors provide tools to create Java classes from database schema • Java classes map closely to database schema and do not necessarily represent the business or uses of the application • more use of javax.persistence metadata required to use proper business classes with existing schema
Java Persistence: Core ORM Elementary Schema Mappings • Core Mappings • @Table.name – assigns table name • @Column.name – assigns column name • More detailed attributes • only necessary when generating schema • @Column • unique – column will be defined as being unique • nullable – column will be defined as being nullable • insertable – column will be included in inserts • updatable – column will be included in updates • table – maps column to table separate from class • length – length of VARCHAR() when using a String • precision – defines # digits in NUMERIC types • scale – defines # digits to right of decimal in NUMERIC types • columnDefinition – exact DDL to use for schema
Java Persistence: Core ORM Example Database Schema for Mapping create table ORMCORE_CAR ( CAR_ID bigint not null, CAR_YEAR integer not null, CAR_MODEL varchar(20) not null, CAR_MAKE varchar(20) not null, CAR_COST double, primary key (CAR_ID) )
Java Persistence: Core ORM Database Mappings using Annotations package ejava.examples.orm.core.annotated; import javax.persistence.*; @Entity @Table(name="ORMCORE_CAR", schema="PUBLIC") public class Car { private long id; private String make; private String model; private int year; private BigInteger cost; public Car() {} public Car(long id) { this.id = id; } @Id @Column(name="CAR_ID", nullable=false) public long getId() { return id; } @SuppressWarnings("unused") private void setId(long id) { this.id = id; }
Java Persistence: Core ORM Database Mappings using Annotations (cont.) @Column(name="CAR_MAKE", unique=false, nullable=false, insertable=true, updatable=true, table="", //note: we can point to another table to get prop length=20) public String getMake() { return make; } public void setMake(String make) { this.make = make; } @Column(name="CAR_MODEL", nullable=false, length=20) public String getModel() { return model; } public void setModel(String model) { this.model = model; }
Java Persistence: Core ORM Database Mappings using Annotations (cont.) @Column(name="CAR_YEAR", nullable=false) public int getYear() { return year; } public void setYear(int year) { this.year = year; } @Column(name="CAR_COST", precision=7, scale=2) public BigDecimal getCost() { return cost; } public void setCost(BigDecimal cost) { this.cost = cost; } ... }
Java Persistence: Core ORM Database Mappings using Descriptor <entity-mappings> <entity class="ejava.examples.orm.core.mapped.Car" access="PROPERTY" metadata-complete="true" name="MappedCar"> <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" table="" length="20"/> </basic> <basic name="model"> <column name="CAR_MODEL" nullable="false" length="20"/> </basic> ...
Java Persistence: Core ORM Database Mappings using Descriptor (cont.) ... <basic name="year"> <column name="CAR_YEAR" nullable="false"/> </basic> <basic name="cost"> <column name="CAR_COST" precision="7" scale="2"/> </basic> </attributes> </entity> </entity-mappings>
Java Persistence: Core ORM 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()); assertTrue("unexpectected value", car.getCost().equals(car2.getCost())); //update beyond 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())); //output -car2.cost=1234.666 -car3.cost=1234.67
Java Persistence: Core ORM More on Precision //update beyond 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); } //output -caught expected exception:javax.persistence.PersistenceException: org.hibernate.exception.DataException: Value too long for column "CAR_COST DECIMAL(7, 2)": "123456.66 (8)"; SQL statement: update ORMCORE_CAR set CAR_COST=?, CAR_MAKE=?, CAR_MODEL=?, CAR_YEAR=? where CAR_ID=? [22001-168]
Java Persistence: Core ORM Primary Keys • Every entity must have an primary key • Primary keys must be unique • Map to one (simple primary key) or more (composite primary key) properties • Properties must be of type • Java primitive types (including wrappers; Integer, etc.) • java.lang.String • primary key class (composed of legal property types)
Java Persistence: Core ORM Generated Primary Keys
Java Persistence: Core ORM Generated Primary Keys public interface javax.persistence.GeneratedValue { javax.persistence.GenerationType strategy(); String generator(); } public final class javax.persistence.GenerationType extends java.lang.Enum{ public static final GenerationType TABLE; public static final GenerationType SEQUENCE; public static final GenerationType IDENTITY; public static final GenerationType AUTO; ... }
Java Persistence: Core ORM AUTO GenerationType • Persistence providers required to provide primary key generation • Specific type of generator provided through strategy property • Using annotations @Entity @Table(name="ORMCORE_DRILL") public class Drill implements Serializable { ... @Id @GeneratedValue( //AUTO is default;could be left off strategy=GenerationType.AUTO) public long getId() { return id; } • Using descriptor <entity class="ejava.examples.orm.core.mapped.Drill" ...> <table name="ORMCORE_DRILL"/> <attributes> <id name="id"> <generated-value strategy="AUTO"/> </id> </attributes> </entity>
Java Persistence: Core ORM AUTO GenerationType @Entity @Table(name="ORMCORE_DRILL") public class Drill implements Serializable { private static final long serialVersionUID = 1L; private long id=0; private String make; public Drill() {} public Drill(long id) { this.id = id; } @Id @GeneratedValue( //AUTO is default strategy=GenerationType.AUTO) public long getId() { return id; }
Java Persistence: Core ORM AUTO GeneratedValue Test (Good) public void testAUTOGood() { //note that since PKs are generated, we must pass in //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); } insert into ORMCORE_DRILL (id, make) values (null, ?) Hibernate: call identity() -created drill: ejava.examples.orm.core.annotated.Drill@1d9e279, id=1, make=acme
Java Persistence: Core ORM AUTO GeneratedValue Test (Bad) public void testAUTOBad() { ejava.examples.orm.core.annotated.Drill drill = new Drill(25L); drill.setMake("BD"); boolean exceptionThrown = false; try { assertFalse(drill.getId() == 0L); em.persist(drill); } catch (PersistenceException ex) { log.info("got expected exception: " + ex); exceptionThrown = true; } assertTrue(exceptionThrown); } -got expected exception: javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: ejava.examples.orm.core.annotated.Drill
Java Persistence: Core ORM TABLE GenerationType • Defines user-defined relational table to generate numeric values • example table create table ORMCORE_EB_UID ( UID_ID varchar(255), //holds name of table UID_VAL integer //holds the counter value ) • example values • UID_ID=ORMCORE_EGGBEATER • UID_VAL=2 • Must be setup by a definition of the table generator
Java Persistence: Core ORM Defining Table Generator • Using annotations @Entity @Table(name="ORMCORE_EGGBEATER") @TableGenerator( //note:all but name are optional if gen schema name="eggbeaterGenerator", //logical name of gen table="ORMCORE_EB_UID", //name of table with seq pkColumnName="UID_ID", //pk column for seq table pkColumnValue="ORMCORE_EGGBEATER", //pk value in pk col valueColumnName="UID_VAL", //column for seq value allocationSize=17 //amount to increment ) public class EggBeater implements Serializable { • Using descriptor <entity class="ejava.examples.orm.core.mapped.EggBeater" ...> <table name="ORMCORE_EGGBEATER"/> <table-generator name="eggbeaterGenerator" table="ORMCORE_EB_UID" pk-column-name="UID_ID" pk-column-value="ORMCORE_EGGBEATER" value-column-name="UID_VAL" allocation-size="17"/>
Java Persistence: Core ORM Using TABLE GenerationType • Using annotations @Id @GeneratedValue( strategy=GenerationType.TABLE, //use DB table generator="eggbeaterGenerator") //point to logical def public long getId() { return id; } • Using descriptor <attributes> <id name="id"> <generated-value strategy="TABLE" generator="eggbeaterGenerator"/> </id> </attributes> </entity>
Java Persistence: Core ORM Testing TABLE GeneratedType public void testTABLE() { 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:" + eggbeater); assertFalse(eggbeater.getId() == 0L); } -created eggbeater: ejava.examples.orm.core.annotated.EggBeater@1cba87, id=1, make=done right
Java Persistence: Core ORM Finishing the allocationSize Hibernate: insert into ORMCORE_EGGBEATER (make, id) values (?, ?) -table id after[2]=1 Hibernate: insert into ORMCORE_EGGBEATER (make, id) values (?, ?) -table id after[3]=1 … … Hibernate: insert into ORMCORE_EGGBEATER (make, id) values (?, ?) -table id after[16]=1 Hibernate: insert into ORMCORE_EGGBEATER (make, id) values (?, ?) -table id after[17]=2
Java Persistence: Core ORM SEQUENCE GenerationType • Some DBMS vendors provide • built-in structure to efficiently generate unique IDs
Java Persistence: Core ORM Defining Sequence Generator • Using Annotations @Entity @Table(name="ORMCORE_FAN") @SequenceGenerator( name="fanSequence", //required logical name sequenceName="FAN_SEQ", //name in database initialValue=44, //doesn’t seem to be used allocationSize=13) //increment seq when reached public class Fan implements Serializable {... • Using Descriptor <entity class="ejava.examples.orm.core.mapped.Fan" ...> <table name="ORMCORE_FAN"/> <sequence-generator name="fanSequence" sequence-name="FAN_SEQ" initial-value="44" allocation-size="13"/>
Java Persistence: Core ORM Using SEQUENCE GenerationType • Using Annotations @Id @GeneratedValue( strategy=GenerationType.SEQUENCE, generator="fanSequence") //point to logical def public long getId() { return id; } private void setId(long id) { this.id = id; } • Using Descriptor <attributes> <id name="id"> <generated-value strategy="SEQUENCE" generator="fanSequence"/> </id> </attributes>
Java Persistence: Core ORM Testing Sequence Generator public void testSEQUENCE() { 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:" + fan); assertFalse(fan.getId() == 0L); } -created fan:ejava.examples.orm.core.annotated.Fan@1dcb3cd, id=1, make=cool runner 1
Java Persistence: Core ORM IDENITY GenerationType • Let database generate • from Hypersonic Log create table ORMCORE_GADGET ( id bigint generated by default as identity (start with 1), make varchar(255), primary key (id) ) ... INSERT INTO ORMCORE_GADGET VALUES(null,'gizmo 1') INSERT INTO ORMCORE_GADGET VALUES(null,'gizmo 2') • HSQL supports a “call identity()” stored procedure to get last generated ID
Java Persistence: Core ORM Using IDENTITY Generator • Using Annotations @Id @GeneratedValue(strategy=GenerationType.IDENTITY) public long getId() { return id; } • Using Descriptor <attributes> <id name="id"> <generated-value strategy="IDENTITY"/> </id> </attributes>
Java Persistence: Core ORM Testing Identity Generator 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:" + gadget); assertFalse(gadget.getId() == 0L); } -created gadget: ejava.examples.orm.core.annotated.Gadget@23d278, id=1, make=gizmo 1
Java Persistence: Core ORM Composite Primary Keys
Java Persistence: Core ORM Composite Primary Keys • Primary Key consisting of multiple properties • Can be represented by a primary key class • must be Serializable public class MowerPK implements java.io.Serializable { • must have a public no-arg constructor public MowerPK() {} • must implement equals() and hashCode() methods public int hashCode() { ... } public boolean equals(Object obj) { ... }
Java Persistence: Core ORM Example Composite PK Class 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 static long getSerialVersionUID() { return serialVersionUID; } public String getMake() { return make; } private void setMake(String make) { this.make = make; }
Java Persistence: Core ORM Example Composite PK Class (cont.) public String getModel() { return model; } private void setModel(String model) { this.model = model; } public int hashCode() { return make.hashCode() + model.hashCode(); } 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; } } public String toString() { return super.toString() + ", make=" + make + ", model=" + model; } }
Java Persistence: Core ORM Using a Primary Key Class as an IdClass • Not used internally by persistent class • Used by entity manager • Properties of IdClass map to @Id properties of persistent class • Example Database Schema create table ORMCORE_MOWER ( make varchar(255) not null, model varchar(255) not null, size integer not null, primary key (make, model) )
Java Persistence: Core ORM Example Usage of an IdClass package ejava.examples.orm.core.annotated; import java.io.Serializable; import javax.persistence.*; import ejava.examples.orm.core.MowerPK; @Entity @Table(name="ORMCORE_MOWER") @IdClass(MowerPK.class) public class Mower implements Serializable { private static final long serialVersionUID = 1L; private String make; private String model; private int size; public Mower() {} public Mower(String make, String model) { this.make = make; this.model = model; }
Java Persistence: Core ORM Example Usage of an IdClass (cont.) @Id @Column(nullable=false, updatable=false) public String getMake() { return make; } private void setMake(String make) { this.make = make; } @Id @Column(nullable=false, updatable=false) public String getModel() { return model; } private void setModel(String model) { this.model = model; } public int getSize() { return size; } public void setSize(int size) { this.size = size; }
Java Persistence: Core ORM Example Usage of an IdClass • Using a Descriptor <entity class="ejava.examples.orm.core.mapped.Mower" access="PROPERTY" metadata-complete="true" name="MappedMower"> <table name="ORMCORE_MOWER"/> <id-class class="ejava.examples.orm.core.MowerPK"/> <attributes> <id name="make"/> <id name="model"/> </attributes> </entity>
Java Persistence: Core ORM Example Usage of an IdClass 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@d2b64e, 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@d2b64e, make=acme, model=power devil2, size=21