JPA Persistence and Hibernate:
JPA 2.0 is the standardization of various Object Relational Mapping (ORM) APIs and their query languages.
JPA uses the "Java Persistence Query Language" (JPQL), a SQL like query language customized for use with the Java language.
JPA uses Java classes to represent relational database tables which can contain data read from a database or have data which will be written to a database.
The mapping of the Java class and its member variables to a database table and fields can be fulfilled by Java annotations to the class or by an XML configuration file.
In this tutorial we will take the annotation approach.
An XML configuration file will be used to define the database connection.
JPA Installation:
Java:
As a prerequisite Java and JAXB need to be installed. See our tutorial on installing Java on Linux.
Java Log4j: (version 1)
Apache Log4j version 1
cd /opt
sudo tar xzf ~/Downloads/log4j-1.2.17.tar.gz
We will be using JAR file located in:
- /opt/apache-log4j-1.2.17/log4j-1.2.17.jar
JPA/Hibernate:
The Hibernate open source implementation of JPA will be used for this tutorial and the latest version can be downloaded from:
Hibernate ORM downloads page
Note that early versions of Hibernate provided an ORM Java API but may predate the JPA 2.0 standard (released Dec 2009).
Red Hat offers commercial support.
cd /opt
sudo unzip ~/Downloads/hibernate-release-4.2.15.Final.zip
If using JBoss, your CLASSPATH can reference JAXB from the JBoss library: /opt/jboss-X.X.X.XXXXX/lib/
We will be using JAR files located in:
- /opt/hibernate-release-4.2.15.Final/lib/jpa/
- /opt/hibernate-release-4.2.15.Final/lib/required/
Database:
In this tutorial we will be working with a MySQL database.
Installation and configuration of MySQL on Linux tutorial
Database Connector:
In this example we will be using a MySQL relational database and will require the MySQL connector.
Download MySQL Connector/J
cd /opt
sudo tar xzf ~/Downloads/mysql-connector-java-5.1.32.tar.gz
We will be using:
- /opt/mysql-connector-java-5.1.32/mysql-connector-java-5.1.32-bin.jar
- /opt/mysql-connector-java-5.1.32/lib/slf4j-api-1.6.1.jar
Alternative source for JAR files: FindJar.com
Note that Java application servers like JBoss will come will most of the required JAR files.
Simple JPA one table, one Java class example:
Our first JPA example is for the exchange of data between a single database table and a single Java class:
SQL file:
corporation.sql
01 | create database corpinfo; |
04 | CREATE TABLE corporation (id integer NOT NULL , |
06 | description varchar (255), |
10 | PRIMARY KEY (id))ENGINE=INNODB; |
13 | INSERT INTO corporation (id, name ,description,address,zipcode,phone) |
14 | VALUES (1, 'Mega Corp' , 'Global industrial multinational corporation' , '555 Mega Way, Acropolis CA' , '90266' , '1-800-555-1211' ); |
15 | INSERT INTO corporation (id, name ,description,address,zipcode,phone) |
16 | VALUES (2, 'Super Corp' , 'National industrial corporation' , '555 Super Way, Acropolis CA' , '90266' , '1-877-555-1212' ); |
17 | INSERT INTO corporation (id, name ,description,address,zipcode,phone) |
18 | VALUES (3, 'Mini Corp' , 'State industrial corporation' , '555 Mini Way, Acropolis CA' , '90266' , '1-888-555-1213' ); |
19 | INSERT INTO corporation (id, name ,description,address,zipcode,phone) |
20 | VALUES (4, 'Stone Corp' , 'Rock Quarry' , '111 Rock Way, Acropolis CA' , '90210' , '1-899-555-1214' ); |
Load database:
mysql -h localhost -u root -psupersecretpassword < corporation.sql
This will generate the following table in MySQL:
mysql -h localhost -u root -psupersecretpassword
mysql> desc corporation;
+-------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(255) | YES | | NULL | |
| description | varchar(255) | YES | | NULL | |
| address | varchar(255) | YES | | NULL | |
| zipcode | varchar(16) | YES | | NULL | |
| phone | varchar(32) | YES | | NULL | |
+-------------+--------------+------+-----+---------+-------+
Generate an anntotated Java class to represent the database table schema:
File:
Corporation.java
01 | import javax.persistence.*; |
04 | @Table (name= "corporation" ) |
05 | public class Corporation { |
09 | private String description; |
10 | private String address; |
11 | private String zipcode; |
14 | public Integer getId() { |
17 | public void setId(Integer _id) { |
21 | public String getName() { |
24 | public void setName(String _Name) { |
28 | public String getDescription() { |
31 | public void setDescription(String _Description) { |
32 | this .description = _Description; |
35 | public String getAddress() { |
38 | public void setAddress(String _Address) { |
39 | this .address = _Address; |
42 | public String getZipcode() { |
45 | public void setZipcode(String _Zipcode) { |
46 | this .zipcode = _Zipcode; |
49 | public String getPhone() { |
52 | public void setPhone(String _Phone) { |
Main program to insert a record to the database and to read two records from the database:
File:
TestDb.java
01 | import javax.persistence.EntityManager; |
02 | import javax.persistence.EntityManagerFactory; |
03 | import javax.persistence.Persistence; |
05 | import org.apache.log4j.BasicConfigurator; |
06 | import org.apache.log4j.Level; |
07 | import org.apache.log4j.spi.RootLogger; |
13 | BasicConfigurator.configure(); |
14 | RootLogger.getRootLogger().setLevel(Level.WARN); |
17 | public static void main(String[] args) throws Exception { |
19 | EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory( "com.corpinfo.model" ); |
20 | EntityManager entityManager = entityManagerFactory.createEntityManager(); |
23 | Corporation corporation = new Corporation(); |
25 | corporation.setName( "Extra Corp" ); |
26 | corporation.setDescription( "Extra industrial multinational corporation" ); |
27 | corporation.setAddress( "555 Extra Way, Acropolis CA" ); |
28 | corporation.setZipcode( "90267" ); |
29 | corporation.setPhone( "1-800-555-1213" ); |
32 | entityManager.getTransaction().begin(); |
33 | entityManager.persist(corporation); |
34 | entityManager.getTransaction().commit(); |
35 | } catch (RuntimeException e) { |
37 | entityManager.getTransaction().rollback(); |
41 | Corporation corp3 = entityManager.find(Corporation. class , 3 ); |
42 | System.out.println( "Corp 3 name: " + corp3.getName() + " Zipcode: " + corp3.getZipcode()); |
43 | Corporation corp5 = entityManager.find(Corporation. class , 5 ); |
44 | System.out.println( "Corp 5 name: " + corp5.getName() + " Zipcode: " + corp5.getZipcode()); |
45 | } catch (RuntimeException e) { |
47 | entityManager.getTransaction().rollback(); |
49 | entityManager.close(); |
JPA configuration file: This defines the database connection parameters.
File:
META-INF/persistence.xml
01 | <? xml version = "1.0" encoding = "UTF-8" ?> |
05 | < persistence-unit name = "com.corpinfo.model" transaction-type = "RESOURCE_LOCAL" > |
06 | < provider >org.hibernate.ejb.HibernatePersistence</ provider > |
07 | < class >Corporation</ class > |
10 | < property name = "hibernate.connection.driver_class" value = "com.mysql.jdbc.Driver" /> |
11 | < property name = "hibernate.dialect" value = "org.hibernate.dialect.MySQLInnoDBDialect" /> |
12 | < property name = "hibernate.connection.username" value = "root" /> |
13 | < property name = "hibernate.connection.password" value = "supersecretpassword" /> |
14 | < property name = "hibernate.show_sql" value = "false" /> |
15 | < property name = "hibernate.format_sql" value = "true" /> |
16 | < property name = "hibernate.flushMode" value = "FLUSH_AUTO" /> |
17 | < property name = "hibernate.hbm2ddl.auto" value = "validate" /> |
Note:
- This database connection is to "localhost using the default MySQL port 3306 and the database name is "corpinfo".
- The "class" definition: <class>Corporation</class>
refers to the Java class marked with @Entity.
Typically this will have the full namespace reference is used for example:
com.megacorp.accounting.Corporation.
There will also be an XML class definition for each Java class annotated with @Entity.
Ant build script:
File:
build.xml
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
02 | < project name = "TestHibernate" default = "compile" basedir = "." > |
03 | < description >Test hibernate JPA</ description > |
04 | < property name = "build.dir" value = "./" /> |
05 | < property name = "dest.dir" value = "./" /> |
06 | < property environment = "env" /> |
08 | < pathelement location = "/usr/java/latest/lib/tools.jar" /> |
09 | < pathelement location = "${build.dir}" /> |
10 | < pathelement location = "${build.dir}/META-INF/persistence.xml" /> |
11 | < pathelement location = "/opt/mysql-connector-java-5.1.32/mysql-connector-java-5.1.32-bin.jar" /> |
12 | < pathelement location = "/opt/mysql-connector-java-5.1.32/lib/slf4j-api-1.6.1.jar" /> |
13 | < pathelement location = "/opt/apache-log4j-1.2.17/log4j-1.2.17.jar" /> |
14 | < pathelement location = "/opt/hibernate-release-4.2.15.Final/lib/jpa/hibernate-entitymanager-4.2.15.Final.jar" /> |
15 | < pathelement location = "/opt/hibernate-release-4.2.15.Final/lib/required/antlr-2.7.7.jar" /> |
16 | < pathelement location = "/opt/hibernate-release-4.2.15.Final/lib/required/dom4j-1.6.1.jar" /> |
17 | < pathelement location = "/opt/hibernate-release-4.2.15.Final/lib/required/hibernate-commons-annotations-4.0.2.Final.jar" /> |
18 | < pathelement location = "/opt/hibernate-release-4.2.15.Final/lib/required/hibernate-core-4.2.15.Final.jar" /> |
19 | < pathelement location = "/opt/hibernate-release-4.2.15.Final/lib/required/hibernate-jpa-2.0-api-1.0.1.Final.jar" /> |
20 | < pathelement location = "/opt/hibernate-release-4.2.15.Final/lib/required/javassist-3.18.1-GA.jar" /> |
21 | < pathelement location = "/opt/hibernate-release-4.2.15.Final/lib/required/jboss-logging-3.1.0.GA.jar" /> |
22 | < pathelement location = "/opt/hibernate-release-4.2.15.Final/lib/required/jboss-transaction-api_1.1_spec-1.0.1.Final.jar" /> |
24 | < target name = "clean" description = "Remove .class files" > |
25 | < delete includeEmptyDirs = "true" failonerror = "false" > |
26 | < fileset dir = "${dest.dir}" > |
27 | < include name = "**/*.class" /> |
28 | < include name = "test-hibernate.jar" /> |
32 | < target name = "compile" > |
33 | < javac srcdir = "./" destdir = "./" debug = "true" includeAntRuntime = "false" > |
34 | < classpath refid = "classpath" /> |
35 | < include name = "**/*.java" /> |
38 | < target name = "jar" depends = "compile" description = "test example" > |
39 | < jar jarfile = "${dest.dir}/test-hibernate.jar" > |
40 | < fileset dir = "${dest.dir}" includes = "**/*.class" /> |
41 | < fileset dir = "${dest.dir}" includes = "META-INF/persistence.xml" /> |
43 | < attribute name = "Main-Class" value = "TestDb" /> |
44 | < attribute name = "Class-Path" value = "classpath" /> |
45 | < attribute name = "Implementation-Title" value = "Example" /> |
46 | < attribute name = "Implementation-Version" value = "1.0" /> |
47 | < attribute name = "Implementation-Vendor" value = "corpinfo.com" /> |
51 | < target name = "run" depends = "jar" > |
52 | < java classname = "TestDb" failonerror = "true" fork = "true" > |
54 | < path refid = "classpath" /> |
Compile: ant compile
Run:
ant run
[java] Corp 3 name: Mini Corp Zipcode: 90266
[java] Corp 5 name: Extra Corp Zipcode: 90267
JPQL and Named Queries:
The Java Persistence Query Language (JPQL) is similar to the SQL SELECT, UPDATE and DELETE statements and was developed to be portable for any database supported by JPA.
JPQL is designed to operate on Java objects, attributes and relationships rather than tables and columns.
JPQL implemented as a Query, NamedQuery or TypedQuery object constructed using the EntityManager createQuery() API call.
Queries can be either static or dynamically generated at runtime.
JPQL uses "identification variables" declared in the FROM clause and is named uniquely and evaluates to a value of the type of the expression used in declaring the variable.
Example:
Query query = entityManager.createQuery("SELECT e FROM Corporation e");
List<Corporation> result = query.getResultList();
or for a specific field:
Query query = entityManager.createQuery("SELECT e.name FROM Corporation e WHERE e.zipcode = :zipcode");
String sZipcode="90266";
query.setParameter("zipcode", sZipcode);
List<Corporation> result = query.getResultList();
or for a specific field and a single result:
Query query = entityManager.createQuery("SELECT e.name FROM Corporation e WHERE e.zipcode = :zipcode");
String sZipcode="90210";
query.setParameter("zipcode", sZipcode);
Corporation result = query.getSingleResult();
JPQL Functions:
- JPQL Aggregation Functions include MIN(), MAX(), AVG(), SUM(), COUNT() and GROUP BY, ORDER BY and HAVING.
Examples:
- SELECT COUNT(e) FROM Corporation e
- SELECT MAX(e.zipcode) FROM Corporation e
- SELECT e FROM Corporation e ORDER BY e.zipcode
- JPQL Functions include "+" (addition), "-" (subtration), "*" (multiplication), "/" (division),
ABS(), CASE/STATUS/WHEN/THEN/ELSE/END (case statement operation),
COALESCE(arg1,arg2,arg3) (first non-null argument), CONCAT(str1,str2,str3),
CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, LENGTH(e.field), LOCATE(),
LOWER() (string case), MOD(), NULLIF(), SQRT(), SUBSTR(), TRIM(), UPPER() (string case).
- JPQL Operators: INDEX(), KEY(), SIZE(), IS EMPTY, TYPE(), FUNCTION() and TREAT()
JPQL Constructors:
JPQL provides a NEW operator which can be used with a fully qualified class name to store the returned data objects from the query.
Examples:
- SELECT NEW com.megacorp.accounting.Corporation (e.name, e.description, e.address, e.zipcode, e.phone) FROM Corporation e WHERE e.zipcode='90210'
JPQL WHERE Clause Comparison Operators:
The following standard SQL databse database operators are available in JPQL:
=, <, >, <=, >=, LIKE (match with wildcards "%" or "_"), BETWEEN, IS NULL and IN (contained in provided list).
JPQL Update and Delete queries:
Examples:
- Update:
Query query = entityManager.createQuery("UPDATE Corporation e SET e.description = 'New description goes here' WHERE e.name = 'Super Corp'");
int rowCount = query.executeUpdate();
- Delete:
Query query = entityManager.createQuery("DELETE Corporation e WHERE e.name = 'Super Corp'");
int rowCount = query.executeUpdate();
JPQL Named and Positional Queries:
Both named and positional parameters are supported.
Examples:
- Named: (variation of example above)
Query query = entityManager.createQuery("SELECT e.name FROM Corporation e WHERE e.zipcode = :var1 AND e.name = :var2");
String sZipcode="90266";
String sCorp="Mega Corp";
query.setParameter("var1", sZipcode);
query.setParameter("var2", sCorp);
Corporation result = query.getSingleResult();
- Positional Parameters:
Query query = entityManager.createQuery("SELECT e.name FROM Corporation e WHERE e.zipcode = ? AND e.name = ?");
String sZipcode="90266";
String sCorp="Mega Corp";
query.setParameter("1", sZipcode);
query.setParameter("2", sCorp);
Corporation result = query.getSingleResult();
JPQL Clause Operators:
These include SELECT, FROM, JOIN, JOIN FETCH, LEFT JOIN, ON (JPA 2.1)
Database configurations for META-INF/persistence.xml:
Specific Database Property Options:
MySQL:
2 | < property name = "hibernate.connection.driver_class" value = "com.mysql.jdbc.Driver" /> |
3 | < property name = "hibernate.dialect" value = "org.hibernate.dialect.MySQLInnoDBDialect" /> |
PostgreSQL:
2 | < property name = "hibernate.connection.driver_class" value = "org.postgresql.Driver" /> |
3 | < property name = "hibernate.dialect" value = "org.hibernate.dialect.PostgreSQLDialect" /> |
Derby:
1 | < property name = "hibernate.connection.url" value = "jdbc:derby:memory/derbydb;create=true" /> |
2 | < property name = "hibernate.connection.driver_class" value = "org.apache.derby.jdbc.EmbeddedDriver" /> |
3 | < property name = "hibernate.dialect" value = "org.hibernate.dialect.DerbyDialect" /> |
JBoss HsqlDb:
1 | < property name = "hibernate.connection.url" value = "jdbc:hsqldb:." /> |
2 | < property name = "hibernate.connection.driver_class" value = "org.hsqldb.jdbcDriver" /> |
3 | < property name = "hibernate.dialect" value = "org.hibernate.dialect.HSQLDialect" /> |
Oracle:
1 | < property name = "hibernate.connection.url" value = "jdbc:oracle:thin:@localhost:1521:dbName" /> |
2 | < property name = "hibernate.connection.driver_class" value = "oracle.jdbc.driver.OracleDriver" /> |
3 | < property name = "hibernate.dialect" value = "org.hibernate.dialect.OracleDialect" /> |
Generic JPA Property Options:
Attribute | Options |
hibernate.hbm2ddl.auto |
- validate: validate the schema, makes no changes to the database.
- update: update the schema.
- create: creates the schema, destroying previous data.
- create-drop: drop the schema at the end of the session.
|
hibernate.show_sql |
- true: Enable the logging of all the generated SQL statements (from HQL) to the console
- false: Not output
|
hibernate.format_sql |
- true: Format the generated SQL statement to make it more readable, but takes up more screen space.
- false: Not formatted
|
use_sql_comments |
- true: Generate SQL comments to show what the generated SQL trying to do
- false: No comments generated
|
hibernate.archive.autodetection |
- class: Determine which element is auto discovered by Hibernate Entity Manager
while parsing the .par archive. (default to class,hbm).
|
hibernate.flushMode |
|
JPA Annotations:
Entity Mapping:
We must map the database and table column name to the class name and class member variable names and data types.
Example:
@Entity
@Table(name="corporation")
public class Corporation {
@Id
private Integer id;
...
..
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
...
..
Column Mapping:
By default it is assumed that the Java variable names matches the database field name. If it does not, one must identify the database field name with the annotation:
@Column(name="Col_Name")
[Potential Pitfall]: If you mispell "Column" you will get the following error. Note that there is only one "
l", NOT two in the word "Column".
[javac] /home/user1/Test/Corporation.java:8: error: cannot find symbol
[javac] @Collumn(name="name")
[javac] ^
[javac] symbol: class Collumn
Its
@Column NOT
@Collumn !!!
Mapping MySQL to Java Data Types:
The data types of the database (MySQL in this example) and of Java may not have an obvious mapping and thus must be explicitly stated.
The
@Column annotation can map data types in addition to the field names.
Example:
- MySQL database type: TEXT
Java type: String
Annotation: @Column(name="description",length = 65535,columnDefinition="Text")
- MySQL database type: varchar(255)
Java type: String
Annotation: none required. Default mapping works without annotation.
Type Comparisons:
MySQL | Java |
BIT | Boolean (Java wrapper class) boolean (Java primitive type) |
BIT(2) to BIT(8) TINYINT | Byte (Java wrapper class) byte (Java primitive type) |
BIT(9) to BIT(16) SMALLINTYEAR | Short (Java wrapper class) short (Java primitive type) |
BIT(17) to BIT(32) INT | Integer (Java wrapper class) int (Java primitive type) |
BIT(33) to BIT(64) BIGINT UNSIGNED | Long (Java wrapper class) long (Java primitive type) |
FLOAT | Float (Java wrapper class) float (Java primitive type) |
DOUBLE | Double (Java wrapper class) double (Java primitive type) |
NUMERIC DECIMAL | java.math.BigDecimal |
NUMERIC (precision=0) DECIMAL (precision=0) | java.math.BigInteger |
CHAR VARCHAR TEXT | String |
BINARY VARBINARY BLOB | byte[] |
TINYTEXT | String char(255) Note: 255 (2^8−1) bytes |
TEXT | String char(65535) Note: 65,535 (2^16−1) bytes = 64 Kb |
MEDIUMTEXT | String char(16777215) Note: 16,777,215 (2^24−1) bytes = 16 Mb |
LONGTEXT | String char(4294967295) Note: 4,294,967,295 (2^32−1) bytes = 4 Gb |
DATETIME TIMESTAMP TIME DATE | Java.util.Date |
DATE | Java.sql.Date |
TIME | Java.sql.Time |
DATETIME TIMESTAMP | Java.sql.Timestamp |
ENUM | String Integer |
ENUM:
Two enum types supported by JPA,
EnumType.STRING vs
EnumType.ORDINAL (integer stored in database).
When using
EnumType.ORDINAL, any new enum elements must be added to the end of the list or you will accidentally change the meaning of all your records as the order represents the enum int value.
Relationship Mapping:
These annotation are used to show table relationships derived from foreign keys.
See our tutorials on the use of foreign keys with the MySQL and PostreSQL databases:
The relationships available to the databases and JPA are:
- Many-to-one: This is defined by a table index (id) being reffered to by the foreign key in many records in another table.
@Entity
@Table(name="employee")
public class Employee {
@Id
@Column(name="id_pk")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer Id;
@Column(name="corp_fk")
private Integer Corp_fk; // Foreign key to table corporation
...
...
@ManyToOne
@JoinColumn(name="corp_fk",nullable=false, insertable=false, updatable=false)
private Corporation corporation; // Added to support joined query
public Corporation getCorporation() {
return corporation;
}
...
...
- One-to-many: one Corporation is referred to by many Employee records (Collection, List, Set or Map. Set enforces unique entries.).
@Entity
@Table(name="corporation")
public class Corporation {
@Id // Tell JPA that id is the primary key
@Column(name="id_pk")
private Integer id;
...
...
@OneToMany(cascade=CascadeType.PERSIST,mappedBy="corporation")
private List<Employee> employees;
public List<Employee> getEmployees() {return employees;}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
...
...
- Many-to-many: this relationship is accomplished by using a foreign key in each table
File: Corporation.java
@Entity
@Table(name="corporation")
public class Corporation {
@Id // Tell JPA that id is the primary key
@Column(name="id_pk")
private Integer id;
...
...
@ManyToMany(mappedBy="corporations")
private List<Employee> employees;
public List<Employee> getEmployees() {return employees;}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
...
File: Employee.java
...
@Entity
@Table(name="employee")
public class Employee {
@Id
@Column(name="id_pk")
private Integer Id;
@Column(name="corp_fk")
private Integer Corp_fk; // Foreign key to table corporation
...
...
@ManyToMany
@JoinTable( name="EmployeeCorporation",
joinColumns="@JoinColumn(name="Id"),
inverseJoinColumns=@JoinColumn(name="id"))
private List<Corporation> corporations;
public List<Corporation> getCorporations() {
return corporations;
}
public void setCorporations(List<Corporation> corporations) {
this.corporations = corporations;
}
...
In this example the Corporation class "owns" the relationship and specifies the mapping. This is arbitrary as we can have the Employee class "own" the relationship as shown below.
The entity owning the relationship is the class with the "mappedBy" annotation attribute.
File: Corporation.java
@Entity
@Table(name="corporation")
public class Corporation {
@Id // Tell JPA that id is the primary key
@Column(name="id_pk")
private Integer id;
...
...
@ManyToMany
@JoinTable(name="EmployeeCorporation",
joinColumns=@JoinColumn(name="corporationId"),
inverseJoinColumns=@JoinColumn(name="Id"))
private List<Employee> employees;
public List<Employee> getEmployees() {return employees;}
public void setEmployees(List<Employee> employees) {
this.employees = employees;
}
...
File: Employee.java
...
@Entity
@Table(name="employee")
public class Employee {
@Id
@Column(name="id_pk")
private Integer Id;
@Column(name="corp_fk")
private Integer Corp_fk; // Foreign key to table corporation
...
...
@ManyToMany(mappedBy="employees")
private List<Corporation> corporations;
public List<Corporation> getCorporations() {
return corporations;
}
public void setCorporations(List<Corporation> corporations) {
this.corporations = corporations;
}
...
...
Note that if the @JoinTable mapping is omitted, a default join table is assumed and assigned a name defined by the owning class name + "_" + non-owning class name. The foreign key reference names would be named class name + "_" + table key.
- One-to-one:
File: Employee.java
...
@Entity
public class Employee {
@Id
@Column(name="id_pk")
private Integer Id;
@OneToOne
@JoinColumn(name="contactId")
private EmergencyContact contact;
...
...
File: EmergencyContact.java
...
@Entity
public class EmergencyContact {
@Id
private Integer Id;
...
...
@OneToOne(mappedBy="emergencyContact")
private Employee employee;
...
Two Table Join with Various Data Types:
This example will perform a SQL table join using two JPA annotated Java classes.
The data types do not match by default to show the required data type mapping including an example of use of an enum.
The dabase schema representation:
SQL file:
example-join.sql
01 | create database corpinfo; |
04 | CREATE TABLE corporation (id_pk integer NOT NULL , |
10 | PRIMARY KEY (ID_pk))ENGINE=INNODB; |
12 | CREATE TABLE employee (id_pk integer NOT NULL AUTO_INCREMENT, |
13 | corp_fk integer NOT NULL , |
20 | PRIMARY KEY (id_pk))ENGINE=INNODB; |
21 | ALTER TABLE employee ADD FOREIGN KEY (corp_fk) REFERENCES corporation(id_pk) |
24 | CREATE TABLE corp_award (id_pk integer NOT NULL AUTO_INCREMENT, |
25 | corp_fk integer NOT NULL , |
26 | award_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , |
28 | value ENUM( 'POOR' , 'OK' , 'GOOD' , 'EXCELLENT' , 'GREAT' , 'PERFECT' ) NOT NULL DEFAULT 'ok' , |
29 | PRIMARY KEY (id_pk))ENGINE=INNODB; |
30 | ALTER TABLE corp_award ADD FOREIGN KEY (corp_fk) REFERENCES corporation(id_pk) |
33 | INSERT INTO corporation (id_pk, name ,description,address,zipcode,phone) |
34 | VALUES (1, 'Mega Corp' , 'Global industrial multinational corporation' , '555 Mega Way, Acropolis CA' , '90266' , '1-800-555-1211' ); |
35 | INSERT INTO corporation (id_pk, name ,description,address,zipcode,phone) |
36 | VALUES (2, 'Super Corp' , 'National industrial corporation' , '555 Super Way, Acropolis CA' , '90266' , '1-877-555-1212' ); |
37 | INSERT INTO corporation (id_pk, name ,description,address,zipcode,phone) |
38 | VALUES (3, 'Mini Corp' , 'State industrial corporation' , '555 Mini Way, Acropolis CA' , '90266' , '1-888-555-1213' ); |
39 | INSERT INTO corporation (id_pk, name ,description,address,zipcode,phone) |
40 | VALUES (4, 'Stone Corp' , 'Rock Quarry' , '111 Rock Way, Acropolis CA' , '90210' , '1-899-555-1214' ); |
42 | INSERT INTO employee (corp_fk,department,title, name ,address,zipcode,phone) |
43 | VALUES (1, 'George Castanza' , 'Corporate Officer' , 'CTO' , '1414 Cherry Lane, Burbsville CA' , '90266' , '1-800-555-1213' ); |
44 | INSERT INTO employee (corp_fk,department,title, name ,address,zipcode,phone) |
45 | VALUES (1, 'Paul Stonehenge' , 'Corporate Officer' , 'CEO' , '1514 Peachtree Lane, Burbsville CA' , '90266' , '1-800-555-1215' ); |
46 | INSERT INTO employee (corp_fk,department,title, name ,address,zipcode,phone) |
47 | VALUES (1, 'John Watermark' , 'IT' , 'System Admin' , '1814 Appleseed Drive, Burbsville CA' , '90266' , '1-800-555-1218' ); |
48 | INSERT INTO employee (corp_fk,department,title, name ,address,zipcode,phone) |
49 | VALUES (4, 'Fred Flinstone' , 'Quary Worker' , 'Rock Digger' , '1814 Appleseed Drive, Burbsville CA' , '90266' , '1-800-555-1218' ); |
50 | INSERT INTO employee (corp_fk,department,title, name ,address,zipcode,phone) |
51 | VALUES (4, 'Wilma Flinstone' , 'Finance' , 'Analyst' , '1814 Appleseed Drive, Burbsville CA' , '90266' , '1-800-555-1218' ); |
53 | INSERT INTO corp_award (corp_fk) VALUES (1); |
54 | INSERT INTO corp_award (corp_fk,value) VALUES (1, 'good' ); |
55 | INSERT INTO corp_award (corp_fk,value) VALUES (2, 'ok' ); |
56 | INSERT INTO corp_award (corp_fk,value) VALUES (3, 'excellent' ); |
57 | INSERT INTO corp_award (corp_fk,value) VALUES (4, 'good' ); |
59 | INSERT INTO corp_award (corp_fk,value) VALUES (4, 'excellent' ); |
Load database: mysql -h localhost -u root -psupersecretpassword < example-join.sql
The "corp_award" table in MySQL:
mysql -h localhost -u root -psupersecretpassword
mysql> select * from corp_award;
+-------+---------+---------------------+-----------+
| id_pk | corp_fk | award_date | value |
+-------+---------+---------------------+-----------+
| 1 | 1 | 2014-09-18 18:35:21 | OK |
| 2 | 1 | 2014-09-18 18:35:21 | GOOD |
| 3 | 2 | 2014-09-18 18:35:21 | OK |
| 4 | 3 | 2014-09-18 18:35:21 | EXCELLENT |
| 5 | 4 | 2014-09-18 18:35:21 | GOOD |
| 6 | 4 | 2014-09-18 18:35:23 | EXCELLENT |
+-------+---------+---------------------+-----------+
Note that MySQL converts the lower case SQL enumerated values to upper case!
Generate an anntotated Java class to represent the database table schema:
File: Corporation.java
01 | import javax.persistence.*; |
02 | import java.util.ArrayList; |
06 | @Table (name= "corporation" ) |
07 | public class Corporation { |
12 | @Column (name= "name" ,columnDefinition= "char(255)" ) |
15 | @Column (name= "description" ,length = 65535 ,columnDefinition= "Text" ) |
16 | private String Description; |
18 | @Column (name= "address" ,columnDefinition= "char(255)" ) |
19 | private String Address; |
21 | @Column (name= "zipcode" ,columnDefinition= "char(16)" ) |
22 | private String Zipcode; |
24 | @Column (name= "phone" ,columnDefinition= "char(32)" ) |
27 | @OneToMany (cascade=CascadeType.PERSIST,mappedBy= "corporation" ) |
28 | private List<Employee> employees; |
29 | public List<Employee> getEmployees() { return employees;} |
30 | public void setEmployees(List<Employee> employees) { |
31 | this .employees = employees; |
34 | public Integer getId() { |
37 | public void setId(Integer _id) { |
41 | public String getName() { |
44 | public void setName(String _Name) { |
48 | public String getDescription() { |
51 | public void setDescription(String _Description) { |
52 | this .Description = _Description; |
55 | public String getAddress() { |
58 | public void setAddress(String _Address) { |
59 | this .Address = _Address; |
62 | public String getZipcode() { |
65 | public void setZipcode(String _Zipcode) { |
66 | this .Zipcode = _Zipcode; |
69 | public String getPhone() { |
72 | public void setPhone(String _Phone) { |
[Potential Pitfall]:
The primary key for the table "Corporation" is assigned and not automatically generated by the MySQL database. The MySQL attribute "AUTO_INCREMENT" is NOT used.
If the following notation is applied to the primary key "id_pk" as follows:
@GeneratedValue(strategy = GenerationType.IDENTITY)
Then you will get the following "detatched entity" error:
[java] Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: Corporation
[Potential Pitfall]:
Note that the database schema defines the Corporation "description" as type "TEXT".
This maps to Java as a JPA column mapping annotation columnDefinition="Text" and length = 65535.
If this mapping is not given by an annotation then you would get the following error:
[java] Caused by: org.hibernate.HibernateException: Wrong column type in corpinfo.corporation for column description. Found: text, expected: varchar(255)
Mapping: http://dev.mysql.com/doc/ndbapi/en/mccj-using-clusterj-mappings.html
[Potential Pitfall]:
If no mapping is assigned to the Java strings then following occurs:
[java] Caused by: org.hibernate.HibernateException: Wrong column type in corpinfo.corporation for column address. Found: char, expected: varchar(255)
This is fixed with a Java annotation or by changing the MySQL field data type.
It is quite common to develop "Data Access Objects" (DAO) to provide methods to access the data.
This abstraction layer is popular when the database is access many times across the application's code.
File:
CorporationDAO.java
02 | import javax.persistence.*; |
04 | public class CorporationDAO { |
06 | public Integer persist(String name, |
12 | EntityManager em = EMgrUtil.getEntityManager(); |
13 | EntityTransaction transaction = em.getTransaction(); |
14 | Corporation corporation = new Corporation(); |
17 | corporation.setDescription(description); |
18 | corporation.setAddress(address); |
19 | corporation.setZipcode(zipcode); |
20 | corporation.setPhone(phone); |
21 | em.persist(corporation); |
23 | } catch (RuntimeException e) { |
25 | transaction.rollback(); |
29 | return corporation.getId(); |
33 | public void insert ( Corporation c ) { |
35 | EntityManager em = EMgrUtil.getEntityManager(); |
36 | em.getTransaction().begin(); |
38 | em.getTransaction().commit(); |
41 | public void update ( Corporation c ) { |
43 | EntityManager em = EMgrUtil.getEntityManager(); |
44 | em.getTransaction().begin(); |
46 | em.getTransaction().commit(); |
49 | public void deleteById ( Integer id ) { |
51 | Corporation c = findById(id); |
53 | EntityManager em = EMgrUtil.getEntityManager(); |
54 | em.getTransaction().begin(); |
56 | em.getTransaction().commit(); |
59 | public List<Corporation> findAll() { |
61 | EntityManager em = EMgrUtil.getEntityManager(); |
62 | List<Corporation> list = em.createQuery( " from Corporation" , Corporation. class ).getResultList(); |
66 | public Corporation findById(Integer id) { |
68 | EntityManager em = EMgrUtil.getEntityManager(); |
69 | Corporation corporation = em.find(Corporation. class , id); |
Entity relationship Many-to-one: many employees to one corporation.
File:
Employee.java
01 | import javax.persistence.*; |
04 | @Table (name= "employee" ) |
05 | public class Employee { |
08 | @GeneratedValue (strategy = GenerationType.IDENTITY) |
11 | @Column (name= "corp_fk" ) |
12 | private Integer Corp_fk; |
14 | @Column (name= "name" ,columnDefinition= "char(255)" ) |
16 | @Column (name= "department" ,columnDefinition= "char(64)" ) |
17 | private String Department; |
18 | @Column (name= "title" ,columnDefinition= "char(64)" ) |
20 | @Column (name= "address" ,columnDefinition= "char(255)" ) |
21 | private String Address; |
22 | @Column (name= "zipcode" ,columnDefinition= "char(16)" ) |
23 | private String Zipcode; |
24 | @Column (name= "phone" ,columnDefinition= "char(32)" ) |
32 | @JoinColumn (name= "corp_fk" ,nullable= false , insertable= false , updatable= false ) |
33 | private Corporation corporation; |
34 | public Corporation getCorporation() { |
37 | public void setCorporation(Corporation corporation) { |
38 | this .corporation = corporation; |
41 | public Integer getId() { |
44 | public void setId(Integer _id) { |
48 | public Integer getCorp_fk() { |
51 | public void setCorp_fk(Integer _Corp_fk) { |
52 | this .Corp_fk = _Corp_fk; |
55 | public String getName() { |
58 | public void setName(String _Name) { |
62 | public String getDepartment() { |
65 | public void setDepartment(String _Department) { |
66 | this .Department = _Department; |
69 | public String getTitle() { |
72 | public void setTitle(String _Title) { |
76 | public String getAddress() { |
79 | public void setAddress(String _Address) { |
80 | this .Address = _Address; |
83 | public String getZipcode() { |
86 | public void setZipcode(String _Zipcode) { |
87 | this .Zipcode = _Zipcode; |
90 | public String getPhone() { |
93 | public void setPhone(String _Phone) { |
The following classes are used to manage the enumerated type in the database:
File:
Award.java
01 | import javax.persistence.*; |
05 | @Table (name= "corp_award" ) |
09 | @GeneratedValue (strategy = GenerationType.IDENTITY) |
11 | @Column (name= "corp_fk" ) |
12 | private Integer Corp_fk; |
13 | @Column (name= "award_date" ) |
14 | @Temporal (TemporalType.TIMESTAMP) |
15 | private Date Award_Date; |
19 | @Column (name= "value" ,columnDefinition = "ENUM('POOR','OK','GOOD','EXCELLENT','GREAT','PERFECT')" ) |
20 | @Enumerated (EnumType.STRING) |
21 | private AwardValue Value; |
24 | @JoinColumn (name= "corp_fk" ,nullable= false , insertable= false , updatable= false ) |
25 | private Corporation corporation; |
26 | public Corporation getCorporation() { |
30 | public Integer getId() { |
33 | public void setId(Integer _id) { |
37 | public Integer getCorp_fk() { |
40 | public void setCorp_fk(Integer _Corp_fk) { |
41 | this .Corp_fk = _Corp_fk; |
44 | public Date getAward_Date() { |
47 | public void setAward_Date(Date _Award_Date) { |
48 | this .Award_Date = _Award_Date; |
51 | public AwardValue getValue() { |
54 | public void setValue(AwardValue _Value) { |
File:
AwardValue.java
1 | public enum AwardValue { |
[Potential Pitfall]:
Case of these enumerated values must match the case of the enumerated values in MySQL.
The EntityManager in larger applications is often handled by a static helper class. This class handles the full life cycle of the EntityManager.
File:
EMgrUtil.java
01 | import javax.persistence.EntityManager; |
02 | import javax.persistence.EntityManagerFactory; |
03 | import javax.persistence.Persistence; |
05 | public class EMgrUtil { |
07 | private static EntityManagerFactory entityManagerFactory; |
08 | private static EntityManager entityManager; |
10 | public static EntityManager createEntityManager() { |
11 | if (entityManager == null ) { |
14 | entityManagerFactory = Persistence.createEntityManagerFactory( "com.corpinfo.model" ); |
15 | entityManager = entityManagerFactory.createEntityManager(); |
16 | } catch (ExceptionInInitializerError e) { |
24 | public static EntityManagerFactory getEntityManagerFactory() { |
25 | return entityManagerFactory; |
28 | public static EntityManager getEntityManager() { |
32 | public static void close() { |
33 | entityManager.close(); |
34 | entityManagerFactory.close(); |
The goal of this JPA example is to mimic SQL joined select:
mysql> select corporation.name, employee.name from corporation, employee where corporation.id_pk=employee.corp_fk AND corporation.name='Stone Corp';
+------------+-----------------+
| name | name |
+------------+-----------------+
| Stone Corp | Fred Flinstone |
| Stone Corp | Wilma Flinstone |
+------------+-----------------+
Main program to access the database (read/write) including a join between the tables corporation and employee and work with an enumerated type.
File:
TestDb.java
01 | import javax.persistence.*; |
03 | import org.apache.log4j.BasicConfigurator; |
04 | import org.apache.log4j.Level; |
05 | import org.apache.log4j.spi.RootLogger; |
11 | BasicConfigurator.configure(); |
12 | RootLogger.getRootLogger().setLevel(Level.WARN); |
15 | public static void main(String[] args) throws Exception { |
17 | EMgrUtil.createEntityManager(); |
18 | CorporationDAO corpDAO = new CorporationDAO(); |
21 | Corporation corporation = new Corporation(); |
23 | corporation.setName( "Extra Corp" ); |
24 | corporation.setDescription( "Extra industrial multinational corporation" ); |
25 | corporation.setAddress( "555 Extra Way, Acropolis CA" ); |
26 | corporation.setZipcode( "90267" ); |
27 | corporation.setPhone( "1-800-555-1213" ); |
28 | corpDAO.insert(corporation); |
30 | Corporation corp5 = corpDAO.findById( 5 ); |
31 | System.out.println( "Corp 5 name: " + corp5.getName() + " Zipcode: " + corp5.getZipcode()); |
33 | System.out.println( "\nList of Corporations:" ); |
34 | List<Corporation> corpAll = corpDAO.findAll(); |
35 | for ( int i = 0 ; i < corpAll.size(); i++) |
37 | System.out.println( "Corp: " + corpAll.get(i).getId() + " " + corpAll.get(i).getName()); |
40 | corpDAO.deleteById( 5 ); |
42 | System.out.println( "\nQuery result:" ); |
43 | String sql = "SELECT c FROM Corporation c WHERE name=?1" ; |
44 | EntityManager entityManager = EMgrUtil.getEntityManager(); |
45 | Query query = entityManager.createQuery(sql); |
46 | query.setParameter( 1 , "Stone Corp" ); |
47 | Corporation corp = (Corporation)query.getSingleResult(); |
48 | System.out.println( "Corp name: " + corp.getName()); |
50 | List<Employee> empAll = corp.getEmployees(); |
51 | for (Employee employee : empAll) { |
52 | System.out.println( " Employee: " + employee.getName()); |
55 | String sql2 = "SELECT a FROM Award a WHERE corp_fk=?1 ORDER BY Award_Date DESC" ; |
56 | Query query2 = entityManager.createQuery(sql2); |
57 | query2.setParameter( 1 , corp.getId()); |
58 | List<Award> awards = query2.getResultList(); |
59 | System.out.println( " Last corporate rating: " + awards.get( 0 ).getValue()); |
Note the us of the Query method
getSingleResult() which returns a single Java Object.
This example uses the same JPA configuration file persistence.xml and Apache ant script build.xml
compile: ant compile
run:
ant run
run:
[java] Corp 5 name: Extra Corp Zipcode: 90267
[java]
[java] List of Corporations:
[java] Corp: 1 Mega Corp
[java] Corp: 2 Super Corp
[java] Corp: 3 Mini Corp
[java] Corp: 4 Stone Corp
[java] Corp: 5 Extra Corp
[java]
[java] Query result:
[java] Corp name: Stone Corp
[java] Employee: Rock Digger
[java] Employee: Analyst
[java] Last corporate rating: EXCELLENT
If the results were non-unique, a list of corporations would be handled as shown in the following code snippet:
04 | System.out.println( "\nQuery result:" ); |
05 | String sql = "SELECT c FROM Corporation c WHERE name=?1" ; |
06 | EntityManager entityManager = EMgrUtil.getEntityManager(); |
07 | Query query = entityManager.createQuery(sql); |
08 | query.setParameter( 1 , "Stone Corp" ); |
09 | corpAll = query.getResultList(); |
10 | for ( int i = 0 ; i < corpAll.size(); i++) { |
11 | System.out.println( "Corp name: " + corpAll.get(i).getName()); |
12 | List<Employee> empAll = corpAll.get(i).getEmployees(); |
13 | for (Employee employee : empAll) { |
14 | System.out.println( " Employee: " + employee.getName()); |
17 | String sql2 = "SELECT a FROM Award a WHERE corp_fk=?1 ORDER BY Award_Date DESC" ; |
18 | Query query2 = entityManager.createQuery(sql2); |
19 | query2.setParameter( 1 , corpAll.get(i).getId()); |
20 | List<Award> awards = query2.getResultList(); |
21 | System.out.println( " Last corporate rating: " + awards.get( 0 ).getValue()); |
Note the use of the Query method
getResultList() which returns a Java List of Objects.
Links:

Books:
 |
"Core Java 2, Volume 1: Fundamentals "
by Cay S. Horstmann, Gary Cornell
ISBN # 0132354764, Prentice Hall PTR 8th edition
The industry standard. Need I say more?
|
|
 |
"Core Java 2: Volume 2 Advanced Features "
by Cay S. Horstmann, Gary Cornell
ISBN # 0132354799, Prentice Hall PTR 8th edition
The industry standard. Need I say more?
|
|
 |
"Core Java Server Faces"
by David Geary, Cay S. Horstmann
ISBN # 0131738860, Prentice Hall PTR 2nd edition
|
|
 |
"JSP, Servlets, and MySQL"
by David Harms
ISBN # 0764547879, Hungry Minds, Inc
|
|