Enterprise Java Development@TOPIC@
Built on: 2015-11-18 03:26 EST
Copyright © 2015 jim stafford (jim.stafford@jhu.edu)
Abstract
This book contains course notes covering Enterprise Computing with Java. This course covers enterprise computing technologies using Java Enterprise Edition (Java EE). The course describes how to build multitier distributed applications, specifically addressing web access, business logic, data access, and applications supporting enterprise service technologies. For the web access tier, the focus will be on integrating the web tier with enterprise applications. For the business logic tier, session beans for synchronous business processing and message-driven beans and timers for asynchronous business processing will be described. The data access tier discussion will focus on data access patterns and the Java Persistence API. Finally, enterprise services will be discussed, including the Bean Validation API, Java Naming and Directory Interface (JNDI), Dependency Injection for Java (JSR-330), Context and Dependency Injection for JavaEE (CDI; JSR-299), remote method invocation (RMI), Java API for XML Binding (JAXB), and Java API for RESTful Wed Services (JAX-RS), Java Transaction API (JTA), Java EE security, and Java Message Service (JMS). Students will build applications using the technologies presented.
The material currently covers standards adopted as of JavaEE 6 and is being actively updated with information from JavaEE 7.
Table of Contents
This course covers enterprise computing technologies using Java Enterprise Edition (Java EE). The course describes how to build multitier distributed applications, specifically addressing web access, business logic, data access, and applications supporting enterprise service technologies. For the web access tier, the focus will be on integrating the web tier with enterprise applications. For the business logic tier, session beans for synchronous business processing and message-driven beans and timers for asynchronous business processing will be described. The data access tier discussion will focus on data access patterns and the Java Persistence API. Finally, enterprise services will be discussed, including the Bean Validation API, Java Naming and Directory Interface (JNDI), Dependency Injection for Java (JSR-330), Context and Dependency Injection for JavaEE (CDI; JSR-299), remote method invocation (RMI), Java API for XML Binding (JAXB), and Java API for RESTful Wed Services (JAX-RS), Java Transaction API (JTA), Java EE security, and Java Message Service (JMS). Students will build applications using the technologies presented.
The course is being actively updated to JavaEE 7 (JPA 2.1, EJB 3.2, and JMS 2.0) but a few JavaEE 6 APIs remain (JMS 1.1). The development environment used in class is JavaSE 8 and JavaEE 7-compliant.
Using modern development tools, commercial persistence providers, and application servers, students will design and implement several significant programming projects using the above mentioned technologies and deploy them to a Java EE environment that they will manage.
Prerequisite: 605.481 Distributed Development on the World Wide Web or equivalent
Strong Java programming skills are assumed.
Students should be prepared to spend between 10-16 hours a week outside of class.
"Enterprise Java Beans 3.1, 6th Edition", Andrew Lee Rubinger and Bill Burke, 2010, O'Reilly, ISBN-10: 0596158025; ISBN-13: 978-0596158026
A Servlet/JSP Text
"Core Servlets and JavaServer Pages (JSP)", Marty Hall and Larry Brown, 2003, Prentice Hall PTR; ISBN 0130092290
This course will make heavy use of development tools (Git, Maven 3, JUnit, Log4j, and Eclipse), a database (H2 Database Engine), persistence provider (Hibernate), and application server (JBoss/Wildfly 9). Students are required to establish a local development environment. Detailed instructions for setup are part of the first exercise (Development Environment Setup).
The core of the EJB 3.2 content is covered in the primary text for the course (and competing texts as well as online). Associated topics (database schema, JDBC, Web Tier, JMS, and tools) are not covered in the course text, but documentation will be provided through course slides, examples, and tutorials. A list of online references can be found on this site. Each lecture will supply a list of references for that technical area.
100 >= A >= 90 > B >= 80 > C >= 70 > F
Table 1.1.
Assessment | % of Semester Grade |
---|---|
Class/Newsgroup Participation | 10% |
Project Startup/Sanity Check | 5% |
Project 1 | 30% |
Project 2 | 30% |
Project 3 | 25% |
Projects will be done individually and graded 100 though 0 based on posted project grading criteria.
Class/newsgroup participation will be based on instructor judgment whether the student has made a contribution to class to either the classroom or newsgroup on a consistent weekly basis. A newsgroup contribution may be a well-formed technical observation/lesson learned, a well formed question that leads to a well formed follow up from another student, or a well formed answer/follow-up to another student's question. Well formed submissions are those that clearly summarize the topic in the subject, and clearly describe the objective, environment, and conditions in the body. The instructor will be the judge of whether a newsgroup contribution meets the minimum requirements for the week. The intent of this requirment is to promote public collaboration between class members.
There will be one required project startup/sanity check submission and will be primarily graded on a done/not-done basis. The intent of this requirement is to promote early, more-focused productivity and exchange of artifacts between the student and instructor.
Late projects will be deducted 10pts/week late, starting after the due date/time, with one exception. A student may submit a single project up to 4 days late without receiving approval and still receive complete credit. Students taking advantage of the free first pass should still submit an e-mail to the instructor and grader(s) notifying them of their intent.
Projects must be submitted via e-mail to the instructor and grader(s) with source code in a zip file with a README. All source code must be written to portably compile in the grader's environment using Maven 3. This will be clearly spelled out during the course and you will have plenty of opportinuties to test the process prior to submitting a project for grade.
Class attendance is strongly recommended, but not mandatory. The student is responsible for obtaining any written or oral information covered during their absence.
Collaboration of ideas and approaches are strongly encouraged. You may use partial solutions provided by others as a part of your project submission. However, the bulk usage of another students implementation or project will result in a 0 for the project. There is a difference between sharing ideas/code snippets and turning in someone else's work as your own. When in doubt, document your sources.
I am available at least 20min before class, breaks, and most times after class for extra discussion. I provide detailed answers to project and technical questions through the course newsgroup. You can get individual, non-technical questions answered via e-mail. I usually have all questions posted by 9pm answered by COB that evening. Students needing further assistance are also welcome make other arrangements during the week or schedule a web meeting using Adobe Connect.
Table 2.1. Data Tier
Session | Date | Topic | Comment |
---|---|---|---|
1 | Sep02 | Course Introduction | |
Rubinger & Burke: Ch 1-4 | |||
2 | Sep09 | ||
3 | Sep16 | ||
Rubinger & Burke: Ch 9 | |||
Project Startup/Sanity Check due by Sun, 8am Sep21 | |||
4 | Sep23 | Rubinger & Burke: Ch 10 | |
5 | Sep30 | Rubinger & Burke: Ch 11 | |
Rubinger & Burke: Ch 12 | |||
6 | Oct07 | Rubinger & Burke: Ch 13 | |
Project 1 due by Sun, 8am Oct18 | |||
Table 2.2. N-Tier Application/Enterprise JavaBeans
Session | Date | Topic | Comment |
---|---|---|---|
7 | Oct14 | Rubinger & Burke: Ch 5-7 | |
8 | Oct21 | Rubinger & Burke: Ch 16 | |
9 | Oct28 | ||
Rubinger & Burke: Ch 17 | |||
10 | Nov04 | ||
Project 2 due by Sun, 8am Nov15 |
Table 2.3. Security and Asynchronous Applications
Session | Date | Topic | Comment |
---|---|---|---|
11 | Nov11 | Rubinger & Burke: Ch 15 | |
12 | Nov18 | ||
Nov25 | Thanksgiving Break (no class) | ||
13 | Dec02 | Rubinger & Burke: Ch 8 & 19 | |
Asynchronous Methods | |||
EJB Timers | |||
14 | Dec09 | Complete Final Project (no class) | Project 3 due by Wed evening/midnight, Dec09 |
Project 0 will require construction of your development tree and early implemementation of technical requirements for class projects. The intent of this short/skeletal project is to get the student to a concrete starting point for course topics, eliminate wrong assumptions, and to generate early discussions about the development and development options/trade-offs. Grading will be primarily a done/not-done grade with unsatisfactory starts given feedback and a request to re-submit.
Specific Goals:
Instantiate a Maven development tree for projects 1 thru 3.
Setup JUnit test case(s).
Develop business logic interfaces and method signatures for end-to-end scenario.
Develop skeletal business objects used in the business logic interfaces.
Develop an early JUnit test that demonstrates understanding of the end-to-end requirements for project 1.
Project 1 will require the development of the business objects, O/R mapping of the business objects to a relational database, and business logic to integrate the end-to-end application. The artifacts from project 1 will also be used as a foundation for all follow-on projects as well. There is no application server used in project 1. All code is executes within a single JVM and database.
Specific Goals:
Develop and test a Java application
Develop and test a set of POJOs
Design a relational database schema
Develop and test a DAO using JPA Entities
Develop and test a business logic to manipulate data using DAOs.
Ingest test data into database
Project 2 deploys the business logic and data tier to the server-side. to be hosted in an EJB tier; providing resource management (e.g., threads), transactions, security (added in project 3), and remote access. The user interface will be supplied through the use of Servlets and JSPs integrated with the EJBs. A remote client will also be developed for the unit tests.
Specific Goals:
Deploy business logic within an EJB application using Session Beans
Configure applications using JNDI and CDI.
Integrate application with JTA
Access EJB business logic using remote interfaces
Integrate business logic with a Web tier
Project 3 enacts security for Project 2 and adds publish/subscribe topics and timers. An application server is used to host the application logic and JMS components. A stand-alone client is also developed to interface with the appication through JMS messages.
Specific Goals:
Protect access to applications using Java EE security
Authenticate users using Java EE security means
Design asynchronous interactions
Design JMS messages
Develop and test MDB and stand-alone JMS clients
Develop and test EJB Timers
Projects must build using Maven 3. Ant will be used late in the semester to wrap any interactive commands (e.g., launch JavaSE JMS subscriber)
All projects must be portable to build and deploy within grader and intructor environments. You may submit partial projects early to get portability feedback (not early content grading feedback).
Test Cases must be written using JUnit or another unit test frameworks that will run within a Maven surefire and failsafe environment.
Projects must be supplied with a README that points out how project meets requirements.
You should test your application prior to submission by
Run maven clean, zip your project from the root, and unzip in a new location. Move your localRepository (or set your settings.xml#localRepository value to a new location -- don't delete your primary localRepository) and run mvn clean install from the root of your project. This will make sure you do not have dependencies on older versions of your modules or manually installed artifacts. This will also make sure you are not depending on any old .class files. This, of course, will download all project dependencies and help verify that the project will build in other environments. This will also simulate what the grader will see when they unzip your project.
Coldstart your database instance(s) and run mvn clean install from the root of your project. This will make sure you are not depending on any residue schema or data in your database.
Make sure the README documents all information required to navigate your application (primarily a project 2 and 3 requirement) and point out issues that would be important for the grader/instructor to know (e.g., "the instructor said...")
You will e-mail the projects to the grader and me with the following subject line
(your name) project #; revision 0; part # of #
Your submission will include source zip and README (could be in source zip). You may also include pre-built artifacts if it does not add a huge size burden. The easiest way to do this is to zip up the project from the root after completing a build.
If you need to make a correction, the correction should have the following e-mail subject. The body should describe what you wish to revise.
* (your name) project #; revision N; part # of #
Submission e-mails (mail to all):
Jim - jim.stafford@jhu.edu
Table of Contents
Understand the purpose of enterprise applications
Understand the role of a container
Introduce the overall architecture for JavaEE
Introduce the standards contained within the JavaEE framework
At the completion of this topic, the student shall be able to:
Identity characteristics of enterprise applications
Identify the difference between a JavaEE API, JavaEE provider, and a JavaEE module
Identify the different container types for JavaEE
Identify the standards that make up a JavaEE
Core Language
Formerly called J2SE. Changed started with JavaSE 6
Java APIs are what applications are written against.
Standardized
Java Runtime Edition (JRE) is what applications run with.
Many providers
Java Developer Kit (JDK) is what applications are built with
Many providers
JRE and JDK provider choices are independent
Formerly called J2ME
Targeted at smaller devices
Small footprint
Limited power
Limited and sporadic bandwidth
Formerly called J2EE. Change started with JavaEE 5
Targeted at enterprise deployments commonly deployed to data centers
Persistence/Data Access
Scaling
Transactions
Security
Resource Management
Availability
Distributed
Web-based
Generally deployed in N-Tiers
Interface
Transacted Business Logic
Persistence/Back-end Integration
Contains
APIs (standardized)
Compatibility test suites
Reference Implementation (Glassfish)
Providers (e.g., Glassfish, JBoss/Wildfly, WebSphere, WebLogic)
Subset of JavaEE APIs targeted at key use cases
Share common underlying APIs for consistency
Provide smaller gates of entry for providers
Provide means to eliminate deprecated specs from concern
Risk fragmenting community and introducing confusion -- not taken lightly
Different types
Deployed to client (Applets)
Deployed to data center (Servlets and EJBs)
Undefined deployment (App Clients)
GUI components typically running in browser
Access middle-tier indirectly thru Web container and HTTP
Generate content to browser from Web container
Can be UI/HTML-based
Can be service/XML (or other structured data forms)-based
May contain EJBs providing transactional support
Provide runtime support for JavaEE application components
Provide an interpose layer around/between each component
Inject required components
Application developers may think of the Application Server==Container
Containers address specifics of the standards (e.g., deployment artifact structure)
System-level software component
Extends standard APIs into specific resources (e.g., JDBC)
Framework provided for:
Connection/Resource Pooling
Security Management
Transaction Management
Table 7.1. JavaEE 7 Specifications [2] [3]
Enterprise Application Technologies | Version | Web Profile | Course Relevance |
---|---|---|---|
Enterprise JavaBeans (EJB) | 3.2 | EJB-lite | Primary |
Java Persistence API (JPA) | 2.1 | Y | Y |
Context and Dependency Injection (CDI) | 1.1 | Y | Y |
Dependency Injection for Java (JSR-330) | 1.0 | Y | Y |
Common Annotations for the Java Platform | 1.2 | Y | Y |
Java Transaction API (JTA) | 1.2 | Y | Y |
Java Message Service API (JMS) | 2.0 | Y | |
Bean Validation | 1.1 | Y | Y |
Interceptors | 1.2 | Y | (maybe) |
Managed Beans | 1.0 | Y | |
JavaEE Connector Architecture | 1.7 | ||
JavaMail | 1.5 | ||
Batch Applications for the Java Platform | 1.0 | ||
Concurrency Utilities for Java EE | 1.0 |
Web Service Technologies | Version | Web Profile | Course Relevance |
---|---|---|---|
Java API for RESTful Services (JAX-RS) | 2.0 | Y | Y |
Java Architecture for XML Binding (JAXB) | 2.2 | Y | |
Java API for XML-based Web Services (JAX-WS) | 2.2 | ||
Web Services Metadata | 2.1 | ||
Java API for XML-based RPC (JAX-RPC) | 1.1 | ||
Java API for XML Registries (JAXR) | 1.0 | ||
Web Services for JavaEE | 1.4 |
Web Related Technologies | Version | Web Profile | Course Relevance |
---|---|---|---|
Servlet | 3.1 | Y | Minor |
JavaServer Pages (JSP) | 2.3 | Y | |
JavaServer Faces (JSF) | 2.2 | Y | |
Expression Language | 3.0 | Y | |
JSTL | 1.2 | Y |
Management Technologies | Version | Web Profile | Course Relevance |
---|---|---|---|
Java Authentication Service Provider Interface for Containers (JASPIC) | 1.1 | ||
Java Authorization Service Provider Contract for Containers (JACC) | 1.5 | ||
Java EE Management | 1.1 | ||
Java EE Deployment | 1.2 | ||
Debugging Support for Other Languages | 1.0 | Y |
Table 7.2. JavaEE 6 Specifications [4]
Enterprise Application Technologies | Version | Web Profile | Course Relevance |
---|---|---|---|
Enterprise JavaBeans (EJB) | 3.1 | EJB-lite | Primary |
Java Persistence API (JPA) | 2.0 | Y | Y |
Context and Dependency Injection (CDI) | 1.0 | Y | Y |
Dependency Injection for Java (JSR-330) | 1.0 | Y | Y |
Common Annotations | 1.1 | Y | Y |
Interceptors | 1.1 | Y | Y |
Java Transaction API (JTA) | 1.1 | Y | Y |
Java Message Service API (JMS) | 1.1 | Y | |
Bean Validation | 1.0 | Y | (maybe) |
Managed Beans | 1.0 | Y | |
JavaEE Connector Architecture | 1.6 | ||
JavaMail | 1.4 |
Web Service Technologies | Version | Web Profile | Course Relevance |
---|---|---|---|
Java API for RESTful Services (JAX-RS) | 1.1 | Y | |
Java Architecture for XML Binding (JAXB) | 2.2 | Y | |
Java API for XML-based Web Services (JAX-WS) | 2.2 | ||
Web Services Metadata | 2.1 | ||
Java API for XML-based RPC (JAX-RPC) | 1.1 | ||
Java API for XML Registries (JAXR) | 1.0 | ||
Web Services for JavaEE | 1.3 | ||
Java API for XML Messaging (JAXM) | 1.3 |
Web Application Technologies | Version | Web Profile | Course Relevance |
---|---|---|---|
Servlet | 3.0 | Y | Minor |
JavaServer Pages (JSP) | 2.2 | Y | |
JavaServer Faces (JSF) | 2.0 | Y | |
Expression Language | 2.2 | Y | |
JSTL | 1.2 | Y |
Management Technologies | Version | Web Profile | Course Relevance |
---|---|---|---|
Java Authentication Service Provider Interface for Containers (JASPIC) | 1.0 | ||
Java Authorization Service Provider Contract for Containers (JACC) | 1.4 | ||
Java EE Management | 1.1 | ||
Java EE Deployment | 1.2 | ||
Debugging Support for Other Languages | 1.0 | Y |
[5]
1.2
"CORBA Integration"; Dec 1999
RMI over IIOP
1.3
"Local Interface EJBs"; Sep 2001
Connector API
EJB (2.x) local interfaces and new CMP model
1.4
"Web Service Enabled"; Nov 2003
SOAP Web Services
Deployment, Management, JAAS
5
"Ease of Development"; May 2006
Annotations
Better Defaults
EJB and Servlet Dependency Injection
JPA, StAX, JAX-WS
6
"More Ease of Development"; Dec 2009
Profiles; making some components optional
Flexible deployments; no EJB.jar or EAR requirement
No interface EJBs
Component and Dependency Injection
7
"Offloading Additional Common Infrastructure Tasks and More Ease of Development"; June 2013
Additional Web standards; Web Sockets and JSON
HTTP Client API within JAX-RS
Batch API
Concurrency Utilities
More consistency between CDI and component models
More inherent integrations with Validation API
Simplified JMS API
JPA Schema generation
Made a technical case for JavaEE and EJB
Described an envolution of EJB
Showed where EJB fits within the JavaEE Specs
Introduced 4-5 types of EJBs
Introduced 3 EJB deployment types
"Java Platform, Enterprise Edition 6 Specification (JSR-316 Final Release)", Roberto Chinnici and Bill Shannon, http://jcp.org/aboutJava/communityprocess/final/jsr316, 2009 Sun Microsystems.
"Java Platform, Enterprise Edition 7 Specification (JSR-342 Final Release)", Linda DeMichiel and Bill Shannon, https://jcp.org/aboutJava/communityprocess/final/jsr342/index.html, 2013 Oracle America, Inc.
"JavaEE Version History", Wikipedia, http://en.wikipedia.org/wiki/Java_EE_version_history, September 3. 2012.
Table of Contents
Become familiar with the development environment used with class and commonly used for enterprise Java developments
At the completion of this topic, the student shall
have more understanding of:
Maven project structure
Maven project pom.xml
Maven/Eclipse integration
Eclipse development tasks
be able to:
Establish a Maven project structure
Create and execute a test for Java class
within Eclipse
from Eclipse, within Maven
Debug a Java test and its associated class
within Eclipse
from Eclipse, within Maven (remote debugging)
Follows a standard layout by convention
Can be customized
Root directory stays fairly clean
|-- pom.xml |-- src | |-- main | `-- test `-- target |-- classes |-- ex1-1.0-SNAPSHOT.jar |-- surefire-reports `-- test-classes
src
Source tree for all developed code
target
Build tree for artifacts created by the build
pom.xml
Defines the build
Managed by CM along with src tree
Related artifacts normally placed in src/compile, etc.
Managed by CM
Should not contain any auto-generated artifacts
src |-- main | |-- java | | `-- myorg | | `-- mypackage | | `-- ex1 | | `-- App.java | `-- resources `-- test |-- java | `-- myorg | `-- mypackage | `-- ex1 | `-- AppTest.java `-- resources `-- log4j.xml
main
Contains production artifacts for module
Built artifacts from this tree are generally placed in the produced archive
jar tf target/ex1-1.0-SNAPSHOT.jar META-INF/ META-INF/MANIFEST.MF myorg/ myorg/mypackage/ myorg/mypackage/ex1/ myorg/mypackage/ex1/App.class ...
test
Contains test artifacts for module
Built artifacts from this tree are not generally placed in the produced archive
A separate archive of built test artifacts can be optionally generated for reuse
[main or test]/java
Contains Java classes
Placed in a directory that matches the Java package for the class
[main or test]/resources
Contains property or other data files
Generally copied to target tree as-is or optionally filtered by the build
Resource filtering is when substitution ${placeholder}s in source files are replaced by their declared property values in the target tree. This is quite useful in customizing URLs, credentials, and other properties that might be unique to the current build environment. Properties can be defined in the pom.xml, settings.xml or injected into the build through various plugins (e.g., inject current date).
Not managed by CM
Contains auto-generated artifacts
Can be deleted at any time
target |-- classes | `-- myorg | `-- mypackage | `-- ex1 | `-- App.class |-- ex1-1.0-SNAPSHOT.jar |-- log4j-out.txt |-- surefire-reports | |-- myorg.mypackage.ex1.AppTest.txt | `-- TEST-myorg.mypackage.ex1.AppTest.xml `-- test-classes |-- log4j.xml `-- myorg `-- mypackage `-- ex1 `-- AppTest.class
classes
An exploded directory tree for built artifacts to be placed into the module archive
Mixture of Java and resource artifacts
test-classes
An exploded directory tree for built artifacts used for test
Mixture of Java and resource artifacts
Part of the test classpath
*-reports
Results of unit and integration testing
surefire-reports
unit test results
failsafe-reports
integration test results
Locate details of test pass/failures within this directory
$ mvn clean test ... Failed tests: testFail(myorg.mypackage.ex1.AppTest): app didn't return 0 Tests run: 2, Failures: 1, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ ... $ more target/surefire-reports/*.txt ------------------------------------------------------------------------------- Test set: myorg.mypackage.ex1.AppTest ------------------------------------------------------------------------------- Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.608 sec <<< FAILURE! testFail(myorg.mypackage.ex1.AppTest) Time elapsed: 0.019 sec <<< FAILURE! java.lang.AssertionError: app didn't return 0 at org.junit.Assert.fail(Assert.java:93) at org.junit.Assert.assertTrue(Assert.java:43) at myorg.mypackage.ex1.AppTest.testFail(AppTest.java:28) ...
{artifactId}-{version}.jar
Results of unit and integration testing
Archive with contents of target/classes
Can be installed into local Maven repo by build
log4j-out.txt
An example output file of the build/tests written to the target directory
Produced by log4j based on configuration in src/test/resources/log4j.xml or log4j.properties
Defines the artifact for dependents
Defines the build of the local module
<?xml version="1.0"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>myorg.myproject</groupId>
<artifactId>ex1</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>My First Simple Project</name>
...
<properties>
...
</properties>
<dependencies>
...
<dependencies>
<build>
<plugins>
...
</plugins>
</build>
</project>
Defines the type of module and automatically enacts a profile of plugs (default=jar)
packaging=jar - primary artifact is a Java archive
packaging=ejb - primary artifact is a JavaEE EJB
packaging=war - primary artifact is a JavaEE WAR
packaging=ear - primary artifact is a JavaEE EAR
packaging=pom - parent pom, used primarily for grouping leaf modules
packaging=... - custom extensions
Value assigned relates to dependency type
element
<dependency> <groupId>${project.groupId}</groupId> <artifactId>ejbsessionBankEAR</artifactId> <version>${project.version}</version> <type>ear</type> <scope>compile</scope> </dependency>
Used to uniquely identify the module
<groupId>myorg.myproject</groupId>
<artifactId>ex1</artifactId>
<version>1.0-SNAPSHOT</version>
groupId
Hierarchical name used to group a set of one or more artifacts
Represented as a directory structure in repositories
myorg/ `-- myproject `-- ex1 |-- 1.0-SNAPSHOT | |-- ex1-1.0-SNAPSHOT.jar | |-- ex1-1.0-SNAPSHOT.pom ...
artifactId
Base name for the artifacts produced by this module
1.0-SNAPSHOT/ |-- ex1-1.0-SNAPSHOT.jar |-- ex1-1.0-SNAPSHOT.pom |-- ex1-1.0-SNAPSHOT-tests.jar
version
Version ID for the artifact
Release: 1.0
Snapshot: 1.0-SNAPSHOT
Timestamped release that does not change name
Useful for active development not yet ready for release
Dependents poll for new snapshots and get the latest version
Easy to use during development but nearly impossible to manage changes
-SNAPSHOT suffix added to version
<version>1.0-SNAPSHOT</version>
1.0-SNAPSHOT/ |-- ex1-1.0-SNAPSHOT.jar
Locations where Maven stores and locates artifacts
Each user has a local repository referenced by ${settings.localRepository}
.
This defaults to $HOME/.m2/repository
<settings xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>c:/jhu/repository</localRepository>
...
MAVEN_HOME/conf/settings.xml defines a core set of remote repositories
Maven poms can define extensions to the remote repository list
<repositories>
<repository>
<id>jboss-nexus</id>
<name>JBoss Nexus Repository</name>
<url>https://repository.jboss.org/nexus/content/groups/public-jboss/</url>
</repository>
</repositories>
Maven will first look for a dependency artifact in the local repository
Maven will then look for a dependency artifact in a remote repository like ibiblio
Maven will search all known repositories until the artifact is found or all repositories have been checked
Maven will install module artifacts into the local repository during the install
phase of the build
$ mvn install ... [INFO] --- maven-install-plugin:2.3.1:install (default-install) @ ex1 --- [INFO] Installing /home/jcstaff/proj/ejava-javaee/solutions/ex1/target/ex1-1.0-SNAPSHOT.jar to /home/jcstaff/.m2/repository/myorg/myproject/ex1/1.0-SNAPSHOT/ex1-1.0-SNAPSHOT.jar [INFO] Installing /home/jcstaff/proj/ejava-javaee/solutions/ex1/pom.xml to /home/jcstaff/.m2/repository/myorg/myproject/ex1/1.0-SNAPSHOT/ex1-1.0-SNAPSHOT.pom [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------
-U
to force an update checkMaven will limit its search of a remote repository to
expressed time limits. If it attempts and fails to locate
an artifact from a remote repository it will not re-attempt
to query that repository until the time limit expires.
Sometimes the failure has nothing to do with the repository
not having the artifact (e.g., wireless network down) and
when you clear the error you can force Maven to retry before
the timeout period expires by adding a -U
to the command line.
-o
offline flag to eliminate repository checksMany times Maven will be too aggressive searching for artifacts you already know are not of value to the current build. You can tell Maven to work in an offline mode to bypass the work involved. This is helpful when you are disconnected from the network but will fail if you are truly missing a required artifact. You can activate/deactivate the offline more consistently using an element in the settings.xml
<offline>false</offline>
Provide a means to define behavior of the build
Provide a means to define values once and re-use across the build
Promotes consistency
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven-compiler-plugin.version>2.5.1</maven-compiler-plugin.version>
<java.source.version>1.8</java.source.version>
<java.target.version>1.8</java.target.version>
</properties>
Properties can also be expressed using -Dkey=value
system properties
Modules depend on artifacts from other modules
<dependencies>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
....
</dependencies>
GAV
Must provide its groupId, artifactId, and version for each dependency
scope
Defines when the dependency applies (default=compile)
scope=compile - classpath of src/main, src/test, and dependents
scope=provided - classpath of src/main, src/test but not dependents
scope=test - classpath of src/test but not src/main and dependents
scope=runtime - classpath of src/test and dependents but not of src/main
scope=import - used to replace locally declared dependencyManagement dependencies with the contents from another pom. Useful when declaring complex dependencies and want to make easy wholesale changes in dependency versions.
scope=system - used to declare a direct reference to a local archive. Not portable.
type
Defines the artifact type of ther module (default=jar)
type=jar - java archive
type=ejb - javaee EJB archive
type=war - javaee WAR archive
type=ear - javaee EAR archive
type=... - custom type
Value directly relates to the project packaging element of the dependency
|-- ejbsessionBankImpl | |-- ejbsessionBankImpl-3.0.2013.2-SNAPSHOT.jar |-- ejbsessionBankEJB | |-- ejbsessionBankEJB-3.0.2013.2-SNAPSHOT.jar |-- ejbsessionBankWAR | |-- ejbsessionBankWAR-3.0.2013.2-SNAPSHOT.war |-- ejbsessionBankEAR | |-- ejbsessionBankEAR-3.0.2013.2-SNAPSHOT.ear
classifier
Defines a special type of artifact/custom from a module
classifier=tests - java archive containing test artifacts
classifier=sources - java archive containing module source code
type=... - custom classifiers
`-- target ... |-- txHotelDAO-3.0.2013.2-SNAPSHOT.jar |-- txHotelDAO-3.0.2013.2-SNAPSHOT-sources.jar `-- txHotelDAO-3.0.2013.2-SNAPSHOT-tests.jar
Provides specific instructions for build
Usually does the right thing by default when possible
All actions of the build are directed by plugins
Even the simplest commands will result in plugin downloads when you first start
Specific plugins are wired into the build by the module packaging type
Plugins have goals (analogous to public methods)
Plugins may have default phases for the goals to be executed
Plugins can be configured
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>${java.source.version}</source> <target>${java.target.version}</target> </configuration> </plugin> <!-- surefire.argLine is set in debugger profile --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.17</version> <configuration> <argLine>${surefire.argLine}</argLine> </configuration> </plugin> ...
New plugins can be added, but will need to be wired into a specific lifecycle phase of the build.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-failsafe-plugin.version}</version>
<configuration>
<argLine>${surefire.argLine}</argLine>
</configuration>
<executions>
<execution> <!-- run the tests here -->
<id>integration-tests</id>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
<execution> <!-- delay failures to after undeploy -->
<id>verify</id>
<phase>verify</phase>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
The above example shows the failsafe plugin being configured to share some properties with surefire and have specific goals wired into the integration-test and verify lifecycle phases.
Maven build broken into phases
Plugins are added to the build and their goals execute during a specific phase
Build invoked using the mvn
command
$ mvn (phase) (plugin:goal) -D(system-property) -P(profile)
phase
clean
Remove contents of target tree
test
Build the src/main and src/test tree and execute the unit tests as defined by the surefire plugin
pre-integration
Perform unit tests and perform any steps required to ready for integration test
integration-test
Perform integration tests as defined by the failsafe plugin
post-integration-test
Tear down anything required to perform integration tests
verify
Evaluate results of integration tests
install
Install module artifacts into local repository
(plugin:goal)
Manually trigger a plugin goal without a specific build lifecycle
ex. jetty:run, dependency:tree, or dependency:help
-D(system-property)
Manually define a system property key or key and value to be used as a property in the build
Must configure surefire/failsafe to promote these as properties Junit -- these are system properties of the maven build and not of the JVM tasks kicked off by the maven build.
-P(profile)
Manually trigger build configurations latched by a profile
e.g., which DB driver to use
-P!(profile)
Manually turn off a profile
e.g., turn off a profile activated by a rule or settings.xml
The bash shell requires the bang ("!") character to be escaped. Use -P\!(profile) when using the bash shell.
Provide groups of conditional configuration
Adds flexibility to build
Flexibility can sometimes add confusion for both developers and build tools -- be careful
Can consist of
properties
dependencies
build elements
...
Cannot include GAV elements
<profile> <!-- defines our default database -->
<id>h2db</id>
<properties>
<jdbc.driver>org.h2.Driver</jdbc.driver>
<jdbc.url>jdbc:h2:\${basedir}/target/h2db/ejava</jdbc.url>
<jdbc.user>sa</jdbc.user>
<jdbc.password/>
<hibernate.dialect>
org.hibernate.dialect.H2Dialect
</hibernate.dialect>
</properties>
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</profile>
The example above defines the properties and dependencies for the H2 database driver
when the h2db
profile is active
Use -P(profile)
to manually trigger a profile to be included
$ mvn test -Ph2db
Use -P!(profile)
to manually trigger a profile to be excluded. Use -P\!(profile) with bash shell
$ mvn test -P\!h2db
Specify an active profile in settings..xml
<activeProfiles> <activeProfile>jboss7</activeProfile> <activeProfile>h2db</activeProfile> </activeProfiles>
Property having a value
<profile>
<id>h2db</id>
<activation>
<property>
<name>jdbcdb</name>
<value>h2</value>
</property>
</activation>
The h2db profile will activate when -Djdbc=h2 is specified on the command line
Property existing
<profile>
<id>h2db</id>
<activation>
<property>
<name>h2db</name>
</property>
</activation>
The h2db profile will activate when -Dh2db is specified on the command line -- no matter the value
Property not existing
<profile>
<id>h2db</id>
<activation>
<property>
<name>!h2db</name>
</property>
</activation>
The h2db profile will activate when -Dh2db is not specified
Property not a spefici value
<profile>
<id>h2db</id>
<activation>
<property>
<name>jdbcdb</name>
<value>!hsql</value>
</property>
</activation>
The h2db profile will activate in cases when -Dhsql is not equal to hsql
Maven modules boundaries are highly influenced by the re-usable artifacts that must be produced
If you have many .jars, you likely have many maven modules
If you have few but large .jars, you likely have few but large maven modules
If you require different types of artifacts, you likely have different maven modules
Maven can be used in multi-module configuration
Many maven modules share the same configuration needs -- could lead to duplication
POM Inheritance helps mitigate duplication issues
Common properties can be defined in parent pom
<project> <modelVersion>4.0.0</modelVersion> <groupId>ejava</groupId> <artifactId>ejava-root</artifactId> <version>3.0.2012.2-SNAPSHOT</version> <packaging>pom</packaging> <name>Enterprise Java</name> <scm> <url>https://jcstaff@github.com/jcstaff/ejava-javaee/tree/master</url> <connection>scm:git:git@github.com:jcstaff/ejava-javaee.git</connection> <developerConnection>scm:git:git@github.com:jcstaff/ejava-javaee.git</developerConnection> </scm> ...
In the above example -- the parent pom is defining the CM repository for all sub-modules
Child projects can inherit from a parent pom
<project> <parent> <groupId>ejava</groupId> <artifactId>ejava-root</artifactId> <version>3.0.2012.2-SNAPSHOT</version> <relativePath>..</relativePath> </parent> <modelVersion>4.0.0</modelVersion> <groupId>ejava.javaee.intro</groupId> <artifactId>javase5Enhancements</artifactId> <name>Java SE 5 Enhancements</name> ...
In the above example, the javase5Enhancements project inherits the CM configuration from the parent
If you include a relativePath to the parent, then changes to parents in your build environment will be immediately reused without having to install them into the localRepository.
Be careful not to attempt to blindly merge all project definition into a single parent pom. This can bloat the inheriting children with unwanted dependency, cause slower builds, and can even break a build. When in doubt -- push active definitions (like dependencies and plugin declarations) to the leaf modules and leave only passive declarations (like properties, dependencyManagement, and pluginManagement) in the parent.
Parent modules can be created to delegate build commands to a group of child modules
Child modules can be physically placed anywhere, but commonly in immediate sub-directories named after their artifactId
<project>
<parent>
<groupId>ejava</groupId>
<artifactId>ejava-root</artifactId>
<version>3.0.2012.2-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>ejava.javaee.ejb</groupId>
<artifactId>ejbsessionBank</artifactId>
<packaging>pom</packaging>
<name>Session Bean</name>
<description>
This project is the root project for the core session bean example.
</description>
<modules>
<module>ejbsessionBankImpl</module>
<module>ejbsessionBankEJB</module>
<module>ejbsessionBankWAR</module>
<module>ejbsessionBankEAR</module>
<module>ejbsessionBankTest</module>
</modules>
</project>
The example above has several child modules that it will build in the order they are specified.
Defines what is possible without actively imposing the dependency or plugin defined
Placed in a parent of multi-module project
Consolidate management of dependency versions without adding active dependencies to all inheriting children
<properties>
<junit.version>4.7</junit.version>
<log4j.version>1.2.13</log4j.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
...
</dependencies>
</dependencyManagement>
Parent defines dependency versions for two artifacts (passive)
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Child declares a dependency on one of the artifacts (active)
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
Net result is a consistent version for dependency in effective pom
Control artifact versions used in transitive dependencies
Exclude artifacts from transitive dependencies
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jboss.jbossas</groupId>
<artifactId>jboss-as-server</artifactId>
<version>${jboss-as-server.version}</version>
<classifier>jmx-invoker-adaptor-client</classifier>
<exclusions>
<exclusion> <!-- gets in the way with JBoss6 and M2 -->
<groupId>org.jboss.security</groupId>
<artifactId>jbosssx-client</artifactId>
</exclusion>
<exclusion> <!-- gets in the way with newer versions -->
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
Parent defines exclusions for possible dependency (passive)
<dependencies>
<dependency>
<groupId>org.jboss.jbossas</groupId>
<artifactId>jboss-as-server</artifactId>
<classifier>jmx-invoker-adaptor-client</classifier>
</dependency>
Child includes simple dependency declaration (active)
Consolidate management of plugin versions and default configuration without adding activations to all inheriting children
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.source.version}</source>
<target>${java.target.version}</target>
</configuration>
</plugin>
...
</plugins>
</pluginManagement>
</build>
Parent defines version for compiler plugin and configures for Java 8
<parent>
<groupId>ejava</groupId>
<artifactId>ejava-root</artifactId>
<version>3.0.2012.2-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>ejava.javaee.intro</groupId>
<artifactId>javase5Enhancements</artifactId>
<packaging>jar</packaging>
Child of packaging=jar type automatically get compiler configured to parent specs
A more complicated example
<pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>${cargo-maven2-plugin.version}</version>
<configuration>
<container>
<containerId>${cargo.containerId}</containerId>
<type>remote</type>
<log>target/server.log</log>
<output>target/output.log</output>
</container>
<configuration>
<type>runtime</type>
<properties>
<cargo.hostname>${jboss.mgmt.host}</cargo.hostname>
<cargo.jboss.management.port>${jboss.mgmt.port}</cargo.jboss.management.port>
</properties>
</configuration>
</configuration>
<dependencies>
<dependency>
<groupId>org.jboss.as</groupId>
<artifactId>jboss-as-controller-client</artifactId>
<version>${jboss.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>cargo-prep</id>
<phase>pre-integration-test</phase>
<goals>
<goal>redeploy</goal>
</goals>
</execution>
<execution>
<id>cargo-post</id>
<phase>post-integration-test</phase>
<goals>
<goal>undeploy</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
Parent defines details of deployment to JBoss container (passive)
<build>
<plugins>
<!-- artifacts to deploy to server -->
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<configuration>
<deployables>
<deployable>
<groupId>${project.groupId}</groupId>
<artifactId>ejbsessionBankEAR</artifactId>
<type>ear</type>
</deployable>
</deployables>
</configuration>
</plugin>
...
</plugins>
</build>
Child declares plugin (active) with configuration extensions specific to child project
Maven/Eclipse integration is handled by adding the M2E plugin to your Eclipse environment and followng a pom-first configuration strategy.
Import an existing maven project into Eclipse as a maven project
M2E will inpsect the pom.xml and configure Eclipse accordingly
M2E provides mechanisms to create new maven projects (we will not discuss)
Notice the Eclipse project dependencies are in sync with the versions specified in the pom.xml
If you make a valid change to the pom.xml the Eclipse dependencies will update
Dependency hierachy shows
Which modules bring in other modules
Specific versions and where there are conflicts
When things are not automatically updated, try...
Refresh/F5
Project->Clean
Maven->Update Projects
A build from the command line that includes a clean
and fails to complete can leave Eclipse in an unhappy state
(lots of red unresolved references). Resolve with a successful
build and repeat some/all of the above actions.
Working sets can be quite helpful organizing multi-modules
You can control their contents, order them, and hide them
Maven builds can be directly launched from within Eclipse
No significant difference from normal command line
Can create custom launchers for common targets and pin for easy recall
Unit tests can be directly executed within Eclipse without Maven
Maven is quite useful in setting up robust test environments but at a cost of repeated seconds of unnecessary work at times. Have your unit tests assume reasonable defaults and provide the ability to optionally override those values through surefire or launcher configurations. That way you can develop by compiling only changes classes within Eclipse and move forward in only a few seconds. Delegate the maven build at that point to batch regression testing.
Necessary evil in application development
Powerful capabilities should not be ignored
Set program breakpoints where problems have been identified
If runnable directly from Eclipse -- use debug option
If not runnable within Eclipse -- use remote debugging option
Remote debugging requires special system properties issued to JVM
surefire and failsafe can be setup to optionally pass system properties to JUnit JVM
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<argLine>${surefire.argLine}</argLine>
<testSourceDirectory>src/test/java</testSourceDirectory>
</configuration>
</plugin>
<profile> <!-- tells surefire to run JUnit tests with remote debug -->
<id>debugger</id>
<activation>
<property>
<name>debugger</name>
</property>
</activation>
<properties>
<surefire.argLine>-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -Xnoagent -Djava.compiler=NONE</surefire.argLine>
</properties>
</profile>
Copyright © 2015 jim stafford (jim.stafford@jhu.edu)
Built on: 2015-11-18 01:27 EST
Abstract
This document contains project specifications for the eMarket project. There are three projects associated with eMarket; data tier and business logic, server-side deployment, and security plus asynchronous actions. Each will be part of the follow-on project.
Please follow the project submission guidelines posted in the course overview.
Table of Contents
Table of Contents
Gain experience designing and testing project implementations around layered boundaries.
Build the data access tier and initial business logic for an application.
Gain experience with database schema and Java Persistence API technologies.
Design and implement a set of business logic interfaces that address the functional requirements of the external users.
Design and implement a set of business objects to represent the data requirements in the system.
Design and implement a relational database schema that will store the state of the business objects.
Design and implement a data access tier that will handle mapping the business objects to the database using the Java Persistence API.
Design and implement the business logic classes that realize the business logic interfaces and leverage the data access objects to implement the scenario logic.
Unit test the different levels of the architecture.
Integration test the end-to-end solution according to a defined scenario.
eSales is planning an on-line auction site to allow sellers to auction items and buyers to place bids and ultimately purchase products. At the same time, eBidbot is planning an on-line automated bidding site that will help bidders make bids with eSales.
Sellers can start an auction using the eSales on-line auction site. They specify a title, category, description, asking price, start/stop time, and any images. Using the eSales buyer interface, buyers can manually bid on auctions until they close.
With eBidbot, buyers also have the option of using an independent site to place their bids. Buyers place orders with eBidbot, providing them the auction and min/max bidding parameters. eBidbot will become a trusted client of eSales and be able to make bids on behalf of the actual buyer.
Both eSales and eBidbot have come to you to develop the initial phase of their applications. You are tasked with implementing a low-cost prototype, based on current standards, to automate much of this activity. At this point in the project we are primarily looking to build the data access tiers for both the eSales and eBidbot (two separate systems). We will also add a minor amount of business logic to coordinate the data access between the individual data access objects.
For eSales, the system is initialized with a starting set of information pertaining to auctions, accounts, and addresses. New information can be added and updated.
For eBidbot, the system is initialized with an empty database that is designed to hold bids. The bids will be for auctions in eSales, an associated account, and maximum bid price. The eBidbot site will make bids for the account until the auction is closed or bidding has exceeded the defined maximum. Information about the ietm being auctioned will be obtained through an interface with eSales implemented later.
The work done during this project focuses on the business objects (BOs), the data access objects (DAOs) of the data access tier, and some initial business logic interfaces (BL) and business logic implementations (BLImpl). The DAOs will be based on the Java Persistence API (JPA). How you partition the implementations of your projects is up to you. A suggested starting point is provided at the end of this project description.
There will be many choices while creating your solution to the two applications. It is intended that you always approach the eSales application as "it is hard" and requires extra formal layers. To provide contrast, it is intended that you always approach the eBidbot application as "it is simple" and target as many simplification and shortcuts in the architectural layers as you can find. The point of this is for you to gain experience with the different approaches and to not make Bidbot a tedious clone of approaches as eSales. The spec will describe both in a similar manner, but you should apply more of a "right approach" based on what was just stated.
The business objects encapsulate the core business data and rules of the application. You will design the business objects and then map them to the database. You are required to implement required CRUD (Create, Read, Update, and Delete) capability with JPA but you only need to implement the CRUD methods that are necessary to complete the provided end-to-end scenario. You will be required to encapsulate all object to relational (O/R) mapping within the DAOs, descriptor files, and/or class metadata annotations. You will be given a set of test data to initially populate your applications and be the source of data for the ingest requirement. To use the data, you will ingest using a parser supplied by the instructor. There is a sample thread in the projects/eMarket/eSales directory that shows how to use the parser as well as other aspects technology within the project.
The database will have a functional schema and indexes to provide query performance. Business objects will be validated in the database using schema constraints and within the JVM using the Validation API.
The business logic will provide a set of classes with concise methods that map easily to the provided end-to-end scenario. The business logic will ensure proper use of the overall application, delegating some business logic functionality to the business objects and full O/R responsibility to the DAOs. eSales will have an ingest requirement as well as the requirement to manipulate and add to what was ingested. eBidbot will start fresh and obtain all data from users and coordinate with eSales. However, for project 1, eBidbot will be unable to fully implement data exchange with eSales because remote interfaces will not be implemented until project2. Some of that must be stubbed at this point. You are only required to implement enough business logic methods that it takes to implement the end-to-end scenarios specified later within this specification.
The test acceptance for the first project will be the unit tests and an integration test that takes the business logic, data access tier, and business objects through a provided end-to-end scenario that will never change during the semester. You are required to supply the following tests:
A unit test that implements the steps of the provided eSales end-to-end scenario
A unit test that implements the steps of the provided Bidbot end-to-end scenario
At least one unit test per architectural layer (BO, DAO, BL) that demonstrates your ability to test at that level.
The amount of work that you can implement within the bounds of this project spec could be endless if you attempted to account for everything/anything. It is very important that you limit your work to only what is necessary to implement (and test) the functionality required for the end-to-end scenario. That means you may have entities that are designed to be created but never modified while having other entities that go through a full lifecycle. When deciding to add or skip certain capabilities -- always ask yourself "does the business logic need this behavior to implement the end-to-end scenario".
Since the work is for separate applications, we will need to establish two separate application projects for this work; eSales and eBidbot. The development can physically share resources (e.g., same database and application server), but should be easily separated. It is suggested that you form a root for the overall eMarket work to coordinate integration testing, and then group the lower-level work under two child projects; eSales and eBidbot. See some suggested project layouts at the bottom of this specification. A sample set of projects that implement a thin eSales thread has been made available. Please ignore the TestData sub-project when using the example to craft your source tree. You will depend on projects within that tree -- not re-implement them and not copy them. See the Getting Started section towards the end of this specification for a more detailed sample project layout.
You will likely copy significant portions of the thin thread example and other class examples into your project. Be aware that the thin thread and other example pom.xml files inherit from the class root project that provides dependencyManagement and pluginManagement duties. You will either need to also inherit from the dependencies/pom.xml or compensate by re-defining the management sections in the root pom.xml of your project.
Since students in the class will be producing parallel implementations for the applications and submitting them for evaluation, it is asked that you come up with unique names for your artifacts. This could be done by replacing the "e" in eMarket, eSales, and eBidbot with a unique name that corresponds to your newsgroup or college login (ex. jcsMarket, jcsSales, jcsBidbot). You should also use this same pattern for java packages (ex. jcs.sales, jcs.bitbot), DB tables (e.g., JCS_), etc.
There should be no use of System.out.println() in the code and all implementations must use a logging API with log4j as the logging provider. You may leave debug in your code, but this should be able to be turned on/off with the proper logging priority changes in the log4j.xml configuration.
Design 2 sets of database schema that account for the following information. Although we will deploy the 2 database schemas to the same database for the project, they should be designed to be independently deployed to separate databases. eSales and eBidbot are two independent applications. This will primarily affect your attempt re-use tables or to make primary key assumptions between the two.
This table stores singleton account information that will not be shared with external users
USER_ID
The user login for the account must be maintained by the database so that we can associate the account with a specific user after they login. This information is unique and cannot be changed, so it can also be used as a primary key field.
FIRST_NAME
text field to hold a person's first name
MIDDLE_NAME
text field to hold a person's middle name
LAST_NAME
text field to hold a person's last name
START_DATE
date when user started with eSales
END_DATE
date when user closed account with eSales
since we want to protect personal information, the public contact information can be placed in a separate table so it can be more freely shared. This has a 1:1 relationship with ESALES_ACCT and could be done as a primary key join.
USER_ID
(1:1 relationship with ESALES_ACCT)
EMAIL
valid e-mail address to contact user. This field should be unique.
users can provide various types of addresses for use at the site. Each will have a name that defines its purpose. Since billing is not a part of the initial project, the only address we are concerned with is a shipping address. Items are also shipped to addresses. Note that an address for a shipped item may need a way to keep a user's change-of-address from modifying the historical fact of where it was shipped. This may mean that address fields cannot be changed, changes once referenced, or copied for use by the shipping side of the application.
ID
there is no single natural field within address that forms a primary key, so a generate value can be used.
NAME
identifies the type of address (e.g., "SHIPPING")
TO
identifies the recipient at the address.
STREET
CITY
STATE
ZIP
There is a 1:N relationship between accounts and addresses. If we want addresses to stay independent of the private account information, we can form a foreign key relationship through a secondary link table. Since this is a 1:N relationship, the foreign key pair can be constrained to be unique by the database. Otherwise, we can simply put a foreign key in the address table.
USER_ID
(N:1 relation to ESALES_ACCT)
ADDRESS_ID
(1:1 relation to ESALES_ADDRESS)
This will anchor the information associated with an auction. Auctions will progress from bidding to sale. Once the bidding is over, the winning bid information can be retained within this table and the incremental bid history purged. Be sure to design in an easy way to determine if an auction item is expired or officially over; even if it did not sell or ship yet. Note that end-of-auction processing will be looking for auctions that have expired, but not officially closed. Buyers and bidders will be looking for auctions that have not closed. You may have to adjust the schema suggested below to accommodate.
ID
there is no natural field for forming a primary key value within this table.
TITLE
CATEGORY
just a text name for now. There will be no defined set of categories.
DESCRIPTION
this will be a straight text narrative.
START_TIME
END_TIME
this is a date in the future when the auction is taking place.
ASKING_PRICE
this forms a minimum bid value.
PURCHASE_PRICE
this comes from the AMOUNT of the winning ESALES_BID before it is purged.
BUYER_ID
(N:1 relation to ESALES_USER) this comes from the BIDDER_ID of the winning ESALES_BID before it is purged.
SELLER_ID
(N:1 relation to ESALES_USER)
SHIPTO_ID
(1:1 or N:1 relation to ADDRESS) We should probably copy the buyer's shipping address since a later update of the user's shipping address should not modify a historical record of where this item was shipped to. No bid deal during this overall project.
bids for an active auction should be retained for viewing. Once the bidding is over, however, the loosing bid information is no longer needed. The winning bid can be kept around or copied into the auction as part of a sale record.
ID
there is no natural field that represents a primary key
BIDTIME
AMOUNT
ITEM_ID
(N:1 relation with ESALES_AUCTIONITEM)
BIDDER_ID
(N:1 relation with ESALES_USER)
design a table to store eBidbot user account information. No need for names here, we just want a userId of some sort and their eSales credentials (they trust us). For simplicity, make the eBidbot SALES_ACCOUNT column have an equal value to the USER_ID column and rely on the fact that their userIds will be the same. eBidbot will be a trusted user of eSales, but we have no way (within the scope of the project) to verify the eSales account ID provided by the user unless it is the same as the eBidbot login.
USER_ID
this user's login will be used as a primary key.
SALES_ACCOUNT
bidder's login name for eSales
design a table to hold eBidbot information associated with an order. This application will use the information to know the specific auction, bid parameters, and final results (cached from eSales result).
ID
AUCTION_ID
logically, this is a N:1 relationship to ESALES_AUCTIONTIME. However, there should be no physical database references between the two applications. All integration will come at the higher application levels.
START_BID
whether we jump in with this bid or wait until it reaches this value is up to you.
MAX_BID
don't bid any more that this amount.
COMPLETE
know if auction is complete for order.
RESULT
know if we won or not.
FINAL_BID
know purchase price for winner. The buyer will owe this amount if the result says he won.
USER_ID
(N:1 relation to EBIDBOT_BIDACCT)
design a class that encapsulates the singleton private information for a user. This class will reference other objects in the system, but will not be referenced by others.
userId:String
use login name. This could be optionally dropped in favor of using the identical property within the referenced POC object.
firstName:String
middleName:String
lastName:String
startDate:Date
endDate:Date
addresses:Map<String, Address>
addresses can be keyed by their name.
poc:POC
design a class that holds addressing information. This will primarily be used as a shipping address.
id:long
name:String
indicates type of address. This should be unique per account.
to:String
the recipient at the address
street:String
city:String
state:String
zip:String
design a way for public point of contact information to be exposed for a user.
userId:String
email:String
auctions:Collection<AuctionItem>
provides direct navigation to auctions the user was a seller. This property could be dropped in favor of a uni-directional mapping from the AuctionItem side.
bids:Collection<Bid>
provides direct navigation to bids the user has placed on open auctions. This property could be dropped in favor of a uni-directional mapping from Bid.
purchases:Collection<AuctionItem>
provides direct navigation to auctions the user has won. This property could be dropped in favor of a uni-directional mapping from AuctionItem.
design a way to record bid information while an auction is still open.
id:long
amount:double
timestamp:Date
bidder:POC
auction:AuctionItem
design a class to encapsulate the information for an auction. AuctionItems will need to know if they are still open, expired but not yet completed, or closed. When searching for an auction to bid on, you should only see items that are still officially open.
id:long
title:String
category:Enum
the full list of categories is still TBD, but a candidate set will be provided with the test data.
description:String
startTime:Date
endTime:Date
askingPrice:double
no bid should be allowed below this value.
purchasePrice:double
filled in from winning bid when auction is complete.
bids:List<Collection>
bids are retained as long as the auction is active. They can be maintained in an ordered list sorted by bid amount to make locating the highest bid easier to find.
buyer:POC
filled in when auction is complete from from winning bid when auction is complete.
shipTo:Address
address item is to be shipped to. This is filled in with the shipping address of the user when the auction is complete.
seller:POC
images:List<Image>
auctions can have zero or more images associated with them.
design a class that encapsulates the bidder information for automated bidding.
userId:String
account login for eBidbot
salesAccount:String
account login for eSales
salesPassword:String
account password for eSales
orders:Collection<Order>
design a class that defines the parameters for making a bid against a specific auction and its results.
auctionId:long
eSales auction Id
startBid:double
starting bid for user. You can determine when they should start with this value; right away or wait.
maxBid:double
no bids should be made above this value.
complete
cache the active/complete state of the auction to help limit activity for application and cache results.
result
provide information that indicates whether the bidder won or not.
finalBid
cache the winning bid amount from eSales.
Add validation API declarations to your business objects as appropriate. This need not be extensive or exhaustive. Just do enough to show use of declarative validation as a part of your application.
Determine the validation groups you wish to use in your application (suggest business logic interface and persistence tier).
Add validation annotations to your business objects and assign them to your designed groups as appropriate.
Manually invoke validation from the your junit test and integrate the validation into your persistence unit.
Design and implement a mechanism to ingest a starting state for eSales based on a provided data file and parser. You will implement two primary sets of classes to support this requirement; the DAO(s) and an Ingestor.
Design and implement a set of DAOs that can be used to ingest eSales business data into the database using the database schema you designed as a part of a separate requirement. These DAOs can optionally be tuned for ingest or simply reused from your CRUD-style requirements.
Design and implement an Ingestor that will use an externally provided parser to obtain business data for eSales and use the Ingest DAO to populate the database.
Please ignore references in the diagrams that call out use of JDBC. All DAOs can be implemented exclusively with JPA for this assignment.
Design and implement a DAO layer that will map the business objects between the object model and the database using the Java Persistence API (JPA). These DAOs will support all standard CRUD operations and can optionally implement the same interface as other potential DAO implementations. The implementation can make liberal use of JPA @Annotations, descriptor files, or a combination of both. Your interface should encapsulate the fact that an EntityManager is being used and the same EntityManager should be shared among other DAOs in the same Thread. Your DAOs should not attempt to control the transaction or they will NOT be portable to the EJB tier.
AccountDAO/JPAAccountDAO
encapsulates the use of JPA when mapping account and closely associated business objects to/from the database.
AuctionDAO/JPAAuctionDAO
encapsulates the use of JPA when mapping auction and closely associated business objects to/from the database.
BidAccountDAO/JPABidAccountDAO
encapsulates the use of JPA when mapping BidAccount and closely associated business objects to/from the database.
Add tuning to your database schema by augmenting the DDL files with indexes for foreign keys, joins, and where clauses. This need not be extensive or exhaustive. Just do enough to show that proper data model and database tuning is part of the overall enterprise development process.
Design an initial business interface and business logic for the applications. The core O/R mapping work will be done by the DAOs. However, it is the ultimate responsibility of these business logic implementations that either it or the business objects enforce the business rules of the application. The DAOs only perform O/R mapping and do not enforce such things as a minimum bid. The business logic is assumed to work within the context of a single, externally controlled transaction. Do not attempt to control the transaction of the EntityManager within these objects or you will NOT be portable to the EJB tier. You need only implement the behavior required to implement the end-to-end use case listed in the testing section. Some of the anticipated methods are listed below.
AccountMgmt/AccountMgmtImpl
encapsulates the actions required to create, get, update and close an account.
createaccount - you will need to at least create a seller and two or more buyer accounts.
SellerMgmt/SellerMgmtImpl
encapsulates the actions required to create and get auctions for a seller.
createAuction - creates a new auction for a seller. All dates and properties of the auction need to be of consistent and legal values.
getUserAuctions - returns a collection of auctions associated with a seller. This can be used to determine if the auction has been added.
getAuction - returns a specific auction by ID. This can be used to track the state of a specific auction for a seller.
BuyerMgmt/BuyerMgmtImpl
encapsulates the actions required to get and bid on auctions.
getOpenAuctions - returns a collection of auctions that have not ended. This can be used to pick a specific auction to bid on.
placeBid - creates a bid for a specific user and auction. The auction must be open, the user must exist, and the bid amount must be greater than any pre-existing bid.
getAuction - returns a specific auction by ID. This can be used to track the state of a specific auction for a buyer.
TestSupport/TestSupport
a useful tool during testing that encapsulates how to get the application back into a known state prior to running a test or to inspect values not normally exposed through the normal business interfaces.
getAccounts - get all accounts in the system.
removeAccount - sanely remove an account from the system. This may require removing the account from any current bids, etc. in order to satisfy referential integrity.
getAuctions - get all auctions in the system.
removeAuction - sanely remove an auction from the system. This may require removing bids, images, and other objects that may have references to this object.
clearAll - sanely take the state of the system down to a coldstart.
Ingestor
the Ingestor written as a part of a separate requirement is also logically considered part of this tier.
ingest - point an externally provided parser at a set of test data and use the DAOs to populate the system to a known state.
OrderMgmt/OrderMgmtImpl
createOrder - create a record within bidbot that indicates the sale and maximum bid. This may require some stubbing in project 1.
placeBid - place a bid that is higher than the current bid for an open auction but less than the order maximum. This will require some stubbing for project1.
endOrder - complete order processing once auction has closed and note if won. This will require some stubbing in project1.
getOrderStatus - did user win or not.
BidbotTestUtil/BidbotTestUtilImpl
reset - reset the bidbot database to an initial starting state.
The following sketch of two directory structures can be used as a starting point for your overall application. The first is a simplified project layout that collapses the number of sub-projects into a single "Impl" project. It is suggested that you use this layout if you are new to maven and want the simplest configuration possible. The second is a more robust layout and is closer to a multi-developer environment. Use the later structure if you want to better simulate a work environment where the work of multiple developers needs clearer separation.
All "e"Market, "e"Sales, and "e"Bidbot names should be changed to your specific name mangler. The same type of modification needs to occur for the java package names.
Other than a README, it is not anticipated that you will have any artifacts at the root layer. It should be used only as a convenience wrapper to perform goals across both projects. The root project can also be used for common property, dependencyManagement, and pluginManagement defintions. All concrete dependency and plugins should be defined in the leaf-level poms to avoid unwanted dependencies from the root or mid-level parents.
Figure 18.1. Single Module Approach Candidate Module Structure
eMarket |-- eSales | +--eSalesImpl | | |-- pom.xml | | `-- src | | |-- main | | | |-- java | | | | `-- esales | | | | |-- bo | | | | |-- dao | | | | |-- jpa | | | | |-- bl | | | | |-- blimpl | | | `-- resources | | | `-- ddl | | | |-- eSales-create.ddl | | | `-- eSales-drop.ddl | | `-- test | | |-- java | | | `-- esales | | | |-- bo | | | |-- dao | | | `-- bl | | `-- resources | | |-- log4j.xml | | `-- META-INF | | `-- persistence.xml | `-- pom.xml |-- eBidbot | +--eBidbotImpl | | |-- pom.xml | | `-- src | | |-- main | | | |-- java | | | | `-- ebidbot | | | | |-- bo | | | | |-- dao | | | | |-- bl | | | `-- resources | | | `-- ddl | | | |-- eBidbot-create.ddl | | | `-- eBidbot-drop.ddl | | `-- test | | |-- java | | | `-- ebidbot | | | |-- bo | | | |-- dao | | | |-- bl | | `-- resources | | |-- log4j.xml | | `-- META-INF | | `-- persistence.xml | `-- pom.xml `-- pom.xml
Figure 18.2. Multi-Module Approach Candidate Module Structure
eSales |-- eSales | +--eSalesBO | | |-- pom.xml | | `-- src | | |-- main | | | `-- java | | | `-- esales | | | |-- bo | | | `-- bl | | `-- test | | |-- java | | | `-- esales | | | `-- bo | | `-- resources | | `log4j.xml | +--eSalesDAO | | |-- pom.xml | | `-- src | | |-- main | | | |-- java | | | | `-- esales | | | | |-- dao | | | | `-- jpa | | | `-- resources | | | `-- ddl | | | |-- eSales-create.ddl | | | `-- eSales-drop.ddl | | `-- test | | |-- java | | | `-- esales | | | `-- dao | | `-- resources | | |-- log4j.xml | | `-- META-INF | | `-- persistence.xml | +--eSalesBLImpl | | |-- pom.xml | | `-- src | | |-- main | | | `-- java | | | `-- esales | | | `-- blimpl | | `-- test | | |-- java | | | `-- esales | | | `-- bl | | `-- resources | | `-- log4j.xml | `-- pom.xml |-- eBidbot | ("it is simple" keep to a single module as shown above) `-- pom.xml
The end-to-end scenario will test your business logic, DAO, and BOs in a "happy path" scenario. Any white-box or black-box testing of alternate and error paths would be appropriate to put in the separate unit tests.
Provide a JUnit test for your business objects (BOs) that test the manipulation of the data. An example test might be to try adding a bid to a closed auction. These tests should be packaged with the BOs. There should be a separate project and test for both eSales and eBidbot. It is anticipated that these tests will be a minimal demonstration of understanding.
Provide a JUnit test for your eSales and eBidbot JPA DAOs. This should test the implementation for the required CRUD operations for each type of object. It is understood that some of the operations will be handled by cascades, so you might not have a set of methods handling each type of business object. This test should be packaged with the DAOs.
Provide a JUnit test for your business logic to test the basic functionality of your business logic design, including ingest. The ingestor test should be able to reference a known data file and ingest records into the database using the DAOs. These tests should be packaged with the business logic implementation.
Provide a set of JUnit test programs to verify the following end-to-end scenario in eSales. This test should be implemented as a JUnit test and packaged with the business logic implementation.
reset (using SalesTestUtilImpl) -- reset the esales database to an initial starting state.
ingest data (using Ingestor) -- ingest the full XML test data file.
createAccount for seller, buyer1, and buyer2 (using AccountMgmtImpl)
createAuction for seller (using SellerMgmtImpl)
getUserAuctions for seller (using SellerMgmtImpl)
getAuction using SellerMgmtImpl) -- i.e., for a specific auction.
getOpenAuctions (using BuyerMgmtImpl) -- i.e., page through open auctions.
placeBid for buyer1 (using BuyerMgmtImpl)
placeBid for buyer2 (using BuyerMgmtImpl)
getAuction (using BuyerMgmtImpl) -- i.e., showing auction has bids associated.
closeAuction (using AuctionMgmtImpl) -- i.e., winner declared and no more bids should be accepted.
Provide a set of JUnit test programs to verify the following end-to-end scenario in eBidbot. This test should be implemented as a JUnit test and packaged with the business logic implementation.
reset (using BidbotTestUtilImpl) -- reset the bidbot database to an initial starting state.
place order (using OrderMgmtImpl) -- i.e., create a record within bidbot that indicates the sale and maximum bid. This may require some stubbing in project 1.
place bid (using OrderMgmtImpl) -- i.e., place a bid that is higher than the current bid for an open auction but less than the order maximum. This will require some stubbing for project1.
end order (using OrderMgmtImpl) -- i.e., complete order processing once auction has closed and note if won. This will require some stubbing in project1.
get order status (using OrderMgmtImpl) -- i.e., did user win or not.
Your project will be graded on completeness and quality of product. In order for you to receive full credit in each area, it must be a) complete, b) done well, and c) tested. The breakdown of grading will be as follows:
README provided that describes where each requirement satisfied: 10pts
Projects cleanly builds with Maven: 15pts
Managed schema to include declaration of key indexes (i.e., defined in a set of files and explicitly used to coldstart and initialize the database at defined times): 5pts
Business Objects: 10pts
Use of Validation API: 5pts
JPA DAO and JPA O/R Mapping: 25pts
Ingest: 10pts
Business Logic: 10pts
End-to-end Integration Test: 10pts
The following table contains examples of where projects have lost points in the past. Of course, each project submitted can introduce new issues or different severity levels of the same issues. Do not treat this as a complete list.
Table 20.1. Sample Lost Points
README | |||
---|---|---|---|
Not provided | 10 |
Projects cleanly builds with Maven | |||
---|---|---|---|
groupIds, schema, java packaging, etc do not have a project-specific name mangler | 2 | ||
Avoidable build errors | 2 | ||
Key areas not building | 10 | ||
Poluting project with do-nothing tests or tests that are not tests | 2 | ||
Managing multiple copies of the same source file. | 1 | ||
Large commented out blocks of code. | 2-5 | ||
Non-portable references to external resources (e.g., ingested file) | 2 |
Managed Schema | |||
---|---|---|---|
Database columns not well defined and constrained (e.g., FKs, non-null, max size) | 1 | ||
Improper use of DATE, TIME, and/or TIMESTAMP | 1 | ||
No definition of any indexes | 2 |
Business Objects | |||
---|---|---|---|
Improper/no TemporalType declaration for Dates | 1 | ||
PK classes (if exist) did not implement required constructs | 1 |
Use of Validation API | |||
---|---|---|---|
Missing any declaration of validation criteria in BOs | 3 | ||
Not explicitly performing validation | 2 |
JPA DAO and JPA O/R Mapping | |||
---|---|---|---|
Missing key relationships | 5 | ||
Managing transactions in DAO | 1 | ||
Relationships should not be modeled as entities | 2 | ||
Using provider-specific mechanisms over JPA-provided technique | 1 | ||
Not honoring dependencies. Attempt to delete entities with incoming relationships/FKs. | 1 | ||
Walking the object tree functionally works but using a JPA-QL query would be much cleaner and more efficient. | 2 |
Ingest | |||
---|---|---|---|
Re-used IDs from ingested XML file and did not generate IDs local to project | 2 | ||
Did not include a unit test that *verified* injest worked | 1 |
Business Logic | |||
---|---|---|---|
Implemented stateful logic (Wrong!) | 5 | ||
No separate testing of business logic. Relied too much on end-to-end as unit test. | 1 | ||
Managing transactions in business logic | 1 | ||
Did not implement all methods required for end-to-end | 1 | ||
Not exhibiting good command of what it means for an entity to be managed and what you do and don't need to do in that state | 1 | ||
Persisting BOs from other application. Not preserving separation between applications. | 1 |
End-to-end Integration Test | |||
---|---|---|---|
Poluted, hard to follow, too much extra stuff | 2 | ||
Missing resetAll and populate at start of scenarios | 5 | ||
Missing step X | 1 |
Table of Contents
Continue to re-use and update the implementations from the previously implemented data access tier and initial business logic
Deploy data access tier and business logic as an EJB component to server
Deploy EJBs using different deployment approaches (EAR and WAR)
Provide a remote interface to business logic
Provide integration prototype for Web Tier
Expand the current project development architecture to include EJB, WAR, EAR, and remote Test sub-projects for eSales. For eSales you will have separate EJB, WAR, EAR, and RMI Test modules. This is being done to simulate a complex project option.
Expand the current project development architecture to include EJB, WAR, and remote Tests for eBidbot. For eBidbot you will have a single WAR for deployment. You may merge all architectural levels into a single WAR module (or a few other options). This is being done to simulate a simple project option.
Re-host the data access tiers and initial business logic from Project 1 within the new EJB and WEB tiers at component/runtime.
Design the data transfer objects (DTOs) to be used to communicate with clients using the remote interface. For eSales you will design a set of DTO classes separate from your BO classes. For eBidbot you will reuse your BO classes as DTOs.
Create the EAR and WAR sub-projects to host/deploy the Java EE components.
Create the remote Test sub-project(s) to deploy and run tests using the remote EJB interface.
Create and test the WAR sub-project to host the user interface. This component will ultimately be deployed the application server and may use local interfaces of the EJB to access business logic.
This project and the final project rely heavily on the components from Project 1. You are encouraged to make sure your implementation of Project 1 is sound and well tested prior to doing too much work on Project 2. You should continue to maintain your JUnit tests for the earlier components while working on these higher level tiers.
We are continuing a theme that eSales is complex and requires all levels of architecture and that eBidbot is simple and can leverage various shortcuts. The intent it to show how JavaEE can be used for projects of different sizes and demands. To inially meet satisfy this requirement, you must ...
Deploy eSales as an EAR and eBidbot as a WAR
Implement separate DTOs for eSales and re-use BOs as DTOs for eBidbot
Implement separate Impl and EJB modules for eSales and combine Impl and EJB into a single module for eBidbot. The combined module may be also combined with the eBidbot WAR.
The project will continue along two parallel paths; eSales and eBidbot. However, this time we will add several new project types; EJB, EAR, WAR, and Test. We will add all new project types to eSales. We will add (or migrate to) the WAR type to eBidbot. These new sub-projects will become siblings to your existing eSales Impl -or- BO, DAO, and BLImpl sub-modules. For eBidbot, you have the option of making the WAR a sibling module, the only module, or migrating the existing Impl project to be a WAR project ( and remain with a single module for eBidbot). The new projects will depend on your legacy work. The remote interface of the EJB will also require specific design of what gets externalized to the client. The remote clients do not share the same address space we had in Project 1 and we cannot afford to serialize the entire contents of a database full of related information. Data Transfer Objects (DTOs) will be part of the EJB remote interface design.
You have finished a significant amount of eSales during Project 1; the O/R mapping and core business logic of a non-trivial business model. You will now host the data access tier and business logic within an EJB component. This EJB component will supply the EntityManager, control transaction boundaries, provide local interaction for the Web Tier, and a RMI interface for remote clients. Security will be addressed in the next project. To limit the scope of the project, the Web UI requirements will be constrained to a limited number of use cases. You are to deploy eSales using an EAR and eBidbot using only a WAR.
eBidbot will require additional work as well. With a remote interface for eSales in hand and the ability to either simulate or operate with a live instance, we can now complete the rest of the the business logic that will also be hosted within an EJB tier. We'll try to keep the Web-UI minimal.
You may develop your Web tier in an alternate environment, such as Jetty. However, it must be submitted as part of the application that runs within JBoss/Wildfly.
As with the previous project, the use of the name eMarket, eSales, and eBidbot within the project specification are to be taken as placeholders. Please make an effort to uniquely name your directories, components, Java packages, JNDI names, etc. to help logically separate the parallel project implementations.
There should be no use of System.out.println() in the code and all implementations must use a logging API with the log4j logging provider. You may leave debug in your code, but this should be able to be turned on/off with the proper logging priority changes in the log4j.xml configuration.
Continue to provide all functionality from Project 1; especially the unit tests as you revise your earlier implementations For example, you should continue to have the ability to run the unit and end-to-end tests implemented in project 1. Make sure you design your project 2 testing such that it does not depend on residue from project 1 tests.
Create an EJB tier to host your eSales business logic and data access tiers. Each of the EJBs provided will supply the necessary dependency injection and initialization for the business objects. Transaction scope will be added as a part of a separate requirement. Security will be added in the next project. The main construction, deployment, and remote interfaces of the EJB will be the focus of this requirement. You will have to address the data that gets serialized to the client in a set of Data Transfer Objects (DTOs).
The EJBs will be used to inject and initialize implementation objects and provide a remote interface.
AccountMgmtEJB
This EJB will primarily host the AccountMgmtImpl business logic. A remote interface and bean implementation will need to be designed for this EJB.
SellerMgmtEJB
This EJB will primarily host the SellerMgmtImpl business logic. A remote interface and bean implementation will need to be designed for this EJB.
BuyerMgmtEJB
This EJB will primarily host the BuyerMgmtImpl business logic. A remote interface and bean implementation will need to be designed for this EJB.
SalesTestUtilEJB
This support EJB will primarily host the TestUtilImpl business logic to support remote testing requirements. A remote interface and bean implementation will need to be designed for this EJB.
Create a set of Data Transfer Objects (DTOs) to serialize the state of the business objects between the EJB and client when using the remote interface.
Remember that there can be more than 1 DTO to represent the same information related to a business object. For example, collections might contain summary information only and a single result might contain full details. The following is only provided as a reasonable starting point.
AccountDTO
This class is meant to primarily be an aggregation of the state found in the Account and related POC business objects.
AddressDTO
This class is meant to be a representation of the state found in the Address business object. Depending on how the Address business object is designed, there may not be a need for this stand-in.
AuctionDTO
This class is meant to be a representation of the state found in the AuctionItem and POC business objects.
BidDTO
This class is meant to be a representation of the state found in the Bid and related POC business objects.
Create an EAR to host the eSales server-side components; including the EJB component just developed.
Create a Test project that will deploy the EAR to the application server and run through a set of JUnit test using the remote interface of the EJBs.
Complete and test the eBidbot business logic using the remote interface of eSales. The diagram below shows the business logic within the context of the EJB Tier implemented as a part of a separate requirement.
OrderMgmt/OrderMgmtImpl
Encapsulates the functionality required to create an automated bidding account, place an order, and check the status of that order. You should be able to test this component using the running eSales application or implement a stub that implements the BuyerMgmtRemote interface. This implementation may return the eSales DTO classes if desired.
Create a WAR module to host the eBidbot server-side components and the remote tests for the WAR/EJB components.
Create an EJB tier to host your eBidbot business logic and data access tiers. To provide practice in alternative deployments, it is a requirement that this EJB either be implemented as part of the implementation module, as part of the WAR module, or part of a combined implementation/EJB/WAR module. Either way, the eBidbot EJBs must ultimately be deployed using a WAR. The EJB(s) provided will have the necessary dependency injection and initialization for the business objects. Transaction scope will be added as a part of a separate requirement. Security will be added in the next project. The main construction, deployment, and remote interfaces of the EJB will be the focus of this requirement.
OrderMgmtEJB
This EJB will primarily host the OrderMgmtImpl business logic. A remote interface, local interface and bean implementation will need to be designed for this EJB.
BidbotTestUtilEJB
This support EJB will primarily host the TestUtilImpl business logic to support remote testing requirements. A remote interface, local interface and bean implementation will need to be designed for this EJB.
The DTOs are be used to serialize the state of the business objects between the EJB and client when using the remote interface. However, with care, business objects can be directly used as DTOs. You are required to re-use the entity classes as DTOs in this application. That means you must account for serialization of the entity class and Lazy-Load issues that can occur during post-transaction marshalling of the entity class.
Add separate Web UIs (WARs) to both applications to demonstrate integration between the Web UI and EJB Tiers.
In order to save development time and better leverage the work you have already performed in the remote interface testing, you may use the remote interfaces between your WAR and EJB components even though local interfaces would be more efficient when deployed within the same EAR or WAR. This is being done to cut down on the amount of project work for class. You would ideally use the local interfaces when possible. You should already be using the local interfaces between EJBs.
Add a Web UI to the eSales application. This may use EJB local or remote interfaces. The WAR for the Web UI must be deployed as part of the eSales EAR. The Web UI must have the following functionality.
Anonymous User
List open auctions
Get details for specific auction
createAccount (using AccountMgmtEJB)
Test Admin
reset All tables
populate tables (using Ingestor)
Seller
createAction (using SellerMgmtEJB)
getUserActions (using SellerMgmtEJB)
getAuction (using SellerMgmtEJB)
Buyer
Place bid (using BuyerMgmtEJB)
Add a Web UI to the eBidbot application (i.e., to the WAR you have already created for the EJBs). This may use the EJB local or remote interfaces. The WAR for the Web UI must be deployed solo -- containing the EJBs and Impls. The Web UI must have the following functionality.
Test Admin
reset All tables
Admin
createAccount (using OrderMgmtEJB)
Bidder
placeOrder (using OrderMgmtEJB)
Add transaction properties to the EJBs.
Transaction Scope - all session bean methods in these two applications should require a transaction.
Transaction Integrity - add transaction rollback logic to EJBs that detect a move date for a residence is prior to the move-in date.
Create a demonstration of transactions and the capability of rollback by implementing a scenario that adds something to the database and then a *follow-on* rollback causes the changes to be undone.
To satisfy the transaction demonstration requirement, you must actually store something in the database and then later in the transaction roll it back based on some decision. If you make the decision before you store the information -- you are not satisfying the requirement. It is common for students to implement a wrapper around an existing method that takes a collection and populate the collection with good data up front and bad data at the end. The good data that got successfully processed should get rolled back once the bad data is processed. Create remote methods that can tell your RMI client that this actually occured.
The following sketch of directory structure can be used as a starting point for your overall application. It assumes you already have either a consolidated Impl -or- a BO, DAO, and BLImpl set of projects in place from Project 1. You will not have the option of consolidating the EJB, WAR, EAR, and RMI Test into a single project for eSales. They must be implemented as separate projects with proper dependencies between them declared. You *do* have the option of merging all modules into a single WAR module for eBidbit or many of the alternatives as long as eBidbit is deployed as a WAR.
Figure 23.1. Candidate Source Module Structure
`-- eMarket |-- eSales | |-- eSales (module(s) from project1) | |-- eSalesEJB | | |-- pom.xml | | `-- src | | `-- main | | |-- java | | | `-- esales | | | `-- ejb | | `-- resources | | `-- META-INF | | |-- beans.xml | | |-- persistence.xml | | |-- (ejb-jar.xml) | | `-- (jboss-ejb3.xml) | |-- eSalesWAR | | |-- pom.xml | | `-- src | | `-- main | | |-- java | | | `-- esales | | | `-- web | | |-- resources | | `-- webapp | | |-- WEB-INF | | | |-- beans.xml | | | |-- web.xml | | | |-- (jboss-web.xml) | | | `-- content | | `-- index.jsp | |-- eSalesEAR | | `-- pom.xml | |-- eSalesTest | | |-- pom.xml | | `-- src | | `-- test | | |-- java | | | `-- esales | | | `-- ejbclient | | `-- resources | | `-- jndi.properties | `-- pom.xml |-- eBidbot (many options) | |-- (eBidbotImpl -- should be merged with EJB or WAR) | |-- (eBidbotEJB -- could be separate from WAR) | |-- (eBidbotTest -- could be separate from WAR) | |-- eBidbotWAR | | |-- pom.xml | | `-- src | | +-- main | | | |-- java | | | | `-- ebidbot | | | | +-- ejb | | | | `-- web | | | |-- resources | | | | `-- META-INF | | | | |-- persistence.xml | | | | `-- (ejb-jar.xml) | | | `-- webapp | | | |-- WEB-INF | | | | |-- beans.xml | | | | |-- web.xml | | | | |-- (jboss-web.xml) | | | | |-- (jboss-ejb3.xml) | | | | `-- content | | | `-- index.jsp | | `-- test | | |-- java | | | `-- ebidbot | | | `-- ejbclient (*IT.java) | | `-- resources | | `-- jndi.properties | `-- pom.xml `-- pom.xml
The end-to-end scenario will test your end-to-end application in a "happy path" scenario. Any white-box or black-box testing of alternate and error paths would be appropriate to put in the separate unit and IT tests.
Provide JUnit tests that verify the EJB functionality of eSales accessed through its remote interface. This will also test the DTOs. These tests should be packaged in the eSales RMI Test project.
Provide JUnit tests that verify the extra business logic functionality of eBidbot interfacing with eSales. These tests may be placed either with the business logic project or where you test the remote interfaces.
Provide JUnit tests that verify the EJB functionality of eBidbot accessed through its remote interface. This will also test its use of DTOs. Theses tests should be packaged in the eBidbot WAR (or optional RMI Test) project.
Perform an end-to-end use case to do the following. This must be demonstrated in an automated JUnit test and then be manually implementable using the Web UI.
Reset all eSales and eBidbot tables (using the SalesTestUtilEJB and BidbotTestUtilEJB)
Populate the eSales tables (using the Ingestor)
Create account for seller, buyer1, and buyer2 in eSales
Create account for buyer2 in eBidbot
Create auction for seller
Get auctions for seller
Get auction for the one created in earlier step
Get open auctions
Place bid for buyer1
Get auctions for buyer1
Place order for buyer2 in eBidbot (stimulate a bid)
Get auction to verify bids were placed for buyer1 and buyer2
Your project will be graded on completeness and quality of product. In order for you to receive full credit in each area, it must be a) complete, b) done well, and c) tested. The breakdown of grading will be as follows:
README provided that describes where each requirement satisfied: 10pts
Projects cleanly builds with Maven: 15pts
Project 1 functionality: 10pts
EJB Tier, remote interface, and EAR deployment: 25pts
WAR/EJB deployment: 10pts
Web UI integration: 10pts
Transactions: 10pts
End-to-end Integration Test: 10pts
The following table contains examples of where projects have lost points in the past. Of course, each project submitted can introduce new issues or different severity levels of the same issues. Do not treat this as a complete list.
Table 25.1. Sample Lost Points
README | |||
---|---|---|---|
Not provided | 10 | ||
README did not indicate where X was located and it was not obvious even after ... | 2 | ||
The WebUI is hard to navigate (fine) but README offered no assistance | 5 |
Projects cleanly builds with Maven | |||
---|---|---|---|
One of your IT tests assume the DB is setup correctly prior to running and that would only be true of we were running a common server database instance across our unit and IT tests. | 2 | ||
Testing does not produce consistent results - out of four runs of mvn install the process failed twice and succeeded twice. | 2 | ||
Build was not portable. I had to make changes. Sending me a copy of this beforehand would have caught this. | 2 |
Project 1 functionality | |||
---|---|---|---|
DB schema is under-constrained | 1 | ||
Project 1 end-to-end scenarios no longer exist/run | 2 |
EJB Tier, remote interface, and EAR deployment | |||
---|---|---|---|
Attempting to set the state of a @Stateless EJB. What do you think will happen to that state when you get a different bean instance the next time you call? | 5 | ||
With auto-create DDL turned on (hibernate.hbm2ddl.auto" value="create") your application deletes all data when redeployed. Deleting all data should be restricted to only an explicit call to resetAll(). | 5 | ||
Not separating a single call into separate transactions to drop and then create schema. First action may fail when not exist. | 2 | ||
You have methods that will only work in a @Local interface a part of your @Remote interface | 2 | ||
Reusing business logic classes instead if EJB components - repeating the same work of the reusable EJB component in each EJB that needs to reuse the functionality. Inject @Local interfaces. Do not repeat instantiation and setup of identical business logic/DAO classes. | 2 | ||
Injest was not implemented within a deployed EJB. It was mistakely implemented the same as project 1 in a JUnit client. | 5 | ||
You have not integrated the two applications at the EJB remote interface level. The second application does not make a single remote call to the first application. All interaction is occcuring outside of the server-side and results passed in. | 5 |
WAR/EJB deployment | |||
---|---|---|---|
You are deploying EJBs from the first application within your second application's WAR. Look at your built/deployed artifact and correct dependencies. | 5 |
Web UI integration | |||
---|---|---|---|
UI does not provide a path to satisfy a step in the end-to-end scenario (that the IT test shows works). | 2 | ||
EJB/Impl functionality called by UI fails and is not exercised by end-to-end IT test | 2 | ||
This is a real bust when tested on the deployment platform. This should have been easily noticed. Pressing resetAll() results in the following error displayed. | 5 |
Transactions | |||
---|---|---|---|
Transaction scope not explicitly defined for EJB. You are accepting container defaults. | 1 | ||
No attempt to demonstrate transaction rollback | 10 | ||
Scenario shows business logic check but not a rollback of actions (store) to a transactional resource (DB). The requirement called for you to persist something all the way to the database - such that if you stopped in a breakpoint you would see the data - and then have the data thrown away due to a rollback. | 7 |
End-to-end Integration Test | |||
---|---|---|---|
Poluted, hard to follow, too much extra stuff | 2 | ||
Missing resetAll and populate at start of scenarios | 5 | ||
Missing step X | 1 | ||
No. I want different functionality in this step | 1 |
Table of Contents
Secure access to EJB and Web applications
Decouple business logic from common tasks
Provide for asynchronous processing.
Re-use the eSales and eBidbot implementations from projects 1 and 2.
Define access controls and implement authentication mechanisms for applications.
Implement DTO validation using interceptors
Implement a publish/subscribe capability between the eBidbot and stand-alone client applications.
Trigger certain behaviors based on timers.
The project will build on the core implementation from Projects 1 and 2. We will mostly extend existing projects with security and asynchronous logic.
Java EE defines authentication and authorization to be independent of the overall API and capability. JBoss and other application servers provide simple, default mechanisms that are easy to demonstrate and more sophisticated mechanisms that are realistic for deployments that require no change to the JavaEE-compliant application code. We will use the simple, default/"other" security-domain defined within the standard JBoss installation. This uses the RealmUsersRoles login-module -- which is powered by two property files supplied and pre-populated by the class server files from ejava-wildfly901.
Table 26.1. User Credential Files
File | Description |
---|---|
${jboss.server.config.dir}/application-users.properties | defines username=password lines that list the login username and hashed password |
${jboss.server.config.dir}/application-roles.properties | defines username=role1,role2 lines that list the login username and assigned roles |
We are going to have a couple types of users. Some of the users will have zero, one, or more of these roles. Because of the static nature of our authentication, all users will have a login configured before the application is even deployed to the server.
Table 26.2. Application Roles
Role | Description |
---|---|
anonymous | these users can view auctions and create an account |
esales-admin | these users can reset and populate the eSales database |
esales-user | these users can create and auction, and bid on auctions. This role is also required to subscribe to JMS auction events. |
esales-sys | role required to perform internal auction actions like JMS publishing |
esales-trusted | these users can bid on auctions on behalf of a specified user |
ebidbot-admin | these users will be able to perform management and test functions on eBidbot |
ebidbot-user | these users can and manage their orders |
If a user has a login for one application, they will use the same account to access the other application (e.g., user3 might have both esales-user and ebidbot-user roles).
Table 26.3. User Roles
User | Roles |
---|---|
known | (no roles) |
admin1 | esales-admin |
admin2 | ebidbot-admin |
syssales1 | esales-sys |
sysbidbot1 | esales-trusted |
user1 | esales-user |
user2 | esales-user |
user3 | esales-user,bidbot-user |
To clarify, your application will have a static set of logins and will ingest a set of accounts. A user with a login and no account can login, but won't be able to do anything meaningful. A user with an account and no login won't be able to access the system. Normally the login would be created at the same time as the account. Except for your JBoss configuration and your "Create Account" logic, no other part of your project should be aware of this tradeoff made for class project simplicity.
Some actions are open to any users; authenticated or not. Authentication will be performed using a JNDI login. All users will have a password of "password1!".
eBidbot will run-as an esales-trusted user and pass the userId for the eSales bidder with the placed bid.
For some asynchronous activity, we will implement an Auction Topic with eSales that will be used to provide updates to auction information. eBidbot will listen to this topic using a Message Driven Bean to keep orders up to date and to specifically know when they are closed. A stand-alone client will also be used to subscribe to auction events. The topic(s) will be pre-defined in your application server along with users and roles. However, you will have to design the type, structure, and payload of the messages on the topic(s).
eSales and eBidbot will use EJB Timers to help perform periodic business logic, like checking for completed auctions or making bids.
Provide all functionality from Projects 1 and 2.
Enhance eSales with access restrictions.
Assign the EJB to the "other" security domain
Restrict access to the EJB methods to appropriate read/write roles using declarative security.
Only users with the esales-user role can access account information but only for their authenticated identity. Derive this information from their login. (i.e., user1 logs in and may only access user1's account as "my account". They do not specify which account they access.
Only users with the esales-admin role may run the test utility functions.
Anonymous users may access Auction information.
Extend your existing eSales RMI Test project and any RMI client(s) to address new authentication requirements.
Add a valid login to your existing tests to re-enable them under the newly secure environment.
Add a new unit test that verifies the access controls of the protected and open methods. This means that -- after fixing access issues in the previous bullet -- you purposely attempt to violate access restrictions from a new JUnit test to show a test of access control.
Enhance eBidbot with access restrictions.
Assign the EJB to the "other" security domain.
Restrict access to the EJB read/write methods to appropriate roles using declarative security.
Only users with the ebidbot-user role may access OrderMgmt (once their account is created).
Only users with the bidbot-admin role may run the test utility functions.
run-as an esales-trusted user when making bids on behalf of a user. This may require the use of a "helper" EJB to encapsulate the scope of the run-as role/identity
Extend your existing eBidbot RMI Test project and any RMI client(s) to address new authentication requirements.
Add a valid login to your existing tests to re-enable them under the new secure environment.
Add a new unit test that verifies the access controls of the protected and open methods. Same comment as above -- after fixing proper access to eBidbot you should have at least one test that demonstrates how the application is preventing unauthorized access.
Enhance eSalesWAR with access restrictions.
Assign the WAR to the "other" security-domain.
Restrict access beyond the main page to users with the appropriate roles. You may use FORM or BASIC authentication. (I suggest FORM for easy logout/login as new user).
Permit only users to only ask the EJB tier for information that is associated with their login. For example, an esales-user/non esales-trusted user should only be allowed to enter a bid for the account they authenticated against.
Enhance eBidbotWAR with access restrictions.
Assign the WAR to the "other" security domain.
Restrict access beyond the main page to users with the appropriate roles. You may use FORM or BASIC authentication. (I suggest FORM for easy logout).
Permit only users to only ask the EJB tier for information that is associated with their login. For example, a eBidbot user should not see an option to reset the database or see another users' account.
Implement a "thin slice" of capability to demonstrate JavaEE interceptors using validation.
Define javax.validation constraints on at least one of your DTOs.
Implement a JavaEE interceptor that gets a Validator injected, intercepts all calls of your EJBs, and runs the validator against each parameter input to the EJB method.
The interceptor can have no knowledge of the specific EJB, method, or parameters it is intercepting and validating.
The EJB can have no knowledge of the interceptor and should do no work to validate the DTO itself.
Extend your eSales implementations to publish a JMS message to a topic whenever information for an auction changes.
Use the emarket-esales-auction topic (JNDI name: topic/ejava/projects/emarket/esales-auction)
Design your JMS Message. You can use any JMS Type and JMS/custom properties you wish. However, know that some subscribers will be filtering on such things as the category or state of an auction.
Have your eSales EJBs publish JMS Messages to the topic when the Auction changes state (created, bid, close).
Add a Java SE JMS subscriber to consume events about Auctions pertaining to a specific category.
Implement a Java SE JMS subscriber that listens for Auctions with a category matching a JMS Selector. This must be a JMS selector, not client-side Java logic.
Simply print the fact that the process was called and the contents reported in the message.
You must launch this subscriber using an Ant wrapper script.
Implement a Message Driven Bean within eBidbot to subscribe to auction closed events.
Use a JMS Selector for the MDB to limit the types of messages consumed.
Update any orders as being closed and with results based on the contents of the JMS message.
Implement an EJB Timer that will allow eSales to automatically wake-up and expire auctions.
The end-to-end scenario will test your end-to-end application in a "happy path" scenario. Any white-box or black-box testing of alternate and error paths would be appropriate to put in the separate unit and IT tests.
Provide JUnit IT tests that verify the EJB functionality of eSales accessed through its remote interface using new access control restrictions.
Provide JUnit IT tests that verify the EJB functionality of eBidbot using its new access control restrictions and ability to authenticate with eSales.
Provide a JUnit IT tests that demonstrates the functionality of the JavaEE interceptor/validator.
Implement the scripted use case below as an automated JUnit test and manually accessed script through the Web UI. All command line implementations must be wrapped in an Ant runtime script to encapsulate the classpath details in a portable manner (see the class jmsNotifier project for an example of using an Ant runtime script). The full JSE subscriber need not be part of the automated end-to-end JUnit test. The JUnit test/module must be delivered in a state that can be executed in a debugger -- whether directly within Eclipse or using a remote debugging session.
eSales initializes the EJB Timer to check for expired auctions
eBidbot initializes the EJB Timer to check auctions associated with its bids.
admin1 resets all eSales tables (using the eSalesTestUtilEJB)
admin1 populates the eSales tables (using the ESalesIngestor)
admin2 resets the eBidbot tables (using the EBidbotTestUtilEJB)
admin2 populates the eBidbot tables (using the EBidTestUtilEJB) if necessary. Suggest adding account for user3 at this point.
user1 creates auction
user2 gets a list of open auctions
user2 places bid on an auction
user2 views the current status of the auction they are bidding on
user3 gets a list of open auctions
user3 views the current status of the auction that was bid by user2
user3 places order with eBidbot
eBidbot EJB wakes up from an EJB Timer
eBidbot EJB places bid for user3
user2 places another bid on auction
eBidbot EJB wakes up again from an EJBTimer
eBidbot EJB sees they have been raised and places another bid for user3
eSales EJB wakes up from an EJBTimer and closes the auction
eSales EJB publishes a message to the topic that informs everyone of the closing and that user3 has win.
The eBidbot MDB receives the message and updates the order.
The stand-alone client recieves the message if it matches their JMS selector for a specific category.
user3 checks their order with eBidbot and finds out they won
Your project will be graded on completeness and quality of product. In order for you to receive full credit in each area, it must be a) complete, b) done well, and c) tested. The breakdown of grading will be as follows:
README provided that describes where each requirement satisfied: 10pts
Projects cleanly builds with Maven: 15pts
Project 1 and 2 functionality: 5pts
Client Security Login: 10pts
EJB Security: 10pts
WAR Security: 10pts
EJB JMS Publisher: 5pts
EJB MDB Subscriber: 5pts
Java SE JMS Listener: 5pts
JavaEE Interceptors/Validator: 5pts
EJB Timers: 10pts
End-to-End Integration Test: 10pts
The following table contains examples of where projects have lost points in the past. Of course, each project submitted can introduce new issues or different severity levels of the same issues. Do not treat this as a complete list.
Table 29.1. Sample Lost Points
README | |||
---|---|---|---|
The WebUI is hard to navigate (fine) but README offered no assistance | 5 |
Projects cleanly builds with Maven | |||
---|---|---|---|
Using rogue users that are not part of the standard class setup in your end-to-end. | 3 | ||
Initial build fails. Looks to depend on DB schema bleedover between unit and IT tests. | 2 |
Project 1 and 2 functionality | |||
---|---|---|---|
Second application being deployed as EAR and not WAR | 2 | ||
Relying on persistence unit to create schema -- thus blowing away all DB data on deployment | 2 | ||
Missing scenario feature (e.g., wrong data) from project 2 end-to-end scenario. | 1 | ||
Attempting to set the state of a @Stateless EJB. What do you think will happen to that state when you get a different bean instance the next time you call? | 2 | ||
Not self managing schema. With the end-to-end having resetAll() in place, why did you rely on the JPA provider to initialize your schema? | 1 |
Client Security Login | |||
---|---|---|---|
Fine, but too many fine-grain logins. | 0 |
EJB Security | |||
---|---|---|---|
Using credential logins for the JMS Connection from EJBs -- versus leveraging the @RunAs role | 1 | ||
Not relying on declaritive security to perform the role checks. You are also having the caller authorized for the role supply instance-specific information. For example, any division coordinator is allowed to report the score for any division. | 2 | ||
Not constraining authorized caller to manage only their information. Caller is passing references to information using identifiers that could be associated with any user versus "manage my stuff". By relying on those identifiers you are allowing them to "manage that stuff which may or may not be my stuff". | 3 | ||
EJB module not being associated with a specific security-domain. Relying on defaults. | 2 | ||
@RunAs takes a role -- not a principal | 1 |
WAR Security | |||
---|---|---|---|
Requiring login to pages that should allow anonymous access. | 1 | ||
WAR is not properly locked down. | 2 | ||
Mixed use of BASIC and FORM. When signing in to perform action a Basic authentication popup appears and logout no longer functions, need to close browser to log out. |
EJB JMS Publisher | |||
---|---|---|---|
Copied provided example wholesale and did not adjust to be your solution (e.g., features specific to the example are not required for project, comments specific to example are not appropriate for a project solution). | 2 | ||
Not closing resources (JMS 1.1) This eventually exhausts resources over time. | 2 | ||
Could not find testing of this anywhere to makeup for the fact that the end-to-end was not implemented | 2 |
EJB MDB Subscriber | |||
---|---|---|---|
Not implemented | 10 | ||
Using System.out versus logging framework or better error reporting | 1 |
Java SE JMS Listener | |||
---|---|---|---|
Did not provide your subscriber any credentials to interact with the server. | 2 | ||
Didn't work out of box. JMS topic mis-named | 1 |
EJB Timers | |||
---|---|---|---|
It would be a better design to treat the timer() callback as an interface facade and not the triggering implementation mechanism. You have combined EJB Timer, JMS publishing, and business logic within a single method. | 0 |
End-to-end Integration Test | |||
---|---|---|---|
Your configuration made it hard to run the end-to-end scenario in a debugger. All JNDI names and properties were solely expressed in the pom.xml rather than having suitable defaults in the IT test and overrides from the pom.xml. With that type of setup you did not have your pom.xml and surefire setup to allow remote debugging. | 2 | ||
Poluted, hard to follow, too much extra stuff | 2 | ||
Missing resetAll and populate at start of scenarios | 5 | ||
Missing step X | 1 | ||
No. I want different functionality in this step | 1 |
Copyright © 2015 jim stafford (jim.stafford@jhu.edu)
Built on: 2015-11-18 01:33 EST
Abstract
This presentation introduces the concept of the business logic tier, using it to form a user facade around the capabilities of the service, and to leverage business logic interfaces and tests as a form of executable requirements.
Introduce the architectural layer responsible for external simplicity and internal coordination
At the completion of this topic, the student shall
have more understanding of:
Role of business logic
How business logic can provide top-down requirements
How business logic tests can provide executable requirement verification
be able to:
Define business logic interface
Define business logic class
Distinguish business logic from data access concerns
Write a black-box test to act as an acceptance test
Potential tight coupling between clients and complex business objects
Too many method invocations between clients and business objects
Business methods exposed for misuse by clients
Hide complex interactions behind a simple client interface
Reduce the number of business objects exposed to the client across the network
Hide implementation, interactions, and dependencies of business components
Use a business logic class to encapsulate the interactions required with the business objects
Simplifies complex systems
May appear to be a no value pass-thru in simple systems
Should involve more than one business object per facade
Should have more than one facade per system
Decouples the business objects from being aware of one another
Improves perceived network performance for remote interfaces
Centralizes security and transactions in some cases
/**
* Purchasing handles payment of purchased products.
*/
public interface Purchasing {
/**
* Creates an account for the user to use in purchasing products.
* @param email
* @param firstName
* @param lastName
* @return
*/
Account createAccount(String email, String firstName, String lastName);
/**
* Completes the purchase of the items in the user's shopping cart,
* empties the cart, and returns the total cost paid.<p/>
*
* Note that this capability is not yet fully defined.
* @param email
* @param password
* @return
*/
double checkout(String email, String password);
}
/**
* The catalog maintains a view of the inventory known to our application.
*/
public interface Catalog {
/**
* Returns a list of products in the catalog chunked into page sizes.
* @param offset
* @param limit
* @return
*/
List<Product> getProducts(int offset, int limit);
/**
* Adds the selected product to the users' shopping cart and returns
* the count of items.
* @param id
* @param validEmail
*/
int addToCart(int id, String validEmail);
}
@Entity
@NamedQueries({
@NamedQuery(name="blPurchasing.findAccountByEmail",
query="select a from Account a where a.email=:email")
})
public class Account {
public static final String FIND_BY_EMAIL="blPurchasing.findAccountByEmail";
@Id @GeneratedValue
private int id;
@Column(nullable=false, unique=true)
private String email;
@Column(nullable=false)
private String password;
@Column(nullable=false)
private String firstName;
@Column(nullable=false)
private String lastName;
...
@Entity
public class Product {
@Id @GeneratedValue
private int id;
@Column(nullable=false)
private String name;
@Column(nullable=false)
private double price;
@Column(nullable=false)
private int count;
...
@Entity
public class Cart {
@Id
private String email;
@OneToOne
@PrimaryKeyJoinColumn(referencedColumnName="email")
private Account account;
@ManyToMany
private List<Product> products = new ArrayList<Product>();
...
Test-first approach
/**
* A user shall be able to establish an account with just a first
* and last name and a unique email address.
*/
@Test
public void establishAccount() {
log.info("*** establishAccount ***");
//the user will supply their email address, first and last name
String email="jharb@ravens.com";
String firstName="john";
String lastName="harbaugh";
Account account = purchasing.createAccount(email, firstName, lastName);
//they will get back a generated password to use as a login for the account
assertNotNull("no account returned", account);
assertNotNull("no password assigned", account.getPassword());
}
/**
* A user shall be able browse products in the catalog.
*/
@Test
public void browseCatalog() {
log.info("*** browseCatalog ***");
//the user will ask for product summaries in pages
int pageSize=10;
int offset=0;
List<Product> products = catalog.getProducts(offset, pageSize);
//they will receive <= a page size of product information
assertNotNull("no products returned", products);
assertTrue("no products provided", products.size() > 0);
//they can page thru the entire set
for (int i=0; products.size() != 0; i++) {
offset += products.size();
products = catalog.getProducts(offset, pageSize);
assertTrue("this catalog never ends!!!", i<100);
}
}
/**
* A user shall be able to purchase a product in the catalog.
*/
@Test
public void purchaseProduct() {
log.info("*** purchaseProduct ***");
//the user selects a product
Product product=null;
Random random=new Random();
for (int i=0; product == null; i++) {
List<Product> products=catalog.getProducts(random.nextInt(100), 1);
product=products.iterator().next();
assertTrue("I can't find anything to buy!!!", i<1000);
}
//the user adds the product to their shopping cart by providing the
//product id and their credentials
int count=catalog.addToCart(product.getId(), validEmail);
//the user receives a count of the items in the cart
assertEquals("somebody tweeked my cart!!!!", 1, count);
//the user checks out with the cashier -- payment not yet implemented
double total=purchasing.checkout(validEmail, validPassword);
//the user gets a total amount back as their receipt
assertEquals("price doesn't add up", product.getPrice(), total, .01);
}
|-- pom.xml `-- src |-- main | |-- java | | `-- ejava | | `-- examples | | `-- blpurchase | | |-- bo | | | |-- Account.java | | | |-- Cart.java | | | `-- Product.java | | |-- bl | | | |-- Catalog.java | | | `-- Purchasing.java | | `-- blimpl | | |-- CatalogImpl.java | | `-- PurchasingImpl.java | `-- resources `-- test |-- java | `-- ejava | `-- examples | `-- blpurchase | `-- bl | |-- PurchaseTest.java | `-- PurchasingFactory.java `-- resources |-- hibernate.properties |-- log4j.xml `-- META-INF `-- persistence.xml
Copyright © 2015 jim stafford (jim.stafford@jhu.edu)
Built on: 2015-11-18 01:37 EST
Abstract
This presentation provides and introduction to the Data Access Object (DAO) pattern to encapsulate access to data, database schema used to design data organization in RDBMS systems, and SQL to access the data.
Table of Contents
At the completion of this topic, the student shall
have an understanding of:
The role played by a DAO and business tier as it relates to accessing data
Core concepts to relational tables
How to define database tables and relationships using RDBMS schema
How to access data within the database using SQL
be able to:
Design the interface for a DAO
Create a schema in the database
Implement a basic DAO using EntityManager and native SQL commands
Interfaces to data sources vary
Relational Database Management Systems (RDBMS)
NoSQL Solutions
Flat Files
Backend Systems
Even standard RDBMS/SQL interfaces can vary
Many components within application need access to data
Interfaces to data vary by technology and vendor
Least common denominator option for portability may not be feasible in all cases
May make use of vendor extensions
Impact of unique interfaces significant when exposed to many components and component types
Components need more abstraction and shielding from the details of the persistent store
Use a Data Access Object (DAO) to abstract and encapsulate access to business objects in the data source
Business Logic
Object within the business domain that needs access to data (e.g., session bean)
Knows when/why data is needed, but not how
Data Access Object
Abstracts the access details from the business object
Knows where/how data is accessed, but not when/why
Business Object (Entity)
An entity within the business logic
Encapsulates information and data business rules within application
A data carrier of information to/from the DAO
Data Source
Physically stores the data (e.g., database)
Key point: hide details from business logic and other interfacing components
Centralizes All Data Access into a Separate Layer
Easier to maintain
Enables Transparency
Access to implementation details hidden within DAO
Enables Easier Migration
Client layers encapsulated from changes
Reduces Code Complexity in Business Logic
No details, such as SQL, in business logic
Harder to abstract with EJB2.x Container Managed Persistence (CMP) frameworks
EJB3 Java Persistence API provides a significant amount of abstraction
Technology agnostic and business object-focused
No mention of Connection or EntityManager in methods
Ability to at least CRUD (with possible options)
Aggregate data functions added when behavior better performed at data source
Extensions added to address data access details for specific use cases (e.g., LAZY/EAGER load)
...
public interface BookDAO {
Book create(Book book) throws PersistenceException;
Book update(Book book) throws PersistenceException;
Book get(long id) throws PersistenceException;
void remove(Book book) throws PersistenceException;
List<Book> findAll(int start, int count) throws PersistenceException;
}
The declaration of the unchecked/runtime exception PersistenceException is not required and is only being done here for extra clarity
Runtime Exceptions
Used to report unexpected issues (e.g., no connection)
Extends java.lang.RuntimeException
ex. javax.persistence.PersistenceException
Checked Exceptions
Used to report anticipated errors mostly have to do with input
Extends java.lang.Exception
Adds implementation out-of-band from DAO interface
...
public class JPABookDAOImpl implements BookDAO {
private EntityManager em;
public void setEntityManager(EntityManager em) {
this.em = em;
}
@Override
public Book create(Book book) { ... }
@Override
public Book update(Book book) { ... }
@Override
public Book get(long id) { ... }
@Override
public void remove(Book book) { ... }
@Override
public List<Book> findAll(int offset, int limit) { ... }
}
Demonstrates how technology-neutral DAO clients can be when dao implementation is injected into client.
...
public class BookDAOTestBase {
protected BookDAO dao; //sub-classes will provide an implementation.
protected Book makeBook() {
Random random = new Random();
Book book = new Book();
book.setTitle("GWW-" + random.nextInt());
...
return book;
}
@Test
public void testCreate() {
Book book = makeBook();
assertEquals("id not assigned", 0, book.getId());
book = dao.create(book);
assertTrue("id not assigned", book.getId()>0);
}
@Test
public void testGet() {... }
@Test
public void testUpdate() { ... }
@Test
public void testDelete() { ... }
@Test
public void testFindAll() { ... }
}
Business Objects represent too much information or behavior to transfer to remote client
Client may get information they don't need
Client may get information they can't handle
Client may get information they are not authorized to use
Client may get too much information/behavior to be useful (e.g., entire database serialized to client)
Some clients are local and can share object references with business logic
Handling specifics of remote clients outside of core scope of business logic
Layer a Remote Facade over Business Logic
Remote Facade constructs Data Transfer Objects (DTOs) from Business Objects that are appropriate for remote client view
Remote Facade uses DTOs to construct or locate Business Objects to communicate with Business Logic
Data Transfer Object
Represents a subset of the state of the application at a point in time
Not dependent on Business Objects or server-side technologies
Doing so would require sending Business Objects to client
XML and JSON provide the “ultimate isolation” in DTO implementation/isolation
Remote Facade
Uses Business Logic to perform core business logic
Layered on top of Business Logic to translate between Business Objects and DTOs
Business Logic
Continues to perform core duties as described in DAO Pattern
Business Object (Entity)
Continues to perform core duties as described in DAO Pattern
May have more server-side-specific logic when DTOs are present in the design
Clients only get what they need
Clients only get what they understand
Clients only get what they are authorized to use
Remote and Local interfaces to services are different
Makes it harder to provide location transparency
Lightweight Business Objects can be used as DTOs
Remote Facade must make sure they are “pruned” of excess related items before transferring to client
Remote Facade must make sure they are “cleaned” of DAO persistence classes before transferring to client
DBMS based on a relational model
Introduced by E. F. Codd in 1970s
Some challenges by other forms but still remains a standard for corporate data stores
Table
Group of columns
Represents a type
Commonly mapped to a Java class
Column
Single piece of data
Represents a property
Commonly mapped to a Java class attribute
Sample (H2) Column Types[8]
INTEGER
DECIMAL
TIME
DATE
TIMESTAMP
VARCHAR
BLOB
CLOB
NOT NULL
Row cannot exist without this column value supplied
UNIQUE
No other row may have a column with this value
FOREIGN KEY
If supplied, must reference matching column(s) of existing row
Foreign Key Join
Column within child table references parent
Primary Key Join
Foreign key column within child table is child's primary key column. Parent and child table primary keys must match.
Link Table Join
Foreign keys to parent/child expressed in separate table
Foreign keys may be defined in child table or link table
Link tables can always be used (at an extra cost) no matter the cardinality
Foreign keys cannot be defined on "one side" of a one-to-many relationship
Link table must be used if foreign key cannot be placed on many side
Link tables must be used in many-to-many relationships
Used to manipulate schema in RDBMS
create table JPADAO_AUTHOR ( ID integer generated by default as identity, FIRST_NAME varchar(16) not null, LAST_NAME varchar(32) not null, primary key (ID) ); create table JPADAO_BOOK ( ID bigint generated by default as identity, DESCRIPTION varchar(1000), PAGES integer, TITLE varchar(32) not null, AUTHOR_ID integer, primary key (ID) );
alter table JPADAO_BOOK add constraint JPADAO_BOOK_AUTHOR_FK foreign key (AUTHOR_ID) references JPADAO_AUTHOR
create index JPADAO_BOOK_AUTHOR_FKX on JPADAO_BOOK(AUTHOR_ID); create unique index JPADAO_BOOK_TITLE_IDX on JPADAO_BOOK(TITLE);
src/main/resources/ `-- ddl |-- book-create.ddl |-- book-drop.ddl |-- book-tuningadd.ddl `-- book-tuningremove.ddl target/classes/ `-- ddl |-- book-create.ddl |-- book-drop.ddl |-- book-tuningadd.ddl `-- book-tuningremove.ddl
insert into JPADAO_BOOK (ID, DESCRIPTION, PAGES, TITLE) values (null, 'this and that', 1037, 'gww')
Get the generated primary key value
call IDENTITY()
public class JDBCBookDAOImpl implements BookDAO {
private Connection connection;
public void setConnection(Connection connection) {
this.connection = connection;
}
@Override
public Book create(Book book) throws PersistenceException {
PreparedStatement statement=null;
ResultSet rs = null;
try {
statement=connection.prepareStatement(
"insert into JPADAO_BOOK (ID, DESCRIPTION, PAGES, TITLE) " +
"values (null, ?, ?, ?)");
statement.setString(1, book.getDescription());
statement.setInt(2, book.getPages());
statement.setString(3, book.getTitle());
statement.execute();
statement.close();
Field id = Book.class.getDeclaredField("id");
id.setAccessible(true);
statement = connection.prepareStatement("call identity()");
rs = statement.executeQuery();
if (rs.next()) {
id.set(book, rs.getLong(1));
}
return book;
} catch (SQLException ex) {
throw new PersistenceException("SQL error creating book", ex);
} catch (NoSuchFieldException ex) {
throw new RuntimeException("Error locating id field", ex);
} catch (SecurityException ex) {
throw new RuntimeException("Security error setting id", ex);
} catch (IllegalArgumentException ex) {
throw new RuntimeException("Error setting id", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException("Access error setting id", ex);
} finally {
try { rs.close(); } catch (Exception ex){}
try { statement.close(); } catch (Exception ex){}
}
}
...
}
public class JPANativeSQLBookDAO implements BookDAO {
private EntityManager em;
public void setEntityManager(EntityManager em) {
this.em = em;
}
@Override
public Book create(Book book) throws PersistenceException {
em.createNativeQuery(
"insert into JPADAO_BOOK (ID, DESCRIPTION, PAGES, TITLE) " +
"values (null, ?1, ?2, ?3)")
.setParameter(1, book.getDescription())
.setParameter(2, book.getPages())
.setParameter(3, book.getTitle())
.executeUpdate();
int idVal = ((Number)em.createNativeQuery("call identity()")
.getSingleResult()).intValue();
try {
Field id = Book.class.getDeclaredField("id");
id.setAccessible(true);
id.set(book, idVal);
} catch (Exception ex) {
throw new RuntimeException("Error setting id", ex);
}
return book;
}
...
}
Table of Contents
At the completion of this topic, the student shall
have an understanding of:
Requirements for entity classes
How to define a persistence unit
How to obtain an entity manager
Persistence methods available from EntityManager
be able to:
Define a Java class as an entity
Define a persistence unit with entity class(es)
Implement DAO operations in terms of javax.persistence.Persistence API
Earlier versions of EJB Spec defined persistence as part of javax.ejb.EntityBean
JavaEE 5 moved persistence to its own specification
Java Persistence API (JPA) version 1.0
javax.persistence
Ease of use API above JDBC
Provides
Object/Relational Mapping (ORM) Engine
Query Language (JPA-QL) - SQL-like - main carryover from EJB 2.x
JavaEE 6 advanced specification
Java Persistence API (JPA) version 2.0
More mapping capabilities
More entity manager capabilities
Standardization of properties
Criteria API
JavaEE 7 enhanced specification
Java Persistence API (JPA) version 2.1
Converters - to convert attributes to/from DB types
Criteria API Bulk Updates
Stored Procedure Query support
Partial fetching of objects
...
Replaced EJB 2.x Home Functionality
Handles O/R Mapping to the database
Provides APIs for
Inserting into database
Updating entities in database
Finding and querying for entities in database
Removing entities from database
Provides caching
Integrates with JTA transactions
Tightly integrated with JavaEE and EJB, but not coupled to it
Plain Old Java Objects (POJOs)
Nothing special happens when calling new()
Author author = new Author();
From JPA perspective the above is a new/unmanaged entity
Persistent when associated with an entity manager/persistence context
em.persist(author);
@javax.persistence.Entity
public class Author {
@Id
@GeneratedValue
private long id;
private long version=0;
private String firstName;
private String lastName;
private String subject;
private Date publishDate;
public Author() {
}
...
}
Entity minimum requirements:
Annotated as entity or declared in orm.xml
Unique identity (form primary key(s))
Non-private default constructor
Author author = new Author();
author.setFirstName("dr");
author.setLastName("seuss");
author.setSubject("children");
author.setPublishDate(new Date());
log.debug("creating author:" + author);
assertFalse("unexpected initialized id", author.getId() > 0);
log.debug("em.contains(author)=" + em.contains(author));
em.persist(author);
log.debug("created author:" + author);
assertTrue("missing id", author.getId() > 0);
log.debug("em.contains(author)=" + em.contains(author));
-creating author:ejava.examples.daoex.bo.Author@1d8e9e, id=0, fn=dr, ln=seuss, subject=children, pdate=Mon Sep 17 00:22:25 EDT 2012, version=0 -em.contains(author)=false -created author:ejava.examples.daoex.bo.Author@1d8e9e, id=50, fn=dr, ln=seuss, subject=children, pdate=Mon Sep 17 00:22:25 EDT 2012, version=0 -em.contains(author)=true
Associated with persistence context
Has identity
Changes to the entity will impact the database
Method em.contains(entity)
returns true
Has identity but not associated with persistence context
Changes to entity will not impact the database
Method em.contains(entity)
returns false
An entity becomes detached when:
Has not yet been persisted
After a transaction-scoped transaction is committed
After a transaction rollback
Manually detaching entity from persistence context thru em.detach()
Manually clearing the persistence context thru em.clear()
Closing EntityManager
Serializing entity thru a remote interface
A set of managed instances managed by an EntityManager
All entities become detached once closed
Two types:
Extended
Author author = new Author();
...
em.persist(author);
em.getTransaction().begin();
em.getTransaction().commit();
author.setFirstName("foo");
em.getTransaction().begin();
em.getTransaction().commit();
em.getTransaction().begin();
author.setFirstName("bar");
em.getTransaction().commit();
Live beyond a single transaction
Allow long-lived algorithms to process without tying up a database transaction
Transaction-Scoped
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Author createAuthor(...) {
Author author = new Author();
...
em.persist(author);
return author;
}
Begin/end at transaction boundaries
Injected by containers
A set of classes that are mapped to the database
Defined in META-INF/persistence.xml
Entity classes may be named in persistence.xml or searched for
Entity mapping may be provided, augmented, or overridden with orm.xml
mapping file
|-- ejava | `-- examples | `-- daoex | |-- AuthorDAO.class | |-- bo | | `-- Author.class | |-- DAOException.class | `-- jpa | `-- JPAAuthorDAO.class `-- META-INF |-- orm.xml `-- 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_2_0.xsd"
version="2.0">
<persistence-unit name="jpaDemo">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<mapping-file>META-INF/orm.xml</mapping-file>
<properties>
<!-- standard properties -->
<property name="javax.persistence.jdbc.url" value="jdbc:h2:./target/h2db/ejava"/>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<!-- hibernate-specific properties -->
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<!-- set to 0 to improve error messages when needed
<property name="hibernate.jdbc.batch_size" value="0"/>
-->
</properties>
</persistence-unit>
</persistence>
Above example defines properties for entity manager to establish physical connections to database
<?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="ejbsessionbank">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<jar-file>lib/ejbsessionBankImpl-3.0.2012.2-SNAPSHOT.jar</jar-file>
<properties>
<property name="hibernate.dialect"
value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.show_sql" value="false"/>
<!-- create is used here for demo project only -->
<property name="hibernate.hbm2ddl.auto" value="create"/>
<!--
<property name="hibernate.jdbc.batch_size" value="0"/>
-->
</properties>
</persistence-unit>
</persistence>
Above example used DataSource from JNDI tree to obtain connections to database
Can be used to specify connection properties outside of persistence.xml
Useful in separating production mapping information from runtime connection properties
#hibernate-specific alternate source of persistence.xml properties hibernate.dialect=org.hibernate.dialect.H2Dialect hibernate.connection.url=jdbc:h2:./target/h2db/ejava hibernate.connection.driver_class=org.h2.Driver hibernate.connection.password= hibernate.connection.username=sa hibernate.hbm2ddl.auto=create hibernate.show_sql=true hibernate.format_sql=true #hibernate.jdbc.batch_size=0
<?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.daoex.bo.Author"
access="FIELD"
metadata-complete="false"
name="jpaAuthor">
<table name="DAO_AUTHOR"/>
<attributes>
<id name="id">
<generated-value strategy="SEQUENCE"
generator="AUTHOR_SEQUENCE"/>
</id>
</attributes>
</entity>
</entity-mappings>
The JPA 2.1 schema references are shown below. They have been left out of all class examples because they are not yet compatible with the schema generation plugin (hibernate3) used in class. It is unlikely that you will need features unique to JPA 2.1 declared in your XML files but know if you include references to JPA 2.1 you must use an alternate means of generating schema -- possibly manually.
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd" version="2.1">
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd" version="2.1">
name
Identity used to reference persistence unit
provider
Fully qualified name of javax.persistence.PersistenceProvider
Not needed if provider is in classpath
mapping-file
Path reference to an orm.xml
mapping file
jta-data-source
JNDI path of a JTA javax.sql.DataSource
non-jta-datasource
JNDI path of a RESOURCE_LOCAL javax.sql.DataSource
jarfile
Reference to an archive with entity classes
class
Fully qualified package name of entity class
One source of entity information
exclude-unlisted-classes
If set, provider will not scan to discover entity classes
properties
name/value property pairs to express additional configuration info
setUp
Create EntityManagerFactory
public class javax.persistence.Persistence extends java.lang.Object{
public static javax.persistence.EntityManagerFactory createEntityManagerFactory(java.lang.String);
public static javax.persistence.EntityManagerFactory createEntityManagerFactory(java.lang.String, java.util.Map);
...
}
private static EntityManagerFactory emf;
@BeforeClass
public static void setUpClass() {
emf = Persistence.createEntityManagerFactory("jpaDemo");
Create EntityManager
public interface javax.persistence.EntityManagerFactory{
public abstract javax.persistence.EntityManager createEntityManager();
public abstract javax.persistence.EntityManager createEntityManager(java.util.Map);
...
}
private EntityManager em;
@Before
public void setUp() throws Exception {
em = emf.createEntityManager();
Runtime
Start Transaction
em.getTransaction().begin();
Interact with EntityManager
em.persist(author);
Commit Transaction
em.getTransaction().commit();
tearDown
Close EntityManager
@After
public void tearDown() throws Exception {
try {
if (em != null) {
if (!em.getTransaction().isActive()) {
em.getTransaction().begin();
em.getTransaction().commit();
}
else if (!em.getTransaction().getRollbackOnly()) {
em.getTransaction().commit();
}
else {
em.getTransaction().rollback();
}
}
}
finally {
if (em != null) { em.close(); em=null;}
}
Close EntityManagerFactory
@AfterClass
public static void tearDownClass() {
if (emf != null) {
emf.close();
emf=null;
}
}
public interface javax.persistence.EntityManager{ ... }
void persist(Object entity);
<T> T find(Class<T> entityClass, Object primaryKey);
<T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties);
<T> T merge(T entity);
void remove(Object entity);
<T> T getReference(Class<T> entityClass, Object primaryKey);
void clear();
void detach(Object entity);
boolean contains(Object entity);
void flush();
void setFlushMode(javax.persistence.FlushModeType);
javax.persistence.FlushModeType getFlushMode();
void refresh(Object);
void refresh(Object, java.util.Map);
void lock(Object entity, javax.persistence.LockModeType);
void lock(Object entity, javax.persistence.LockModeType, Map<String, Object> properties);
<T> T find(Class<T> entityClass, Object primaryKey, javax.persistence.LockModeType);
<T> T find(Class<T> entityClass, Object primaryKey, javax.persistence.LockModeType, Map<String, Object> properties);
void refresh(Object, javax.persistence.LockModeType);
void refresh(Object, javax.persistence.LockModeType, Map<String, Object> properties);
javax.persistence.LockModeType getLockMode(Object entity);
javax.persistence.Query createQuery(String jpaql);
<T> javax.persistence.TypedQuery<T> createQuery(String jpaql, Class<T> resultClass);
javax.persistence.Query createNamedQuery(String name);
<T> javax.persistence.TypedQuery<T> createNamedQuery(String name, Class<T> resultClass);
javax.persistence.Query createNativeQuery(String sql);
javax.persistence.Query createNativeQuery(String sql, Class resultClass);
javax.persistence.Query createNativeQuery(String sql, String resultMapping);
<T> javax.persistence.TypedQuery<T> createQuery(javax.persistence.criteria.CriteriaQuery<T> criteria);
javax.persistence.criteria.CriteriaBuilder getCriteriaBuilder();
void close();
boolean isOpen();
javax.persistence.EntityTransaction getTransaction();
void joinTransaction();
void setProperty(String key, Object value);
java.util.Map getProperties();
<T> T unwrap(Class<T> clazz);
Object getDelegate();
javax.persistence.metamodel.Metamodel getMetamodel();
javax.persistence.EntityManagerFactory getEntityManagerFactory();
Author author = new Author();
author.setFirstName("dr");
author.setLastName("seuss");
author.setSubject("children");
author.setPublishDate(new Date());
log.debug("creating author:" + author);
em.persist(author);
log.debug("created author:" + author);
-creating author:ejava.examples.daoex.bo.Author@17e7691, id=0, fn=dr, ln=seuss, subject=children, pdate=Sun Sep 16 10:14:32 EDT 2012, version=0 -created author:ejava.examples.daoex.bo.Author@17e7691, id=50, fn=dr, ln=seuss, subject=children, pdate=Sun Sep 16 10:14:32 EDT 2012, version=0
Inserts entity into database
Actual insert time depends on transaction active and FlushMode
Extended Persistence Context - queues insert until transaction active
Transaction-Scoped Persistence Context - always has transaction active
Flush occurs automatically prior to or during commit()
Flush can be forced with manual em.flush()
call
New or removed entity enters managed state
All further changes are watched and will update database
log.debug("em.contains(author)=" + em.contains(author));
em.persist(author);
log.debug("created author:" + author);
log.debug("em.contains(author)=" + em.contains(author));
-em.contains(author)=false -created author:... -em.contains(author)=true
Existing entity is ignored
Cascades will still occur for CascadeType.PERSIST and ALL relationships
em.persist(author);
...
em.persist(author);
Detached entities will be rejected
Entities with an identity but not associated with a persistence context
Author author = new Author(1);
author.setFirstName("dr");
...
log.debug("creating author:" + author);
log.debug("em.contains(author)=" + em.contains(author));
try {
em.persist(author);
fail("did not detect detached entity");
} catch (PersistenceException ex) {
log.debug("caught expected exception:" + ex.getLocalizedMessage(), ex);
}
log.debug("em.contains(author)=" + em.contains(author));
-creating author:ejava.examples.daoex.bo.Author@ad339b, id=1, ... -em.contains(author)=false -caught expected exception:javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: ejava.examples.daoex.bo.Author -em.contains(author)=false
<T> T find(Class<T> entityClass, Object primaryKey);
<T> T find(Class<T> entityClass, Object primaryKey, Map<String, Object> properties);
Searches for the entity by primary key value
Returns managed entity if found, else null
Properties permitted for vendor-specific values and ignored if not understood
<T> T getReference(Class<T> entityClass, Object primaryKey);
Searches for the entity by primary key
Returns a LAZY load reference if found
Throws EntityNotFoundException if not exist
//create initial author
Author author = new Author();
...
em.persist(author);
//create detached author with changes
Author author2 = new Author(author.getId());
author2.setFirstName("updated " + author.getFirstName());
...
//merge changes
Author tmp = em.merge(author2);
em.getTransaction().begin();
em.getTransaction().commit();
...
//verify results
assertFalse("author2 is managed", em.contains(author2));
assertTrue("tmp Author is not managed", em.contains(tmp));
assertSame("merged result not existing managed", author, tmp);
...
//verify changes were made to the DB
Author author3 = em.find(Author.class, author.getId());
assertEquals("updated " + firstName, author3.getFirstName());
Update database with state of detached object
State of detached object copied into managed entity or new instance created
Merging a managed entity is ignored. CascadeType.MERGE and ALL relationships are propagated
Merging a removed entity instance results in an IllegalArgumentException
Includes automatic checking of @Version
property
Could be replaced with manual merge
public Author update(Author author) {
Author dbAuthor = em.find(Author.class, author.getId());
dbAuthor.setFirstName(author.getFirstName());
dbAuthor.setLastName(author.getLastName());
dbAuthor.setSubject(author.getSubject());
dbAuthor.setPublishDate(author.getPublishDate());
return dbAuthor;
}
The above manual merge will successfully merge a transient entity -- but what errors and conditions does it not account for?
void remove(Object entity);
Managed entity is removed from database
Actual delete time depends on transaction active and FlushMode
Extended Persistence Context - queues delete until transaction active
New entities are ignored but CascadeType.REMOVE and ALL relationships are cascaded
log.debug("em.contains(author)=" + em.contains(author));
em.remove(author);
log.debug("em.contains(author)=" + em.contains(author));
-em.contains(author)=false -HHH000114: Handling transient entity in delete processing -em.contains(author)=false
Detached entities are rejected
Author author = new Author(1);
...
try {
em.remove(author);
fail("did not reject removal of detached object");
} catch (IllegalArgumentException ex) {
log.debug("caught expected exception:" + ex);
}
-caught expected exception:java.lang.IllegalArgumentException: Removing a detached instance ejava.examples.daoex.bo.Author#1
Removed entities are ignored
Author author = new Author();
...
log.debug("peristed:" + author);
log.debug("em.contains(author)=" + em.contains(author));
em.remove(author);
log.debug("em.contains(author)=" + em.contains(author));
//entity managers will ignore the removal of a removed entity
em.remove(author);
log.debug("em.contains(author)=" + em.contains(author));
-peristed:ejava.examples.daoex.bo.Author@6c5356, id=50, ... -em.contains(author)=true -em.contains(author)=false -em.contains(author)=false
boolean contains(Object entity);
Returns true if object is managed in the persistence context
void clear();
Clears all entities and queued changes from the persistence context
All entities become detached
em.persist(author);
//callers can detach entity from persistence context
log.debug("em.contains(author)="+em.contains(author));
log.debug("detaching author");
em.getTransaction().begin();
em.flush();
em.detach(author);
log.debug("em.contains(author)="+em.contains(author));
em.getTransaction().commit();
//changes to detached entities do not change database
author.setFirstName("foo");
em.getTransaction().begin();
em.getTransaction().commit();
Author author2 = em.find(Author.class, author.getId());
log.debug("author.firstName=" + author.getFirstName());
log.debug("author2.firstName=" + author2.getFirstName());
assertFalse("unexpected name change",
author.getFirstName().equals(author2.getFirstName()));
-em.contains(author)=true -detaching author -em.contains(author)=false -author.firstName=foo -author2.firstName=dr
Detaches existing entity from persistence context
Detach cascaded to CascadeType.DETACH and ALL relationships
Subsequent changes to entity will not change database
Portable use requires call to flush() prior to detach()
New entities are ignored
Author author = new Author();
log.debug("em.contains(author)="+em.contains(author));
log.debug("detaching author");
em.detach(author);
log.debug("em.contains(author)="+em.contains(author));
-em.contains(author)=false -detaching author -em.contains(author)=false
Detached entities are ignored
Author author = new Author();
...
em.persist(author);
em.getTransaction().begin();
em.getTransaction().commit();
//detaching detached entity will be ignored
Author detached = new Author(author.getId());
log.debug("em.contains(author)="+em.contains(detached));
log.debug("detaching detached author");
em.detach(detached);
log.debug("em.contains(author)="+em.contains(detached));
-em.contains(author)=false -detaching detached author -em.contains(author)=false
void flush();
Synchronizes cached changes with underlying database
Requires active transaction
void setFlushMode(javax.persistence.FlushModeType);
javax.persistence.FlushModeType getFlushMode();
AUTO (default) - unspecified
COMMIT - flush only happens during commit
em.persist(author);
em.getTransaction().begin();
em.getTransaction().commit();
//change DB state out-of-band from the cache
em.getTransaction().begin();
String newName="foo";
int count=em.createQuery(
"update jpaAuthor a set a.firstName=:name where a.id=:id")
.setParameter("id", author.getId())
.setParameter("name", newName)
.executeUpdate();
em.getTransaction().commit();
assertEquals("unexpected count", 1, count);
//object state becomes stale when DB changed out-of-band
log.debug("author.firstName=" + author.getFirstName());
assertFalse("unexpected name", author.getFirstName().equals(newName));
//get the cached object back in sync
log.debug("calling refresh");
em.refresh(author);
log.debug("author.firstName=" + author.getFirstName());
assertEquals("unexpected name", newName, author.getFirstName());
-author.firstName=dr -calling refresh -author.firstName=foo
Updates/overwrites cached entity state with database state
Refresh of new entity results in java.lang.IllegalArgumentException
Author author = new Author();
author.setFirstName("test");
author.setLastName("new");
try {
em.refresh(author);
fail("refresh of new entity not detected");
} catch (IllegalArgumentException ex) {
log.debug("caught expected exception:" + ex);
}
-caught expected exception:java.lang.IllegalArgumentException: Entity not managed
Refresh of detached entity results in java.lang.IllegalArgumentException
em.persist(author);
em.getTransaction().begin();
em.getTransaction().commit();
//refreshing a detached entity will get rejected
Author detached = new Author(author.getId());
em.refresh(author);
log.debug("refreshed managed entity");
try {
em.refresh(detached);
fail("refresh of detached entity not detected");
} catch (IllegalArgumentException ex) {
log.debug("caught expected exception:" + ex);
}
-refreshed managed entity -caught expected exception:java.lang.IllegalArgumentException: Entity not managed
Optimistic
Entity assumed not to have concurrent access
No active database locks are obtained at start
Success judged based on entity state at end
State tracked in entity @Version
field
Pessimistic
Entity requires mitigation for concurrent access
Active database locks are obtained at start of transaction
NONE
No locks
OPTIMISTIC (was READ)
Requires entity to have a @Version
property
Prevent dirty reads
Prevent non-repeatable reads
OPTIMISTIC_FORCE_INCREMENT (was WRITE)
Requires entity to have a @Version
property
Update only occurs if change has proper version
Version is incremented upon update
Incorrect version results in OptimisticLockException
PESSIMISTIC_READ
Supported with or without @Version
property
Obtains active database lock
Provides repeatable reads
Does not block other reads
PESSIMISTIC_WRITE
Supported with or without @Version
property
Forces serialization of entity updates among transactions
PESSIMISTIC_FORCE_INCREMENT
Requires entity to have a @Version
property
PESSIMISTIC_WRITE lock with increment of @Version
void lock(Object entity, javax.persistence.LockModeType);
void lock(Object entity, javax.persistence.LockModeType, Map<String, Object> properties);
Requests lock on entity
<T> T find(Class<T> entityClass, Object primaryKey, javax.persistence.LockModeType);
<T> T find(Class<T> entityClass, Object primaryKey, javax.persistence.LockModeType, Map<String, Object> properties);
Find object by primary key and lock
void refresh(Object, javax.persistence.LockModeType);
void refresh(Object, javax.persistence.LockModeType, Map<String, Object> properties);
Refresh entity state and obtain lock
javax.persistence.Query createQuery(String jpaql);
<T> javax.persistence.TypedQuery<T> createQuery(String jpaql, Class<T> resultClass);
javax.persistence.Query createNamedQuery(String name);
<T> javax.persistence.TypedQuery<T> createNamedQuery(String name, Class<T> resultClass);
Create query based on JPA Query Language (JPAQL)
javax.persistence.Query createNativeQuery(String sql);
javax.persistence.Query createNativeQuery(String sql, Class resultClass);
javax.persistence.Query createNativeQuery(String sql, String resultMapping);
Create query based on SQL
javax.persistence.EntityTransaction getTransaction();
Returns transaction object for inspection and control
Invalid call for transaction-scoped persistence contexts
void joinTransaction();
Associate a persistence context with a JTA transaction
<T> T unwrap(Class<T> clazz);
Return object of specified type
Provides access to underlying implementation classes
javax.persistence.metamodel.Metamodel getMetamodel();
Returns metamodel object for persistence context
javax.persistence.EntityManagerFactory getEntityManagerFactory();
Returns EntityManagerFactory associated with EntityManager
src |-- main | |-- java | | `-- ejava | | `-- examples | | `-- daoex | | |-- AuthorDAO.java | | |-- bo | | | `-- Author.java | | |-- DAOException.java | | `-- jpa | | `-- JPAAuthorDAO.java | `-- resources | `-- META-INF | |-- orm.xml | `-- persistence.xml (could be placed in src/test branch) `-- test |-- java | `-- ejava | `-- examples | `-- daoex | `-- jpa | |-- JPAAuthorDAOTest.java | |-- JPACRUDTest.java | `-- JPATestBase.java `-- resources |-- hibernate.properties (optional) `-- log4j.xml
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.1-api</artifactId>
<version>1.0.0.Final</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.4.Final</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
<scope>test</scope>
</dependency>
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
...
</build>
Above will depend on whether you use src/main or src/test for resource file(s)
<properties>
<!-- standard properties -->
<property name="javax.persistence.jdbc.url" value="${jdbc.url}"/>
<property name="javax.persistence.jdbc.driver" value="${jdbc.driver}"/>
<property name="javax.persistence.jdbc.user" value="${jdbc.user}"/>
<property name="javax.persistence.jdbc.password" value="${jdbc.password}"/>
<!-- hibernate-specific properties -->
<property name="hibernate.dialect" value="${hibernate.dialect}"/>
...
</properties>
<properties>
<jdbc.driver>org.h2.Driver</jdbc.driver>
<jdbc.url>jdbc:h2:${basedir}/target/h2db/ejava</jdbc.url>
<jdbc.user>sa</jdbc.user>
<jdbc.password/>
<hibernate.dialect>
org.hibernate.dialect.H2Dialect
</hibernate.dialect>
</properties>
<properties>
<!-- standard properties -->
<property name="javax.persistence.jdbc.url" value="jdbc:h2:/home/jcstaff/proj/ejava-javaee/git/jpaDAO/target/h2db/ejava"/>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
<property name="javax.persistence.jdbc.user" value="sa"/>
<property name="javax.persistence.jdbc.password" value=""/>
<!-- hibernate-specific properties -->
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
...
</properties>
Table of Contents
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 non-private no-arg constructor
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 46.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 46.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 46.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 46.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 46.5. Example Directory Structure
src/ |-- main | |-- java | | `-- ejava ... | | |-- annotated | | | `-- Bike.java | | `-- mapped | | `-- Bike.java | `-- resources | |-- META-INF | | `-- persistence.xml | `-- orm | `-- Bike-orm.xml `-- test `-- resources `-- 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 46.6. Defining FIELD Access using Annotations
@Entity
public class Bike {
@Id
private long id;
private String make;
Figure 46.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 47.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 47.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 47.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 47.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 47.5. Transient Example Database Schema
create table ORMCORE_TANK ( id bigint not null, make varchar(255), model varchar(255), primary key (id) )
Figure 47.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 47.7. CLOB Example Database Schema
create table ORMCORE_UMBRELLA ( id bigint not null, make clob, model varchar(255), primary key (id) )
Figure 47.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 47.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 47.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 47.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 47.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 48.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 48.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
SEQUENCE
Database generates unique value from a global sequence
IDENTITY
Database generates unique value on a per-table basis
TABLE
AUTO
Provider may choose any technique, including one not specified above
Figure 48.3. Entity with GenerationType.AUTO
@Entity
@Table(name="ORMCORE_DRILL")
public class Drill {
@Id
@GeneratedValue
private long id;
private String make;
...
Figure 48.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=acme
Figure 48.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 48.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 48.7. IDENTITY Database Schema
create table ORMCORE_GADGET ( id bigint generated by default as identity, make varchar(255), primary key (id) )
Figure 48.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 48.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 our manual call to flush()
Figure 48.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 48.11. SEQUENCE Database Schema
create table ORMCORE_FAN ( id bigint not null, make varchar(255), primary key (id) ) create sequence FAN_SEQ
Figure 48.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 1
Figure 48.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 48.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 48.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 48.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 48.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 49.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 49.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 49.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 49.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 49.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 49.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 49.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 49.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 49.9. Example Overridden @Embeddable PK Class
@Embeddable
public class MakeModelPK implements Serializable {
private String make;
private String model;
Figure 49.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 49.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 49.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 50.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_WATCH
Figure 50.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 50.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 50.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 51.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 51.2. Embedded Object Example Embedded Class
@Embeddable
public class Manufacturer {
private String name;
private String address;
private String phone;
Figure 51.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 51.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 51.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
Table of Contents
Explore capabilities of Validation API
Introduce the JPA persistence lifecycle
Demonstrate Validation API integration with JPA lifecycle
Integrate validation into Maven builds
Independent of other APIs (e.g., persistence, web-tier)
Available server-side and client-side
Provide validation metadata thru annotations and XML descriptors
Applied to FIELDs, METHODs, TYPEs, etc.
import javax.validation.constraints.*;
public class Person {
private int id;
@NotNull
@Size(min=1,max=12)
@Pattern(regexp="^[a-zA-Z\\ \\-]+$", message="invalid characters in name")
private String firstName;
@NotNull
@Size(min=1,max=20)
@Pattern(regexp="^[a-zA-Z\\ \\-]+$", message="invalid characters in name")
private String lastName;
@Past
private Date birthDate;
@Size(min=7,max=50)
@Pattern(regexp="^.+@.+\\..+$")
private String email;
Obtain instance of Validator from ValidationFactory
private ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
private Validator val = vf.getValidator();
Validate bean instances
Person p = new Person()
.setFirstName("Bob2")
.setLastName("Smith")
.setEmail("bob2");
Set<ConstraintViolation<Person>> violations = val.validate(p);
for (ConstraintViolation<Person> v : violations) {
log.info(v.getPropertyPath() + ":" + v.getInvalidValue() + " " + v.getMessage());
}
assertEquals("unexpected number of violations", 3, violations.size());
-email:bob2 size must be between 7 and 50 -email:bob2 must match "^.+@.+\..+$" -firstName:Bob2 invalid first name
Logical grouping of constraints
Used to define which constraints and activated when
Can be used to define sequences -- with short-circuit
package ejava.jpa.example.validation;
import javax.validation.groups.Default;
public interface Drivers extends Default {}
package ejava.jpa.example.validation;
import javax.validation.groups.Default;
public interface POCs extends Default {}
public class Person {
...
@NotNull(groups={Drivers.class, POCs.class})
@Past(groups=Drivers.class)
private Date birthDate;
@NotNull(groups=POCs.class)
@Size(min=7,max=50)
@Pattern(regexp="^.+@.+\\..+$")
private String email;
Validate named groups of constraints
Person p = new Person()
.setFirstName("Bob")
.setLastName("Smith")
.setEmail("bob.smith@gmail.com")
.setBirthDate(new Date(System.currentTimeMillis()+100000));
Set<ConstraintViolation<Person>> validPerson = val.validate(p, Default.class);
Set<ConstraintViolation<Person>> validDriver = val.validate(p, Drivers.class);
Set<ConstraintViolation<Person>> validPOC = val.validate(p, POCs.class);
assertTrue("not validPerson", validPerson.isEmpty());
assertFalse("validDriver", validDriver.isEmpty());
assertTrue("not validPOC", validPOC.isEmpty());
Drivers group fails because birthDate in future
POC does not yet have a birthDate constraint
package ejava.jpa.example.validation;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import javax.validation.Constraint;
import javax.validation.Payload;
/**
* Defines a constraint annotation for expressing a minimum age.
*/
@Documented
@Constraint(validatedBy={MinAgeValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
public @interface MinAge {
String message() default "too young";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default{};
int age() default 0;
}
@Constraint defines validator class
@Target defines what can be assigned to
FIELD - attributes
METHOD - getters
TYPE - classes
ANNOTATION_TYPE - constraints composing other constraints
PARAMETER - no support required by spec
CONSTRUCTOR - no support required by spec
Several reserved properties
message() - used to create error message
groups() - defines which groups constraint member of. Defaults to Default group
payload() - defines association for constraint
@MinAge(age=16, payload=Severity.Critical.class) private Date birthDate;
@NotNull(payload=Severity.Warning.class) private Date voterRegistrationDate;
names starting with "valid" - reserved/not allowed
Define use-specific properties (i.e., age)
Optionally define annotation for multiple annotations
public @interface MinAge {
...
/**
* Defines an array of annotations so that more than one can be applied.
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
public @interface List {
MinAge[] value();
}
}
@Documented adds this spec to API spec of what it annotates
package ejava.jpa.example.validation;
...
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class MinAgeValidator implements ConstraintValidator<MinAge, Date>{
int minAge;
@Override
public void initialize(MinAge constraint) {
this.minAge = constraint.age();
}
@Override
public boolean isValid(Date date, ConstraintValidatorContext ctx) {
if (date==null) { return true; }
//get today's date
Calendar latestBirthDate = new GregorianCalendar();
latestBirthDate.add(Calendar.YEAR, -1*minAge);
//get calendate date of object
Calendar birthDate = new GregorianCalendar();
birthDate.setTime(date);
if (birthDate.after(latestBirthDate)) {
String errorMsg = String.format("%d is younger than minimum %d",
getAge(birthDate),
minAge);
ctx.buildConstraintViolationWithTemplate(errorMsg)
.addConstraintViolation();
return false;
} else {
return true;
}
}
private int getAge(Calendar birth) {
...
...
}
public class Person {
...
@NotNull(groups={Drivers.class, POCs.class})
@MinAge.List({
@MinAge(age=18, groups=POCs.class),
@MinAge(age=16, groups=Drivers.class)
})
private Date birthDate;
...
Calendar fifteen = new GregorianCalendar();
fifteen.add(Calendar.YEAR, -16);
fifteen.add(Calendar.DAY_OF_YEAR, 2);
Person p = new Person()
.setFirstName("Bob")
.setLastName("Smith")
.setBirthDate(fifteen.getTime());
Set<ConstraintViolation<Person>> violations = val.validate(p, Drivers.class);
for (ConstraintViolation<Person> v : violations) {
log.debug(v.getPropertyPath() + ":" + v.getInvalidValue() + " " + v.getMessage());
}
...
assertFalse("valid driver", violations.isEmpty());
Bob is too young to be a valid driver
-birthDate:Wed Jun 11 01:06:33 EDT 1997, 15 is younger than minimum 16 -birthDate:Wed Jun 11 01:06:33 EDT 1997, too young
public class Person {
...
@NotNull
@Size(min=1,max=12)
@Pattern(regexp="^[a-zA-Z\\ \\-]+$", message="invalid characters in name")
private String firstName;
@NotNull
@Size(min=1,max=20)
@Pattern(regexp="^[a-zA-Z\\ \\-]+$", message="invalid characters in name")
private String lastName;
Multiple constraints makeup complete definition
Verbose and tedious to define multiple times
public class Person {
...
@ValidName(min=1, max=12, regexp="^[a-zA-Z\\ \\-]+$", message="invalid first name")
private String firstName;
...
@ValidName(min=1, max=20, regexp="^[a-zA-Z\\ \\-]+$", message="invalid last name")
private String lastName;
...
/**
* Defines a validation composition
*/
@NotNull
@Size
@Pattern(regexp="")
@Documented
@Constraint(validatedBy={})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
public @interface ValidName {
String message() default "invalid name";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default{};
@OverridesAttribute(constraint=Size.class, name="min") int min() default 0;
@OverridesAttribute(constraint=Size.class, name="max") int max() default Integer.MAX_VALUE;
@OverridesAttribute(constraint=Pattern.class, name="regexp") String regexp() default ".*";
/**
* Defines an array of annotations so that more than one can be applied.
*/
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER})
@Retention(RUNTIME)
@Documented
public @interface List {
ValidName[] value();
}
}
Annotate a composite constraint annotation with the building blocks
Define attribute overrides as appropriate
Person p = new Person()
.setFirstName("Bob")
.setLastName("Smithhhhhhhhhhhhhhhhhh$%$%$$$$$$$$$$$$$$$$");
Set<ConstraintViolation<Person>> violations = val.validate(p);
for (ConstraintViolation<Person> v : violations) {
log.debug(v.getPropertyPath() + ":" + v.getInvalidValue() + " " + v.getMessage());
}
-lastName:Smithhhhhhhhhhhhhhhhhh$%$%$$$$$$$$$$$$$$$$ size must be between 1 and 20 -lastName:Smithhhhhhhhhhhhhhhhhh$%$%$$$$$$$$$$$$$$$$ must match "^[a-zA-Z\ \-]+$"
Bean failed two composed constraints
Define a sequence of validation groups to be tested in order and short-circuit upon failure
public interface DBChecks {} //checks whether data will fit within DB
public interface DataChecks {} //more detailed content checks
A set of Constraint Groups defined based on complexity/cost
@GroupSequence({Default.class, DBChecks.class, DataChecks.class})
public interface ValidationSequence {}
A Group Sequence defines the order
@Column(name="STREET", length=32, nullable=false)
@NotNull(message="street not supplied")
@Size(max=32, message="street name too large", groups=DBChecks.class)
@Pattern(regexp="^[0-9A-Za-z\\ ]+$", groups=DataChecks.class,
message="street must be numbers and letters")
private String street;
Constraints are assigned to Constraint Groups
We cause an error in the data size and content
Address a = new Address()
.setStreet("1600$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$")
.setCity("Washington")
.setState("DC")
.setZip("20500");
Set<ConstraintViolation<Address>> violations = val.validate(a, ValidationSequence.class);
...
//we should only get violations from the DB group
assertEquals("unexpected number of violations", 1, violations.size());
Notice Default passed, DBChecks failed, and DataChecks not attempted
-street:1600$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ street name too large
Look across properties of type
...
@Documented
@Constraint(validatedBy={CityStateOrZipValidator.class})
@Target({TYPE, ANNOTATION_TYPE})
@Retention(RUNTIME)
public @interface CityStateOrZip {
String message() default "must have city and state or zip code";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default{};
}
public class CityStateOrZipValidator implements ConstraintValidator<CityStateOrZip, Address1>{
@Override
public void initialize(CityStateOrZip constraintAnnotation) {}
@Override
public boolean isValid(Address1 address, ConstraintValidatorContext context) {
if (address==null) { return true; }
return (address.getCity()!=null && address.getState()!=null) ||
address.getZip()!=null;
}
}
Address1 a1 = new Address1()
.setStreet("1600")
.setCity("Washington");
Set<ConstraintViolation<Address1>> violations = val.validate(a1,PreCheck.class);
...
assertEquals("unexpected violation", 1, violations.size());
-:1600 Washington, null null, must have city and state or zip code
Traverse validation across relationships
public class Purchase {
@Valid
private Set<PurchaseItem> items;
Define Constraints external to Bean Class
Constraints need not be defined within bean class
public class Book {
private int id;
//@NotNull(message="title is required")
//@Size(max=32, message="title too long")
private String title;
//@Min(value=1, message="pages are required")
private int pages;
<validation-config xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration">
<default-provider>org.hibernate.validator.HibernateValidator</default-provider>
<constraint-mapping>META-INF/book-constraints.xml</constraint-mapping>
</validation-config>
<constraint-mappings
xmlns="http://jboss.org/xml/ns/javax/validation/mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jbss.org/xml/ns/javax/validation/mapping
http://jboss.org/xml/ns/javax/validation/mapping/validation-mapping-1.0.xsd">
<bean class="ejava.jpa.example.validation.Book">
<field name="title">
<constraint annotation="javax.validation.constraints.NotNull">
<message>title is required</message>
</constraint>
<constraint annotation="javax.validation.constraints.Size">
<message>title is required</message>
<element name="max">32</element>
</constraint>
</field>
<field name="pages">
<constraint annotation="javax.validation.constraints.Min">
<message>pages are required</message>
<element name="value">1</element>
</constraint>
</field>
</bean>
</constraint-mappings>
Provide access to persistence lifecycle events
Persistence lifecycle events provided to class methods
@Entity
@Table(name="ORMLISTEN_PERSON")
public class Person {
@Id @GeneratedValue
private long id;
private String name;
@OneToOne(mappedBy="person", optional=true, cascade=CascadeType.ALL)
private Residence residence;
@PrePersist public void prePersist() {
}
@PostPersist public void postPersist() {
}
@PostLoad public void postLoad() {
}
@PreUpdate public void preUpdate() {
}
@PostUpdate public void postUpdate() {
}
@PreRemove public void preRemove() {
}
@PostRemove public void postRemove() {
}
}
Could be used to log or validate instance at certain stages of lifecycle
JPA prohibits calling EntityManager within callback methods
Persistence lifecycle events provided to external class
@Entity
@Table(name="ORMLISTEN_PERSON")
@EntityListeners(Listener.class)
public class Person {
public class Listener {
@PrePersist public void prePersist(Object entity) {
}
@PostPersist public void postPersist(Object entity) {
}
@PostLoad public void postLoad(Object entity) {
}
@PreUpdate public void preUpdate(Object entity) {
}
@PostUpdate public void postUpdate(Object entity) {
}
@PreRemove public void preRemove(Object entity) {
}
@PostRemove public void postRemove(Object entity) {
}
}
Same Listener class may listen to multiple entity types
Good for when Listener has specific purpose that is type-agnostic
JPA and Validation API annotations can be mixed together
@Entity
@Table(name="VALIDATION_PERSON")
public class Person {
@Id @GeneratedValue
private int id;
@Column(name="FIRST_NAME", length=12, nullable=false)
@ValidName(min=1, max=12, regexp="^[a-zA-Z\\ \\-]+$", message="invalid first name")
private String firstName;
@Column(name="LAST_NAME", length=20, nullable=false)
@ValidName(min=1, max=20, regexp="^[a-zA-Z\\ \\-]+$", message="invalid last name")
private String lastName;
@Temporal(TemporalType.DATE)
@NotNull(groups={Drivers.class, POCs.class})
@Past(groups=Drivers.class)
@MinAge.List({
@MinAge(age=18, groups=POCs.class),
@MinAge(age=16, groups=Drivers.class)
})
private Date birthDate;
@Column(name="EMAIL", length=50)
@NotNull(groups=POCs.class)
@Size(min=7,max=50)
@Pattern(regexp="^.+@.+\\..+$")
private String email;
...
<?xml version="1.0" encoding="UTF-8"?>
<persistence ...
<persistence-unit name="jpa-validation-example-test">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<validation-mode>AUTO</validation-mode>
<properties>
<property name="javax.persistence.validation.group.pre-persist"
value="ejava.jpa.example.validation.Drivers"/>
<property name="javax.persistence.validation.group.pre-update"
value="ejava.jpa.example.validation.Drivers"/>
</properties>
</persistence-unit>
</persistence>
validation-mode
AUTO - enable if validator present in classpath
CALLBACK - turn on and report error if no validator found in classpath
NONE - turn off
Can also be set using javax.persistence.validation.mode property
validation groups
javax.persistence.validation.group.pre-persist - defines groups to call during @PrePersist phase
javax.persistence.validation.group.pre-update - defines groups to call during @PreUpdate phase
javax.persistence.validation.group.pre-remove - defines groups to call during @PreRemove phase
default behavior
Default group for @PrePersist and @PreUpdate
Nothing for @PreRemove
Person p = new Person()
.setFirstName("Bob")
.setLastName("Smith")
.setBirthDate(new Date());
try {
em.persist(p);
} catch (ConstraintViolationException ex) {
log.info("caught expected exception:" + ex);
}
-caught expected exception:javax.validation.ConstraintViolationException: Validation failed for classes [ejava.jpa.example.validation.Person] during persist time for groups [ejava.jpa.example.validation.Drivers, ] List of constraint violations:[ ConstraintViolationImpl{ interpolatedMessage='too young', propertyPath=birthDate, rootBeanClass=class ejava.jpa.example.validation.Person, messageTemplate='too young'} ConstraintViolationImpl{ interpolatedMessage='0 is younger than minimum 16', propertyPath=birthDate, rootBeanClass=class ejava.jpa.example.validation.Person, messageTemplate='0 is younger than minimum 16'} ]
Validation is not automatically cascaded (@Valid) across relationships
Validation will occur for related entity during its appropriate lifecycle phases
Groups could be divided into client, service, DAO-insert, DAO-update, DAO-delete
Permits errors to be automatically detected without transaction going into rollback state
Figure 55.1. API Dependency
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
<scope>compile</scope>
</dependency>
Figure 55.2. Implementation Dependency (includes API dependency)
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.0.1.Final</version>
<scope>test</scope>
</dependency>
Figure 55.3. Hibernate Implementation Dependency for @Pattern constraints
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
<scope>test</scope>
</dependency>
Figure 55.4. Add validator to schema generation pluginManagement
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>hibernate3-maven-plugin</artifactId>
<version>${hibernate3-maven-plugin.version}</version>
<extensions>true</extensions>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate3.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
</dependencies>
</plugin>
Table of Contents
At the completion of this topic, the student shall
have an understanding of:
Object/Relational Mapping (ORM) based on the Java Persistence API (JPA)
Ownership (owning and inverse sides)
Relationship mapping strategies
Cardinality (one-to-one, one-to-many, many-to-one, and many-to-many)
Direction (uni-directional, bi-directional)
Realization (foreign key and link table)
Simple types/element collections
Orphan removal
Cascades
Foreign key reuse (primary key as foreign key (insert/update=false) and foreign key as primary key(@MapsId))
be able to:
Be able to map a Java entity class relationships to the database using class annotations and ORM descriptors
Be able to map one-to-one, one-to-many, many-to-one, and many-to-many relationships
Be able form relationships using foreign keys and link tables
Be able define cascades for relationships
Uni-directional
Only one class ("owner") knows of the relationship
Uses the @OneToOne annotation
Defines mapping to database
Uses either @JoinColumn or @JoinTable
Bi-directional
Both classes know of the relationship
Both classes use the @OneToOne annotation
One class is considered the owner and maps relation to the database
Uses either @JoinColumn or @JoinTable
Changes here change the database
One class is considered the inverse and names the other entity's property
@OneToOne(mappedBy="owning-property")
Changes here do *not* change database
Figure 56.3. One-to-One Uni-directional Example Database Schema
create table ORMREL_PERSON ( PERSON_ID bigint generated by default as identity, firstName varchar(255), lastName varchar(255), phone varchar(255), +----- PERSON_PHOTO bigint, | primary key (PERSON_ID) | ) | create table ORMREL_PHOTO ( +-----> PHOTO_ID bigint generated by default as identity, image blob, primary key (PHOTO_ID) ) alter table ORMREL_PERSON add constraint FK14D7C425DCCB1C0D foreign key (PERSON_PHOTO) references ORMREL_PHOTO
Relation realized through a foreign key
Foreign key represented by a separate column
Foreign key from owning entity table references primary key of inverse entity table
Figure 56.4. One-to-One Uni-directional Example Java Mapping
@Entity
@Table(name="ORMREL_PERSON")
public class Person {
@Id @GeneratedValue @Column(name="PERSON_ID")
private long id;
@OneToOne(cascade={
CascadeType.ALL}, //have creates, deletes, etc. cascade to photo
fetch=FetchType.LAZY) //a hint that we don't need this
@JoinColumn(name="PERSON_PHOTO") //define local foreign key column
+----- private Photo photo;
|
| @Entity
| @Table(name="ORMREL_PHOTO")
| public class Photo {
| @Id @GeneratedValue @Column(name="PHOTO_ID")
+----> private long id;
Figure 56.5. One-to-One Uni-directional Usage
//create the owning side
ejava.examples.orm.rel.annotated.Person person = new Person();
person.setFirstName("john");
person.setLastName("doe");
person.setPhone("410-555-1212");
//create the inverse side
ejava.examples.orm.rel.annotated.Photo photo = new Photo();
photo.setImage(image);
//add photo to person and persist object tree
person.setPhoto(photo); //this sets the FK in person
log.info("added photo to person:" + person);
em.persist(person);
assertTrue("personId not set", person.getId() != 0);
assertTrue("photoId not set", photo.getId() != 0);
log.info("created person:" + person);
-added photo to person:Person@1201f5bc, id=0, name=john doe, phone=410-555-1212, photo=Photo@130b5045, id=0. image=46080 bytes Hibernate: insert into ORMREL_PHOTO (PHOTO_ID, image) values (null, ?) Hibernate: insert into ORMREL_PERSON (PERSON_ID, firstName, lastName, phone, PERSON_PHOTO) values (null, ?, ?, ?, ?) -created person:Person@1201f5bc, id=1, name=john doe, phone=410-555-1212, photo=Photo@130b5045, id=1. image=46080 bytes
//verify what we can get from DB
em.flush(); em.clear();
Person person2 = em.find(Person.class, person.getId());
assertNotNull(person2);
assertNotNull(person2.getPhoto());
log.info("found person:" + person2);
Hibernate: select person0_.PERSON_ID as PERSON1_24_0_, person0_.firstName as firstNam2_24_0_, person0_.lastName as lastName3_24_0_, person0_.phone as phone4_24_0_, person0_.PERSON_PHOTO as PERSON5_24_0_ from ORMREL_PERSON person0_ where person0_.PERSON_ID=? Hibernate: select photo0_.PHOTO_ID as PHOTO1_25_0_, photo0_.image as image2_25_0_ from ORMREL_PHOTO photo0_ where photo0_.PHOTO_ID=? -found person:Person@29564bb9, id=1, name=john doe, phone=410-555-1212, photo=Photo@785e7845, id=1. image=46080 bytes
optional:boolean (default=true)
Designates whether relation is required. Default is true.
fetch:FetchType (default=EAGER)
Use EAGER or LAZY fetching of relationship when loading this entity. More of coverage of fetch in Fetching section
orphanRemoval:boolean (default=false)
Remote entity only exists for use by this relation. Automatically delete when relation terminated.
cascade:CascadeType[] (default=none)
Perform actions taken on this entity on related entity
targetEntity:Class
Define type for related class (if related Java type over-generalized)
mappedBy:String
Used by inverse side to specify owning entity property that maps relation to DB
Defines a foreign key mapping
name
Database column name
referencedColumnName (default=primary key)
Primarily used during composite key mappings to signify which property goes with which primary key member
unique (default=false)
Foreign key is unique within entity table
nullable (default=true)
Foreign key is optional within entity table. Used for 0..1 relationships
insertable (default=true)
Foreign key is part of SQL INSERT for entity
updatable (default=true)
Foreign key is part of SQL UPDATEs for the entity
table
Used in multi-table mappings to identify source table for column
columnDefinition
Customized DDL for column definition when generating schema
Used to define multiple @JoinColumns when using composite foreign keys
@OneToOne
@JoinColumns({ //defines an array of @JoinColumns
@JoinColumn(...),
@JoinColumn(...)
})
Navigate relationship from both sides of the Java relationship
Figure 56.6. One-to-One Uni-directional Example Database Schema
create table ORMREL_APPLICANT ( id bigint generated by default as identity, +----- APP_BORROWER bigint, | APP_PERSON bigint not null, | primary key (id) | ) | create table ORMREL_BORROWER ( +----> BORROWER_ID bigint not null, endDate date, startDate date, primary key (BORROWER_ID) ) alter table ORMREL_APPLICANT add constraint FKD1860812DA35F52F foreign key (APP_BORROWER) references ORMREL_BORROWER
No additional foreign key used to satisfy the bi-directional aspect of relation
Figure 56.7. One-to-One Uni-directional Example Database Java Mapping
@Entity @Table(name="ORMREL_APPLICANT")
public class Applicant {
@Id @GeneratedValue
private long id;
+--> @OneToOne(optional=true) //we may exist without Borrower
| @JoinColumn(name="APP_BORROWER")//we own relationship to Borrower
+----- private Borrower borrower;
| |
| | @Entity @Table(name="ORMREL_BORROWER")
| | public class Borrower {
| | @Id @Column(name="BORROWER_ID")
`----> private long id;
|
| @OneToOne(fetch=FetchType.LAZY,
| optional=true, //lets make this optional for demo
| mappedBy="borrower") //the other side owns foreign key column
+-- private Applicant application;
Figure 56.8. One-to-One Uni-directional Example Usage
//locate them from DB
Applicant applicant2 = em.find(Applicant.class, applicant.getId());
Borrower borrower2 = em.find(Borrower.class, borrower.getId());
//form relationship
borrower2.setApplication(applicant2); //set inverse side
applicant2.setBorrower(borrower2); //set owning side
update ORMREL_APPLICANT set APP_BORROWER=?, APP_PERSON=? where id=?
Notice only owning entity table is updated when relationship formed.
Figure 56.9. One-to-One Uni-directional Example Usage (cont.)
//locate them from DB
em.flush(); em.clear();
Applicant applicant3 = em.find(Applicant.class, applicant.getId());
Borrower borrower3 = em.find(Borrower.class, borrower.getId());
assertEquals(applicant.getId(), borrower3.getApplication().getId());
assertEquals(borrower.getId(), applicant3.getBorrower().getId());
Hibernate: select applicant0_.id as id1_12_2_, applicant0_.APP_BORROWER as APP2_12_2_, applicant0_.APP_PERSON as APP3_12_2_, borrower1_.BORROWER_ID as BORROWER1_15_0_, borrower1_.endDate as endDate2_15_0_, borrower1_.startDate as startDat3_15_0_, person2_.PERSON_ID as PERSON1_24_1_, ... from ORMREL_APPLICANT applicant0_ left outer join ORMREL_BORROWER borrower1_ on applicant0_.APP_BORROWER=borrower1_.BORROWER_ID inner join ORMREL_PERSON person2_ on applicant0_.APP_PERSON=person2_.PERSON_ID where applicant0_.id=? Hibernate: select applicant0_.id as id1_12_2_, applicant0_.APP_BORROWER as APP2_12_2_, applicant0_.APP_PERSON as APP3_12_2_, borrower1_.BORROWER_ID as BORROWER1_15_0_, borrower1_.endDate as endDate2_15_0_, borrower1_.startDate as startDat3_15_0_, person2_.PERSON_ID as PERSON1_24_1_, ... from ORMREL_APPLICANT applicant0_ left outer join ORMREL_BORROWER borrower1_ on applicant0_.APP_BORROWER=borrower1_.BORROWER_ID inner join ORMREL_PERSON person2_ on applicant0_.APP_PERSON=person2_.PERSON_ID where applicant0_.APP_BORROWER=? -
Notice the extra joins that occur with default fetch mode=EAGER
inner joins used for optional=false relationships
left outer joins used for optional=true relationships
Only changes made to owning side impact database
persist (set)
update (change)
remove (null out)
Actions taken on owning side not automatically propagated to inverse side
Inverse reference to owning side not changed (for persist, update, or remove)
Must either
Manually update inverse side
Refresh object if proper state in database
Figure 56.10. Update Inverse-side Only Example (Setup)
Borrower borrower = em.find(Borrower.class, borrowerId);
Applicant applicant = em.find(Applicant.class, applicantId);
assertNull("borrower has unexpected applicant:" +
borrower.getApplication(),
borrower.getApplication());
assertNull("applicant has unexpected borrower:" +
applicant.getBorrower(),
applicant.getBorrower());
//set ONLY the inverse side of the relationship
borrower.setApplication(applicant);
assertNotNull("borrower does not have applicant",
borrower.getApplication());
assertNull("applicant has unexpected borrower:" +
applicant.getBorrower(),
applicant.getBorrower());
log.info("writing rel owner (application) to DB:" + applicant);
log.info("writing rel inverse (borrower) to DB:" + borrower);
Only inverse side being set | |
Setter does not automatically propagate to other side in cache |
Figure 56.11. Update Inverse-side Only Example (Verify)
//commit changes to the DB, but since only inserse side of relationship
//was set, no FK data gets written
em.getTransaction().commit(); em.clear();
assertFalse("borrower was managed", em.contains(borrower));
assertFalse("application was managed", em.contains(applicant));
borrower = em.find(Borrower.class, borrowerId);
applicant = em.find(Applicant.class, applicantId);
//verify that relationship from cache never written to DB
assertNull("borrower has unexpected applicant:" +
borrower.getApplication(),
borrower.getApplication());
assertNull("applicant has unexpected borrower:" +
applicant.getBorrower(),
applicant.getBorrower());
Not setting owning side caused relationship to *not* be written to database -- thus lost when read back in from database |
Figure 56.12. Update Owning-side Only Example (Setup)
Borrower borrower = em.find(Borrower.class, borrowerId);
Applicant applicant = em.find(Applicant.class, applicantId);
assertNull("borrower has unexpected applicant:" +
borrower.getApplication(),
borrower.getApplication());
assertNull("applicant has unexpected borrower:" +
applicant.getBorrower(),
applicant.getBorrower());
//set ONLY the owning side of the relationship
applicant.setBorrower(borrower);
assertNull("borrower has unexpected applicant:" +
borrower.getApplication(),
borrower.getApplication());
assertNotNull("applicant does not have borrower",
applicant.getBorrower());
Updating only the owning side | |
Change does *not* automatically propagate to other side |
Figure 56.13. Update Owning-side Only Example (Verify)
//commit changes to the DB, since the owning side was set, we do
//get changes made to DB
em.getTransaction().commit(); em.clear();
borrower = em.find(Borrower.class, borrowerId);
applicant = em.find(Applicant.class, applicantId);
//verify that relationship from cache written to DB
assertNotNull("borrower was not updated with applicant:" +
borrower.getApplication(),
borrower.getApplication());
assertNotNull("applicant was not updated with borrower",
applicant.getBorrower());
Relationship retained because owning side written to database |
Primary Key Join
No separate foreign key column
Primary key column used to also represent the foreign key
Discussed in Foreign Key section
Link Table
Neither entity class table directly references to other
Separate table used to realize relationship
Discussed in Join Tables section
Represents two classes that reference the other using a Java reference
Relationship realized through either a foreign key or join table
May be represented at the entity level as either a uni-directional or bi-directional relationship
Only the owning side is mapped to the database
Inverse side is populated from database but does not change the database
Uni-directional
Only one side ("owner") knows of the relationship
Uses the @OneToMany annotation
Defines mapping to database
Uses either @JoinColumn or @JoinTable
@JoinTable adds the foreign key to the child table and not to the owning entity class table in this uni-directional case
Bi-directional
Both classes know of the relationship
Many side required to be owning side and maps relation to the database
Uses the @ManyToOne annotation
Uses either @JoinColumn or @JoinTable
Changes here change the database
One side required to be inverse and names the other entity's property
@OneToMany(mappedBy="owning-property")
Changes here do *not* change database
This example uses the @JoinColumn technique of inserting foreign key into child table of inverse entity class
No construct in child/inverse side to map to foreign key
Figure 57.3. One-to-Many Uni-directional Database Schema
create table ORMREL_INVENTORY ( /----> id bigint generated by default as identity, | name varchar(255), | primary key (id) | ) | create table ORMREL_MEDIA ( | MEDIA_ID bigint generated by default as identity, | title varchar(255), `----- INVENTORY_ID bigint, primary key (MEDIA_ID) ) alter table ORMREL_MEDIA add constraint FK639A68F4BCF517CD foreign key (INVENTORY_ID) references ORMREL_INVENTORY
Figure 57.4. One-to-Many Uni-directional Database Java Mapping
@Entity @Table(name="ORMREL_INVENTORY")
public class Inventory {
@Id @GeneratedValue
private long id;
private String name;
@OneToMany(cascade={CascadeType.ALL})
/----- @JoinColumn(name="INVENTORY_ID")
| private Collection<Media> media = new ArrayList<Media>();
|
| @Entity @Table(name="ORMREL_MEDIA")
| public class Media {
| @Id @GeneratedValue @Column(name="MEDIA_ID")
`----> private long id;
Figure 57.5. One-to-Many Uni-directional Database Usage
ejava.examples.orm.rel.annotated.Inventory inventory = new Inventory();
inventory.setName("testLinkCreate");
em.persist(inventory);
for(int i=0; i<5; i++) {
ejava.examples.orm.rel.annotated.Media media = new Media();
em.persist(media);
log.info("created media:" + media);
inventory.getMedia().add(media);
}
log.info("created inventory:" + inventory);
Relationship formed when inverse side added to owning collection
Figure 57.6. One-to-Many Uni-directional Database Usage
Hibernate: insert into ORMREL_INVENTORY (id, name) values (null, ?) Hibernate: insert into ORMREL_MEDIA (MEDIA_ID, title) values (null, ?) -created media:ejava.examples.orm.rel.annotated.Media@5ae9fa73, id=1, title=null, authors(0)={} ... Hibernate: insert into ORMREL_MEDIA (MEDIA_ID, title) values (null, ?) -created media:Media@433a3459, id=5, title=null, authors(0)={} -created inventory:Inventory@5b9e6638, id=1, name=testLinkCreate, media(5)={1,2,3,4,5,} Hibernate: update ORMREL_MEDIA set INVENTORY_ID=? where MEDIA_ID=? ... Hibernate: update ORMREL_MEDIA set INVENTORY_ID=? where MEDIA_ID=?
The foreign key is in the inverse entity class table
No construct in inverse class maps to this foreign key column
Identical properties to @OneToOne annotation with the exception of no "optional" property
Figure 57.7. One-to-Many Bi-directional Example Database Schema
create table ORMREL_BORROWER ( +----> BORROWER_ID bigint not null, | endDate date, | startDate date, | primary key (BORROWER_ID) | ) | create table ORMREL_CHECKOUT ( | CHECKOUT_ID bigint generated by default as identity, | outDate date, | returnDate date, `----- CHECKOUT_BID bigint not null, primary key (CHECKOUT_ID) ) alter table ORMREL_CHECKOUT add constraint FK7F287E16C07B41F3 foreign key (CHECKOUT_BID) references ORMREL_BORROWER
Figure 57.8. One-to-Many Bi-directional Example Java Mapping
@Entity @Table(name="ORMREL_CHECKOUT")
public class Checkout {
@Id @GeneratedValue @Column(name="CHECKOUT_ID")
private long id;
+--> @ManyToOne(optional=false)
| @JoinColumn(name="CHECKOUT_BID")
+----- private Borrower borrower;
| |
| | @Entity @Table(name="ORMREL_BORROWER")
| | public class Borrower {
| | private static Log log = LogFactory.getLog(Borrower.class);
| | @Id @Column(name="BORROWER_ID")
`----> private long id;
|
| @OneToMany(mappedBy="borrower", //this relationship is owned by Checkout
| fetch=FetchType.LAZY) //try to limit what we get back
`--- private Collection<Checkout> checkouts = new ArrayList<Checkout>();
Figure 57.9. One-to-Many Bi-directional Example Usage (Create)
//get a borrower
Borrower borrower = em.find(Borrower.class, borrowerId);
assertNotNull(borrower);
assertTrue(borrower.getCheckouts().size() == 0);
//create 1st checkout
Checkout checkout = new Checkout(new Date());
checkout.setBorrower(borrower); //set owning side of the relation
borrower.addCheckout(checkout); //set inverse side of relation
//wrapper around - borower.getCheckouts().add(checkout)
em.persist(checkout); //persist owning side of the relation
Hibernate: -- query for the parent object, lazily loads child objects select borrower0_.BORROWER_ID as BORROWER1_15_0_, borrower0_.endDate as endDate2_15_0_, borrower0_.startDate as startDat3_15_0_ from ORMREL_BORROWER borrower0_ where borrower0_.BORROWER_ID=?
//get a borrower
//create a couple more
for(int i=0; i<5; i++) {
Checkout co = new Checkout(new Date());
co.setBorrower(borrower); //set owning side of the relation
borrower.addCheckout(co); //set inverse side of relation
em.persist(co); //persist owning side of the relation
}
Hibernate: insert into ORMREL_CHECKOUT (CHECKOUT_ID, CHECKOUT_BID, outDate, returnDate) values (null, ?, ?, ?)
Figure 57.10. One-to-Many Bi-directional Example Usage (Verify)
//check the DB
em.flush(); em.clear();
Borrower borrower2 = em.find(Borrower.class, borrower.getId());
assertEquals(6, borrower2.getCheckouts().size());
Maps collection of simple data types to child table
Uses orphanRemoval for child values
Figure 57.11. Element Collection Example Database Schema
create table RELATIONEX_SUSPECT ( +----> id integer generated by default as identity, | name varchar(32), | primary key (id) | ) | create table RELATIONEX_SUSPECT_ALIASES ( `----- SUSPECT_ID integer not null, ALIAS varchar(32), unique (SUSPECT_ID, ALIAS) ) alter table RELATIONEX_SUSPECT_ALIASES add constraint FK3FD160E6DE29C9CF foreign key (SUSPECT_ID) references RELATIONEX_SUSPECT
Figure 57.12. Element Collection Example Database Java Mapping
@Entity
@Table(name="RELATIONEX_SUSPECT")
public class Suspect {
@Id @GeneratedValue
private int id;
@Column(length=32)
private String name;
@ElementCollection
@CollectionTable(
name="RELATIONEX_SUSPECT_ALIASES",
joinColumns=@JoinColumn(name="SUSPECT_ID"),
uniqueConstraints=@UniqueConstraint(columnNames={"SUSPECT_ID", "ALIAS"}))
@Column(name="ALIAS", length=32)
private Set<String> aliases;
Figure 57.13. Element Collection Example Database Usage
Suspect suspect = new Suspect(); suspect.setName("william"); em.persist(suspect); suspect.getAliases().add("bill"); suspect.getAliases().add("billy");
Link Table
Foreign key not placed in child table
Separate table used to realize relationship
Discussed in Join Tables section
Determines how when related objects are retrieved
LAZY
Only parent object immediately retrieved -- child objects retrieved on demand
Can result in better performance when child data is not needed - unused objects not retrieved
Can result in poor performance when all child data is needed - retrieves objects one at a time rather than in bulk
Can result in lazy load exception if unloaded data accessed after it can no longer be retrieved from database
Can be simulated, on demand, using value or result class queries with JPA-QL
EAGER (the default)
Provider required to have loaded prior to transaction committing
Can result in better performance when all child data is needed
Can result in poor performance when no child data or limited child data is needed
Can result in poor performance when parent has multiple collections and equivalent of an "EAGER LAZY load" is performed before returning control back to the caller
Should never result in a lazy load exception
Can be simulated, on-demand, using "join fetch" queries with JPA-QL
Figure 58.1. fetch=LAZY Example Declaration
@OneToMany(mappedBy="borrower", //this relationship is owned by Checkout
fetch=FetchType.LAZY)
private Collection<Checkout> checkouts = new ArrayList<Checkout>();
@XxxToXxx Relationship annotated with fetch=LAZY property
Figure 58.2. fetch=LAZY Example Use
Borrower borrower2 = em.find(Borrower.class, borrower.getId());
log.info("found borrower: " + borrower.getName());
assertEquals(6, borrower2.getCheckouts().size());
Parent accessed, debug printed, and then child collection accessed
Figure 58.3. fetch=LAZY SQL Output
Hibernate: select applicant0_.id as id1_12_2_, applicant0_.APP_BORROWER as APP2_12_2_, applicant0_.APP_PERSON as APP3_12_2_, borrower1_.BORROWER_ID as BORROWER1_15_0_, borrower1_.endDate as endDate2_15_0_, borrower1_.startDate as startDat3_15_0_, person2_.PERSON_ID as PERSON1_24_1_, ... person2_.PERSON_PHOTO as PERSON5_24_1_ from ORMREL_APPLICANT applicant0_ left outer join ORMREL_BORROWER borrower1_ on applicant0_.APP_BORROWER=borrower1_.BORROWER_ID inner join ORMREL_PERSON person2_ on applicant0_.APP_PERSON=person2_.PERSON_ID where applicant0_.APP_BORROWER=? -found borrower: john smith Hibernate: select checkouts0_.CHECKOUT_BID as CHECKOUT4_15_1_, checkouts0_.CHECKOUT_ID as CHECKOUT1_16_1_, checkouts0_.CHECKOUT_ID as CHECKOUT1_16_0_, checkouts0_.CHECKOUT_BID as CHECKOUT4_16_0_, checkouts0_.outDate as outDate2_16_0_, checkouts0_.returnDate as returnDa3_16_0_ from ORMREL_CHECKOUT checkouts0_ where checkouts0_.CHECKOUT_BID=?
Debug for parent printed before child rows retrieved
Child rows retrieved when parent collection accessed
Figure 58.4. fetch=EAGER Example Declaration
@OneToMany(mappedBy="borrower", //this relationship is owned by Checkout
fetch=FetchType.EAGER)
private Collection<Checkout> checkouts = new ArrayList<Checkout>();
Figure 58.5. fetch=EAGER Example Use
Borrower borrower3 = em.find(Borrower.class, borrowerId);
log.info("found borrower: " + borrower.getName());
assertEquals(0,borrower3.getCheckouts().size());
Same as fetch=LAZY case
Figure 58.6. fetch=EAGER SQL Output
Hibernate: select applicant0_.id as id1_12_3_, applicant0_.APP_BORROWER as APP2_12_3_, applicant0_.APP_PERSON as APP3_12_3_, borrower1_.BORROWER_ID as BORROWER1_15_0_, borrower1_.endDate as endDate2_15_0_, borrower1_.startDate as startDat3_15_0_, checkouts2_.CHECKOUT_BID as CHECKOUT4_15_5_, checkouts2_.CHECKOUT_ID as CHECKOUT1_16_5_, checkouts2_.CHECKOUT_ID as CHECKOUT1_16_1_, checkouts2_.CHECKOUT_BID as CHECKOUT4_16_1_, checkouts2_.outDate as outDate2_16_1_, checkouts2_.returnDate as returnDa3_16_1_, person3_.PERSON_ID as PERSON1_24_2_, ... person3_.PERSON_PHOTO as PERSON5_24_2_ from ORMREL_APPLICANT applicant0_ left outer join ORMREL_BORROWER borrower1_ on applicant0_.APP_BORROWER=borrower1_.BORROWER_ID left outer join ORMREL_CHECKOUT checkouts2_ on borrower1_.BORROWER_ID=checkouts2_.CHECKOUT_BID inner join ORMREL_PERSON person3_ on applicant0_.APP_PERSON=person3_.PERSON_ID where applicant0_.APP_BORROWER=? -found borrower: john smith
Child objects fetched with parent
Access to child collection occurs after all children fetched
Automatically cause persistence commands to be repeated on related objects
Figure 58.7.
@Entity
@Table(name="RELATIONEX_LICAPP")
public class LicenseApplication {
@Id @GeneratedValue
private int id;
@Temporal(TemporalType.TIMESTAMP)
private Date updated;
@OneToOne(optional=false, fetch=FetchType.EAGER,
cascade={
CascadeType.PERSIST,
CascadeType.DETACH,
CascadeType.REMOVE,
CascadeType.REFRESH,
CascadeType.MERGE
})
private License license;
PERSIST
Related entities persisted when this entity is passed to em.persist()
DETACH
Related entities detached from persistence unit when this entity passed to em.detach()
REMOVE
Related entities deleted from database when this entity passed to em.remove()
REFRESH
Related entities refreshed with state of database when this entity passed to em.refresh()
MERGE
Related entities update state of database when this entity is passed to em.merge()
Automatic removal of an object who's sole purpose is to support a related object that may dereference it
Related to cascade=REMOVE but the triggering object is not being deleted
Supported in the following relationships
@OneToOne
@OneToMany
Figure 58.8. OrphanRemoval Example Declaration
@Entity
@Table(name="RELATIONEX_ATTENDEE")
public class Attendee {
@Id @GeneratedValue
private int id;
//orphanRemoval will take care of dereference and DELETE from dependent Attendee
@OneToOne(cascade=CascadeType.PERSIST, orphanRemoval=true)
private Residence residence;
Figure 58.10. OrphanRemoval Database Interaction
Hibernate: update RELATIONEX_ATTENDEE set name=?, residence_id=? where id=? Hibernate: delete from RELATIONEX_RESIDENCE where id=?
Attendee.residence_id set to null
Orphaned residence deleted
Use fetch=EAGER when always accessing related objects together
Use fetch=LAZY when commonly access one object without accessing related objects
Use JPA-QL when encountering corner cases that violate default mapping
Cascades can be used to automate persistence actions on an entire object graph
OrphanRemoval can used to automatically delete dereferenced objects that have no use outside the scope of its owning relation
hashCode()
Returns an int value to be used by hashtables to help store and locate
Must remain constant *unless* state that is used to derive value changes
Two objects resulting in equals(obj)=true must return same hashCode
Two objects resulting in same hashCode may result in equals(obj)=false - but may lead to inneficient access
equals()
Returns the boolean result of whether two objects are "equal"
Reflexive - this should always equal this
Symmetric - if x==y, then y==x
Transitive - if x==y and y==z, then x==z
Consistent - if x==y, then x will always equal y (unless state used changes)
Uses object instance to determine identity
Two instances with identical state will not be equal
Default implementation
Works for cases where you will only have a single instance representing a single object
Figure 59.1. Using Default Identity Methods
public class java.lang.Object {
...
public native int hashCode();
public boolean equals(java.lang.Object);
...
}
Use assigned database primary key as identity
Positives: unique value within table or possibly database
Negatives: automatically generated values not available until object persisted
Only an issue for auto-generated primary keys
Figure 59.2. Using Database Primary Key For Identity
@Entity
public abstract class Ship {
@Id
@GeneratedValue
protected int id;
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
try {
if (obj == null) { return false; }
if (this == obj) { return true; }
return id==((Ship)obj).id;
} catch (Exception ex) { return false; }
}
Using instanceId until database primary key assigned
Positives: able to compare objects in same persistent stage
Negatives:
Persisting object will break consistency rule (except that state really did change)
Cannot compare transient instances to persisted instances
Identity-based collections can get confused by inconsistent behavior
Figure 59.3. Switching Identity Schemes
@Override
public int hashCode() {
return id==0 ? super.hashCode() : id;
}
@Override
public boolean equals(Object obj) {
try {
if (obj == null) return false;
if (this == obj) { return true; }
return (id==0) ? super.equals(obj) : id==((Ship)obj).id;
} catch (Exception ex) { return false; }
}
Use unique business values to determine identity -- independent of database primary key
Positives: consistent state through persistence lifecycle of object
Negatives: may have trouble identifying unique properties within each object type
Figure 59.4. Switching Identity Schemes
@Override
public int hashCode() {
return (name==null ? 0 : name.hashCode()) +
(created==null ? 0 : (int)created.getTime());
}
@Override
public boolean equals(Object obj) {
try {
if (obj == null) { return false; }
if (this == obj) { return true; }
return name.equals(((ShipByBusinessId)obj).name) &&
created.getTime() == (((ShipByBusinessId)obj).created.getTime());
} catch (Exception ex) { return false; }
}
Collection
Bag, no specific collection ordering. Duplicates allowed.
List
Duplicates allowed. Order maintained by database using @SortKey("property ASC/DESC")
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER) @JoinColumn @OrderBy("number ASC") private List<Segment> segments;
Set
Unique value (object identity comes into play here). No specific order.
Map
Each object registered in collection with a property key using @MapKey("property")
@OneToMany @MapKey(name="position") @JoinColumn(name="LINEUP_ID") private Map<String, Position> positions;
Special case of one-to-one mapping
Uses primary key as the foreign key
No additional column for foreign key
Primary keys must match
Figure 60.2. Primary Key Join Example Database Schema
create table ORMREL_BORROWER ( +----- BORROWER_ID bigint not null, | endDate date, | startDate date, | primary key (BORROWER_ID) | ) | create table ORMREL_PERSON ( +----> PERSON_ID bigint generated by default as identity, firstName varchar(255), lastName varchar(255), phone varchar(255), PERSON_PHOTO bigint, primary key (PERSON_ID) ) alter table ORMREL_BORROWER add constraint FKA0973E32F113D9BA foreign key (BORROWER_ID) references ORMREL_PERSON
Figure 60.3. Primary Key Join Example Java Mapping
@Entity @Table(name="ORMREL_BORROWER")
public class Borrower {
@Id @Column(name="BORROWER_ID")
+----- private long id;
|
| @OneToOne(fetch=FetchType.LAZY, optional=false,
| cascade={CascadeType.PERSIST,
| CascadeType.REFRESH,
| CascadeType.MERGE})
| @PrimaryKeyJoinColumn //the two tables will be joined by PKs
+----- private Person identity;
|
| @Entity
| @Table(name="ORMREL_PERSON")
| public class Person {
| @Id @GeneratedValue @Column(name="PERSON_ID")
+----> private long id;
public Borrower(Person identity) {
this.id = identity.getId();
this.identity = identity;
}
In this case, the primary key is used as the foreign key and must be set. To use the foreign key as the primary key -- use @MapsId
//@PrimaryKeyJoinColumn //the two tables will be joined by PKs @MapsId private Person identity; public Borrower(Person identity) { this.identity = identity; }
Figure 60.4. Primary Key Join Example Usage
//create the person we'll use in the relationship
ejava.examples.orm.rel.annotated.Person person = new Person();
...
//create the Borrower, who requires a Person for its identity
ejava.examples.orm.rel.annotated.Borrower borrower = new Borrower(person);
borrower.setStartDate(new Date());
//persist the borrower, creating the relationship to person
em.persist(borrower);
log.info("created borrower:" + borrower);
assertEquals(person.getId(), borrower.getId()); //ctor copies PK
Signals primary key column used as foreign key -- no separate foreign key column
name (default=primary key column of this entity)
Names column in this entity's table this property maps to when using composite keys
referencedColumnName (default=primary key column of joined entity)
Names column in joined entity table this property maps to when using composite keys
columnDefinition
Custom DDL definition for column when generating database schema
Child object on many side, using a composite primary key may reference parent with a property from that key
Primary keys cannot be changed
If primary key used -- database manipulation for foreign keys reasons must be turned off
Primary key used to represent foreign key
Primary key must be known -- cannot be derived from generated value
Figure 60.5. Composite @IdClass Property Reused for Foreign Key
@Entity @Table(name="ORMREL_ROOM")
@IdClass(RoomPK.class)
@AttributeOverrides({
@AttributeOverride(name = "houseId", column=@Column(name="HOUSE_ID")),
@AttributeOverride(name = "roomId", column=@Column(name="ROOM_ID"))
})
public class Room {
@Id
private int houseId;
@Id
private int roomId;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
//assign join column to primary key value and turn off inserts/updates here
@JoinColumn(name="HOUSE_ID", insertable=false, updatable=false)
private House house;
public Room() {}
public Room(House house, int roomId) {
this.houseId=house.getId();
this.house=house;
this.roomId=roomId;
}
Setting primary key prior to persisting
Foreign key used twice -- once for relation and once for primary key
Figure 60.6. Referenced Parent Object -- Source of Foreign Key/Primary Key Value
@Entity @Table(name="ORMREL_HOUSE")
public class House {
@Id @GeneratedValue
private int id;
@OneToMany(cascade={CascadeType.PERSIST, CascadeType.REMOVE},
fetch=FetchType.LAZY, mappedBy="house")
private Collection<Room> rooms=new ArrayList<Room>();
Figure 60.7. Composite @IdClass
public class RoomPK implements Serializable {
@Column(name="PK_HOUSE_ID") //overridden
private int houseId;
@Column(name="PK_ROOM_ID") //overridden
private int roomId;
Figure 60.8. Database Schema
create table ORMREL_HOUSE ( id integer generated by default as identity, primary key (id) ) create table ORMREL_ROOM ( HOUSE_ID integer not null, ROOM_ID integer not null, primary key (HOUSE_ID, ROOM_ID) ) alter table ORMREL_ROOM add constraint FKD9EEA1ABC0069D7C foreign key (HOUSE_ID) references ORMREL_HOUSE
HOUSE_ID is both primary and foreign key for ROOM
Figure 60.9. Example Use
House house = new House();
em.persist(house); //generate a PK for parent
house.getRooms().add(new Room(house,0));
house.getRooms().add(new Room(house,1));
house.getRooms().add(new Room(house,2));
em.persist(house); //cascade persists to children
//get a new copy of house
em.flush(); em.clear();
House house2 = em.find(House.class, house.getId());
Figure 60.10. Example Output
Hibernate: insert into ORMREL_HOUSE (id) values (null) Hibernate: insert into ORMREL_ROOM (HOUSE_ID, ROOM_ID) values (?, ?) ... Hibernate: select house0_.id as id1_18_0_ from ORMREL_HOUSE house0_ where house0_.id=? Hibernate: select rooms0_.HOUSE_ID as HOUSE1_18_1_, rooms0_.HOUSE_ID as HOUSE1_30_1_, rooms0_.ROOM_ID as ROOM2_30_1_, rooms0_.HOUSE_ID as HOUSE1_30_0_, rooms0_.ROOM_ID as ROOM2_30_0_ from ORMREL_ROOM rooms0_ where rooms0_.HOUSE_ID=?
Primary key used to represent foreign key
Primary key must be known -- cannot be derived from generated value
Figure 60.11. Composite @EmbeddedId Property Reused for Foreign Key
@Entity @Table(name="ORMREL_DOOR")
public class Door {
@EmbeddedId
@AttributeOverrides({
@AttributeOverride(name="houseId", column=@Column(name="HOUSE_ID")),
@AttributeOverride(name="doorId", column=@Column(name="DOOR_ID"))
})
private DoorPK pk;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
//assign join column to primary key value and turn off inserts/updates here
@JoinColumn(name="HOUSE_ID", insertable=false, updatable=false)
private House house;
public Door() {}
public Door(House house, int doorId) {
pk=new DoorPK(house.getId(), doorId);
this.house=house;
}
Figure 60.12. Composite @Embeddable Class
@Embeddable
public class DoorPK implements Serializable {
@Column(name="PK_HOUSE_ID") //overridden
private int houseId;
@Column(name="PK_DOOR_ID") //overridden
private int doorId;
Figure 60.13. Database Schema
create table ORMREL_DOOR ( DOOR_ID integer not null, HOUSE_ID integer not null, primary key (DOOR_ID, HOUSE_ID) ) alter table ORMREL_DOOR add constraint FKD9E8447EC0069D7C foreign key (HOUSE_ID) references ORMREL_HOUSE
Figure 60.14. Example Use
House house = new House();
em.persist(house); //generate a PK for parent
house.getDoors().add(new Door(house,0));
house.getDoors().add(new Door(house,1));
house.getDoors().add(new Door(house,2));
em.persist(house); //cascade persists to children
//get a new copy of house
em.flush(); em.clear();
House house2 = em.find(House.class, house.getId());
Figure 60.15. Example Output
Hibernate: insert into ORMREL_HOUSE (id) values (null) Hibernate: insert into ORMREL_DOOR (DOOR_ID, HOUSE_ID) values (?, ?) ... Hibernate: select house0_.id as id1_18_0_ from ORMREL_HOUSE house0_ where house0_.id=? Hibernate: select doors0_.HOUSE_ID as HOUSE2_18_1_, doors0_.DOOR_ID as DOOR1_17_1_, doors0_.HOUSE_ID as HOUSE2_17_1_, doors0_.DOOR_ID as DOOR1_17_0_, doors0_.HOUSE_ID as HOUSE2_17_0_ from ORMREL_DOOR doors0_ where doors0_.HOUSE_ID=?
Primary key value is derived from foreign key value
Value can be dynamically generated
Figure 60.16. Composite @IdClass Property Derived from Foreign Key
@Entity @Table(name="ORMREL_RESIDENT")
@IdClass(ResidentPK.class)
@AttributeOverrides({
@AttributeOverride(name = "residentId", column=@Column(name="RESIDENT_ID"))
})
public class Resident {
@Id
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="HOUSE_ID", nullable=false)
private House house;
@Id
private int residentId;
public Resident() {}
public Resident(House house, int residentId) {
this.house=house;
this.residentId=residentId;
}
No longer modeling separate primary key - derived from foreign key
Figure 60.17. Composite @IdClass Class
public class ResidentPK implements Serializable {
private int house;
private int residentId;
Figure 60.18. Database Schema
create table ORMREL_RESIDENT ( HOUSE_ID integer not null, RESIDENT_ID integer not null, primary key (HOUSE_ID, RESIDENT_ID) ) alter table ORMREL_RESIDENT add constraint FKEDC7E20C0069D7C foreign key (HOUSE_ID) references ORMREL_HOUSE
Figure 60.19. Example Use
House house = new House();
em.persist(house); //generate a PK for parent
house.getResidents().add(new Resident(house,0));
house.getResidents().add(new Resident(house,1));
house.getResidents().add(new Resident(house,2));
em.persist(house); //cascade persists to children
//get a new copy of house
em.flush(); em.clear();
House house2 = em.find(House.class, house.getId());
Figure 60.20. Example Output
Hibernate: insert into ORMREL_HOUSE (id) values (null) Hibernate: insert into ORMREL_RESIDENT (HOUSE_ID, RESIDENT_ID) values (?, ?) ... Hibernate: select house0_.id as id1_18_0_ from ORMREL_HOUSE house0_ where house0_.id=? Hibernate: select residents0_.HOUSE_ID as HOUSE1_18_1_, residents0_.HOUSE_ID as HOUSE1_29_1_, residents0_.RESIDENT_ID as RESIDENT2_29_1_, residents0_.HOUSE_ID as HOUSE1_29_0_, residents0_.RESIDENT_ID as RESIDENT2_29_0_ from ORMREL_RESIDENT residents0_ where residents0_.HOUSE_ID=?
Figure 60.21. Composite @EmbeddedId Property Derived from Foreign Key
@Entity @Table(name="ORMREL_MORTGAGE")
public class Mortgage {
@EmbeddedId
@AttributeOverrides({
@AttributeOverride(name="mortgageId", column=@Column(name="MORTGAGE_ID"))
})
private MortgagePK pk;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="HOUSE_ID", nullable=false)
@MapsId("houseId")
private House house;
public Mortgage() {}
public Mortgage(House house, int mortgageId) {
pk=new MortgagePK(house.getId(), mortgageId);
this.house=house;
}
Figure 60.22. Composite @Embeddable Class
@Embeddable
public class MortgagePK implements Serializable {
@Column(name="PK_HOUSE_ID") //overridden
private int houseId;
@Column(name="PK_MORTGAGE_ID") //overridden
private int mortgageId;
Figure 60.23. Database Schema
create table ORMREL_MORTGAGE ( HOUSE_ID integer not null, MORTGAGE_ID integer not null, primary key (HOUSE_ID, MORTGAGE_ID) ) alter table ORMREL_MORTGAGE add constraint FK175C656CC0069D7C foreign key (HOUSE_ID) references ORMREL_HOUSE
Figure 60.24. Example Use
House house = new House();
em.persist(house); //generate a PK for parent
house.getMortgages().add(new Mortgage(house,0));
house.getMortgages().add(new Mortgage(house,1));
house.getMortgages().add(new Mortgage(house,2));
em.persist(house); //cascade persists to children
//get a new copy of house
em.flush(); em.clear();
House house2 = em.find(House.class, house.getId());
Figure 60.25. Example Output
Hibernate: insert into ORMREL_HOUSE (id) values (null) Hibernate: insert into ORMREL_MORTGAGE (HOUSE_ID, MORTGAGE_ID) values (?, ?) ... Hibernate: select house0_.id as id1_18_0_ from ORMREL_HOUSE house0_ where house0_.id=? Hibernate: select mortgages0_.HOUSE_ID as HOUSE1_18_1_, mortgages0_.HOUSE_ID as HOUSE1_26_1_, mortgages0_.MORTGAGE_ID as MORTGAGE2_26_1_, mortgages0_.HOUSE_ID as HOUSE1_26_0_, mortgages0_.MORTGAGE_ID as MORTGAGE2_26_0_ from ORMREL_MORTGAGE mortgages0_ where mortgages0_.HOUSE_ID=?
Separate table created to hold foreign keys
Can be used for all relationship enumatations and directions
Figure 60.27. Join/Link Table Database Table Schema
create table ORMREL_INVENTORY (
/----> id bigint generated by default as identity,
| name varchar(255),
| primary key (id)
| )
| create table ORMREL_INVENTORY_MEDIA (
`----- ORMREL_INVENTORY_id bigint not null,
/----- media_MEDIA_ID bigint not null
| )
| create table ORMREL_MEDIA (
`----> MEDIA_ID bigint generated by default as identity,
title varchar(255),
primary key (MEDIA_ID)
)
Join table name either derived from associated tables or explicitly named
Join table columns either derived from referenced table column or explicitly named
Figure 60.28. Join/Link Table Database Constraint Schema
alter table ORMREL_INVENTORY_MEDIA add constraint UK_F6FA5C31B7DAA951 unique (media_MEDIA_ID) alter table ORMREL_INVENTORY_MEDIA add constraint FKF6FA5C31A70D4E48 foreign key (media_MEDIA_ID) references ORMREL_MEDIA alter table ORMREL_INVENTORY_MEDIA add constraint FKF6FA5C317DD5E49D foreign key (ORMREL_INVENTORY_id) references ORMREL_INVENTORY
Unique constraint enforces the (1)-to-Many aspect of relationship
Figure 60.29. Join/Link Table Database Java Mapping
@Entity @Table(name="ORMREL_INVENTORY")
public class Inventory {
@Id @GeneratedValue
private long id;
private String name;
@OneToMany(cascade={CascadeType.ALL})
@JoinTable(name="ORMREL_INVENTORY_MEDIA")
/----- private Collection<Media> media = new ArrayList<Media>();
|
| @Entity @Table(name="ORMREL_MEDIA")
| public class Media {
| @Id @GeneratedValue @Column(name="MEDIA_ID")
`----> private long id;
name
Database table name for join table
catalog
Database catalog for join table
schema
Database schema for join table
joinColumns
List of @JoinColumn definitions from join table back to owning entity class table
inverseJoinColumns
List of @JoinColumn definitions from join table to inverse entity class table
uniqueConstraints
Uniqueness constraints to be added to join table
Uni-directional
Only one side ("owner") knows of the relationship
Uses the @ManyToOne annotation
Defines mapping to database
Uses either @JoinColumn or @JoinTable
Bi-directional
Same as One-to-Many Bi-directional
Example uses composite key and derives primary key from foreign key
Foreign key auto-generated
Figure 61.2. Many-to-One Uni-directional Database Schema
create table ORMREL_MEDIA ( +--------> MEDIA_ID bigint generated by default as identity, | title varchar(255), | primary key (MEDIA_ID) | ) | create table ORMREL_MEDIACOPY ( | COPY_NO integer not null, `--------- MEDIACOPY_MID bigint not null, primary key (COPY_NO, MEDIACOPY_MID) ) alter table ORMREL_MEDIACOPY add constraint FKCDB47669F152B359 foreign key (MEDIACOPY_MID) references ORMREL_MEDIA
Figure 61.3. Many-to-One Uni-directional Database Java Mapping
@Entity @Table(name="ORMREL_MEDIACOPY2")
@IdClass(MediaCopyPK2.class)
@AttributeOverrides({
@AttributeOverride(name="copyNo", column=@Column(name="COPY_NO"))
})
public class MediaCopy2 {
@Id //mapped to COPY_NO by attribute override
private int copyNo;
@Id
@ManyToOne
@JoinColumn(name="MEDIACOPY_MID")
+----- private Media media;
|
| private MediaCopy2() {}
| public MediaCopy2(Media media, int copyNo) {
| this.media=media;
| this.copyNo=copyNo;
| }
| ...
| @Entity @Table(name="ORMREL_MEDIA")
| public class Media {
`----> @Id @GeneratedValue @Column(name="MEDIA_ID")
private long id;
Figure 61.4. Many-to-One Uni-directional Database Usage
ejava.examples.orm.rel.annotated.Media media = new Media();
media.setTitle("EJB31");
//add media to DB
assertTrue(media.getId() == 0);
em.persist(media);
log.info("created media:" + media);
//create some copies
for(int i=0; i<5; i++) {
ejava.examples.orm.rel.annotated.MediaCopy2 mc =
new MediaCopy2(media, i);
assertNotNull(mc.getMedia());
assertEquals(i, mc.getCopyNo());
em.persist(mc);
log.info("created copy:" + mc);
}
Figure 61.5. Many-to-One Uni-directional Database Output
Hibernate: insert into ORMREL_MEDIA (MEDIA_ID, title) values (null, ?) -created media:Media@51942b40, id=1, title=EJB31, authors(0)={} -created copy:MediaCopy2@3787f275, mediaId=1, copyNo=0, media=Media@51942b40, id=1, title=EJB31, authors(0)={} ... -created copy:MediaCopy2@7a7fdbb0, mediaId=1, copyNo=4, media=Media@51942b40, id=1, title=EJB31, authors(0)={} Hibernate: insert into ORMREL_MEDIACOPY2 (COPY_NO, MEDIACOPY_MID) values (?, ?) ... Hibernate: insert into ORMREL_MEDIACOPY2 (COPY_NO, MEDIACOPY_MID) values (?, ?)
Uni-directional
Only one side ("owner") knows of the relationship
Uses the @ManyToMany annotation
Defines mapping to database
Must use @JoinTable
Bi-directional
Both classes know of the relationship
One side required to be owning side and maps relation to the database
Uses the @ManyToMany annotation
Must use @JoinTable
Changes here change the database
One side required to be inverse and names the other entity's property
@ManyToMany(mappedBy="owning-property")
Changes here do *not* change database
Figure 62.3. Many-to-Many Uni-directional Database Schema
create table ORMREL_WANTED ( +----> id bigint generated by default as identity, | primary key (id) | ) | create table ORMREL_WANTED_MEDIA ( `----- ORMREL_WANTED_id bigint not null, +----- media_MEDIA_ID bigint not null | ) | create table ORMREL_MEDIA ( `----> MEDIA_ID bigint generated by default as identity, title varchar(255), primary key (MEDIA_ID) ) alter table ORMREL_WANTED_MEDIA add constraint FKEE528304A70D4E48 foreign key (media_MEDIA_ID) references ORMREL_MEDIA alter table ORMREL_WANTED_MEDIA add constraint FKEE52830486AFC6B6 foreign key (ORMREL_WANTED_id) references ORMREL_WANTED
Figure 62.4. Many-to-Many Uni-directional Database Java Mapping
@Entity @Table(name="ORMREL_WANTED")
public class WantList {
@Id @GeneratedValue
private long id;
@ManyToMany
@JoinTable(name="ORMREL_WANTED_MEDIA") //define table, let columns use default names
+----- private Collection<Media> media = new ArrayList<Media>();
| ...
| @Entity @Table(name="ORMREL_MEDIA")
| public class Media {
| @Id @GeneratedValue @Column(name="MEDIA_ID")
`----> private long id;
private String title;
Figure 62.5. Many-to-Many Uni-directional Example Usage
for(WantList w: wantLists) {
for(Media m: media) {
//we can only navigate this in one direction
w.getMedia().add(m);
log.info("added media(" + m.getId() +
") to want list (" + w.getId() +")");
}
}
Figure 62.6. Many-to-Many Uni-directional Database Output
-added media(1) to want list (1) -added media(2) to want list (1) -added media(1) to want list (2) -added media(2) to want list (2) -added media(1) to want list (3) -added media(2) to want list (3) Hibernate: insert into ORMREL_WANTED_MEDIA (ORMREL_WANTED_id, media_MEDIA_ID) values (?, ?) ...
Rows added to join table rather than updating entity class tables with foreign key
Figure 62.7. Many-to-Many Bi-directional Database Schema
create table ORMREL_AUTHOR ( +-----> id bigint generated by default as identity, | name varchar(255), | primary key (id) | ) | create table ORMREL_AUTHOR_MEDIA ( `----- LINK_AUTHOR_ID bigint not null, +----- LINK_MEDIA_ID bigint not null | ) | create table ORMREL_MEDIA ( `----> MEDIA_ID bigint generated by default as identity, title varchar(255), primary key (MEDIA_ID) ) alter table ORMREL_AUTHOR_MEDIA add constraint FKA0D2B4E09B01C6F2 foreign key (LINK_MEDIA_ID) references ORMREL_MEDIA alter table ORMREL_AUTHOR_MEDIA add constraint FKA0D2B4E089FFE922 foreign key (LINK_AUTHOR_ID) references ORMREL_AUTHOR
Figure 62.8. Many-to-Many Bi-directional Database Java Mapping
@Entity @Table(name="ORMREL_AUTHOR")
public class Author {
@Id @GeneratedValue
private long id;
private String name;
@ManyToMany
@JoinTable(name="ORMREL_AUTHOR_MEDIA", //defines the link table
//defines the column in the link table for author FK
joinColumns={@JoinColumn(name="LINK_AUTHOR_ID")},
//defines the column in the link table for the media FK
inverseJoinColumns={@JoinColumn(name="LINK_MEDIA_ID")})
+--> @OrderBy("title DESC") //order the list returned from database
+----- private List<Media> media = new ArrayList<Media>();
| |
| | @Entity @Table(name="ORMREL_MEDIA")
| | public class Media {
| | @Id @GeneratedValue @Column(name="MEDIA_ID")
| | private long id;
| | private String title;
| `---- @ManyToMany(mappedBy="media") //names property in Author that points to us
`-----> private Collection<Author> authors = new ArrayList<Author>();
Figure 62.9. Many-to-Many Bi-directional Example Usage
for(Author a: authors) {
for(Media m: media) {
a.getMedia().add(m);
log.info("added media(" + m.getId() +
") to author (" + a.getId() +")");
m.getAuthors().add(a);
log.info("added author(" + a.getId() +
") to media (" + m.getId() +")");
}
}
Owning and inverse sides now both need to be set
Figure 62.10. Many-to-Many Bi-directional Database Output
-added media(3) to author (1) -added author(1) to media (3) ... -added media(5) to author (5) -added author(5) to media (5) Hibernate: insert into ORMREL_AUTHOR_MEDIA (LINK_AUTHOR_ID, LINK_MEDIA_ID) values (?, ?) ... Hibernate: insert into ORMREL_AUTHOR_MEDIA (LINK_AUTHOR_ID, LINK_MEDIA_ID) values (?, ?)
Table of Contents
At the completion of this topic, the student shall
have an understanding of:
Inheritance strategies
Single Table
Table per Concrete Class
Joined
Mapped Superclass for non-entity parent classes
be able to:
Be able to map a Java inheritance relationship using a Single Table strategy
Be able to map a Java inheritance relationship using a Table per Concrete Class strategy
Be able to map a Java inheritance relationship using a Table per Subclass (Join) strategy
Advantages
Simplest to implement
Single table to administer
Performs better than other inheritance strategies
No complex joins
Disadvantages
Unused fields when sub-types have unique properties
Sub-type columns must be nullable
Harder to enforce constraints within database
SQL "check" constraint can help
check(TYPE != 'BREAD_TYPE' or (BAKEDON is not null and SLICES is not null)) check(TYPE != 'Soup' or (SOUPTYPE is not null and EXPIRATION is not null)
Not normalized
More suitable for hierarchies with sub-types that...
Differ primarily in behavior only
Do not have unique data requirements
Figure 63.2. Single Table Example Database Schema
create table ORMINH_PRODUCT ( PTYPE varchar(32) not null, id bigint generated by default as identity, cost double not null, expiration date, SOUPTYPE varchar(16), bakedOn date, slices integer, primary key (id) )
Single table
No joins
Unused columns
Figure 63.3. Single Table Example Java Mapping (Parent Class)
@Entity @Table(name="ORMINH_PRODUCT")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="PTYPE", //column in root table indicating type
discriminatorType=DiscriminatorType.STRING,//data type of column
length=32) //length of discriminator string
public abstract class Product {
@Id @GeneratedValue
private long id;
private double cost;
...
@Transient
public abstract String getName();
Parent defines default mapping for all derived types
Figure 63.4. Single Table Example Java Mapping (Annotated Derived)
@Entity
@DiscriminatorValue("BREAD_TYPE") //value placed in root table to indicate type
public class Bread extends Product {
private int slices;
@Temporal(TemporalType.DATE)
private Date bakedOn;
...
@Transient
public String getName() { return "Bread"; }
Supplies type-specific column value
Figure 63.5. Single Table Example Java Mapping (Default Derived)
@Entity
public class Soup extends Product {
public enum SoupType {
UNKNOWN("Unknown"),
CHICKEN_NOODLE("Chicken Noodle"),
NEW_ENGLAND_CLAM_CHOWDER("New England Clam Chowder"),
TOMATO("Tomato");
private String text;
private SoupType(String text) { this.text = text; }
public String text() { return text; }
};
@Enumerated(EnumType.STRING)
@Column(name="SOUPTYPE", length=16)
private SoupType type = SoupType.UNKNOWN;
@Temporal(TemporalType.DATE)
private Date expiration;
...
@Transient
public String getName() { return type.text() + "Soup"; }
Accepts default type-specific column value
Figure 63.6. Single Table Example Usage (Persist)
ejava.examples.orm.inheritance.annotated.Soup soup = new Soup();
soup.setCost(2.12);
final long lifetime = 365L*24*60*60*1000;
soup.setExpiration(new Date(System.currentTimeMillis() + lifetime));
soup.setSoupType(Soup.SoupType.CHICKEN_NOODLE);
em.persist(soup);
ejava.examples.orm.inheritance.annotated.Bread bread = new Bread();
bread.setBakedOn(new Date());
bread.setCost(2.25);
bread.setSlices(24);
em.persist(bread);
Hibernate: insert into ORMINH_PRODUCT (id, cost, expiration, SOUPTYPE, PTYPE) values (null, ?, ?, ?, 'Soup') Hibernate: insert into ORMINH_PRODUCT(id, cost, bakedOn, slices, PTYPE) values (null, ?, ?, ?, 'BREAD_TYPE')
Two rows inserted into single base table with discriminator type supplied
Figure 63.7. Single Table Example Usage (Get Entities)
List<Product> products = em.createQuery(
"select p from Product p", Product.class)
.getResultList();
assertTrue("unexpected number of products:" + products.size(),
products.size() == 2);
for(Product p: products) {
log.info("product found:" + p);
}
Hibernate: select product0_.id as id2_9_, product0_.cost as cost3_9_, product0_.expiration as expirati4_9_, product0_.SOUPTYPE as SOUPTYPE5_9_, product0_.bakedOn as bakedOn6_9_, product0_.slices as slices7_9_, product0_.PTYPE as PTYPE1_9_ from ORMINH_PRODUCT product0_ -product found:Soup, id=1, cost=2.12, type=CHICKEN_NOODLE, expiration=2014-10-05 -product found:Bread, id=2, cost=2.25, slices=24, baked=2013-10-05
Single table queried for objects
Figure 63.8. Single Table Example Usage (Verify DB Schema)
//query specific tables for columns
int rows = em.createNativeQuery(
"select ID, TYPE, COST, SOUPTYPE, EXPIRATION, BAKEDON, SLICES " +
" from ORMINH_PRODUCT")
.getResultList().size();
assertEquals("unexpected number of product rows:" + rows, 2, rows);
select * from ORMINH_PRODUCT PTYPE ID COST BAKEDON SLICES EXPIRATION SOUPTYPE ---------- -- ---- ---------- ------ ---------- -------------- Soup 1 2.12 (null) (null) 2007-10-08 CHICKEN_NOODLE BREAD_TYPE 2 2.25 2006-10-08 24 (null) (null)
Single table "sparsely" filled based on type
@DiscriminatorColumn defines column to hold type-specific value
name (default="DTYPE")
Column name
discriminatorType
STRING (defaults to Entity.name)
Only portable technique when accepting default
CHAR
Vendor-specific value when accepting default
INTEGER
Vendor-specific value when accepting default
columnDefinition
Database-specific definition for when generating schema
length
Size of STRING
Advantages
May have constrained columns
No joins when accessing a single concrete type
Disadvantages
Not normalized
Redundant columns in each concrete child table
More work required to query across tables
Requires use of SQL "UNION"
Least desirable from a performance/portability standpoint
More suitable for ...
Sub-types not needed to be manipulated with sibling other sub-types
Figure 64.2. Table per Concrete Class Example Database Schema
create table ORMINH_CHECKING ( id bigint not null, balance double not null, fee double not null, primary key (id) ) create table ORMINH_INTERESTACCT ( id bigint not null, balance double not null, rate double not null, primary key (id) ) create sequence ORMINH_SEQ
Table for each concrete class
No separate table for parent class
Parent columns repeated in concrete sub-class tables
* his particular example uses SEQUENCE for primary key generation
Figure 64.3. Table per Concrete Class Example Java Mapping (Parent Class)
@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
@SequenceGenerator(
name="orminhSeq", //required logical name
sequenceName="ORMINH_SEQ" //name in database
)
public abstract class Account {
@Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="orminhSeq")
private long id;
private double balance;
...
public void deposit(double amount) throws AccountException {
setBalance(getBalance() + amount);
}
public abstract void withdraw(double amount) throws AccountException;
...
Parent defines default mapping for all derived types, including PK generation
Using a common SEQUENCE allows sub-classes to have unique PKs across all tables
Figure 64.4. Table per Concrete Class Example Java Mapping (Subclasses)
@Entity
@Table(name="ORMINH_CHECKING")
public class CheckingAccount extends Account {
private double fee;
public void withdraw(double amount) throws AccountException {
super.setBalance(super.getBalance() - fee);
}
@Entity
@Table(name="ORMINH_INTERESTACCT")
public class InterestAccount extends Account {
private double rate;
public void withdraw(double amount) throws AccountException {
super.setBalance(super.getBalance() - amount);
}
Subclasses name their specific entity class table
Figure 64.5. Table per Concrete Class Example Usage (Persist)
ejava.examples.orm.inheritance.annotated.CheckingAccount checking = new CheckingAccount();
checking.setFee(0.50);
em.persist(checking);
ejava.examples.orm.inheritance.annotated.InterestAccount savings = new InterestAccount();
savings.setRate(0.25);
em.persist(savings);
Hibernate: call next value for ORMINH_SEQ Hibernate: insert into ORMINH_CHECKING (balance, fee, id) values (?, ?, ?) Hibernate: insert into ORMINH_INTERESTACCT (balance, rate, id) values (?, ?, ?)
Rows for entities placed into separate tables
Figure 64.6. Table per Concrete Class Example Usage (Get Entities)
List<Account> accounts =em.createQuery("select a from Account a").getResultList();
assertTrue("unexpected number of accounts:" + accounts.size(), accounts.size() == 2);
for(Account a: accounts) {
log.info("account found:" + a);
}
select account0_.id as id1_0_, account0_.balance as balance2_0_, account0_.rate as rate1_7_, account0_.fee as fee1_2_, account0_.clazz_ as clazz_ from ( select id, balance, rate, null as fee, 1 as clazz_ from ORMINH_INTERESTACCT union all select id, balance, null as rate, fee, 2 as clazz_ from ORMINH_CHECKING ) account0_ -account found:InterestAccount, id=51, balance=0.0, rate=0.25 -account found:CheckingAccount, id=50, balance=0.0, fee=0.5
Query through parent type causes SQL "UNION ALL" of each concrete sub-class table
Figure 64.7. Table per Concrete Class Example Usage (Verify DB Schema)
//query specific tables for columns
int rows = em.createNativeQuery(
"select ID, BALANCE, FEE from ORMINH_CHECKING")
.getResultList().size();
assertEquals("unexpected number of checking rows:" + rows, 1, rows);
rows = em.createNativeQuery(
"select ID, BALANCE, RATE from ORMINH_INTERESTACCT")
.getResultList().size();
assertEquals("unexpected number of interestacct rows:" + rows, 1, rows);
select * from ORMINH_CHECKING ID BALANCE FEE -- ------- --- 50 0.0 0.5 select * from ORMINH_INTERESTACCT ID BALANCE RATE -- ------- ---- 51 0.0 0.25
Table per concrete class
No unused columns
Parent columns repeated in each sub-class table
Advantages
Can be normalized
Permits constraints to be defined
Disadvantages
Requires access to multiple tables when using (insert, select, update, and delete) an entity
More suitable for sub-classes that...
Have many unique properties
Require database constraints
Are queried for across sibling sub-types
Figure 65.2. Join Example Database Schema
create table ORMINH_PERSON ( id bigint generated by default as identity, firstName varchar(255), lastName varchar(255), primary key (id) ) create table ORMINH_EMPLOYEE ( hireDate date, payrate double not null, id bigint not null, primary key (id) ) alter table ORMINH_CUSTOMER add constraint FK6D5464A42122B7AC foreign key (id) references ORMINH_PERSON alter table ORMINH_EMPLOYEE add constraint FK9055CB742122B7AC foreign key (id) references ORMINH_PERSON
Table for each entity class is created
Each table has a primary key
Sub-class tables use a primary key join with parent class
Figure 65.3. Join Example Java Mapping (Parent Class)
@Entity @Table(name="ORMINH_PERSON")
@Inheritance(strategy=InheritanceType.JOINED)
public class Person {
@Id @GeneratedValue
private long id;
private String firstName;
private String lastName;
Parent defines default mapping for all derived types
Parent entity class table also defined
Figure 65.4. Join Example Java Mapping (Subclasses)
@Entity
@Table(name="ORMINH_CUSTOMER") //joined with Person table to form Customer
public class Customer extends Person {
public enum Rating { GOLD, SILVER, BRONZE }
@Enumerated(EnumType.STRING)
private Rating rating;
@Entity
@Table(name="ORMINH_EMPLOYEE") //joined with Person table to form Employee
public class Employee extends Person {
private double payrate;
@Temporal(TemporalType.DATE)
private Date hireDate;
Sub-classes define their entity-specific tables
Figure 65.5. Join Example Usage
ejava.examples.orm.inheritance.annotated.Employee employee = new Employee();
employee.setFirstName("john");
employee.setLastName("doe");
employee.setHireDate(new Date());
employee.setPayrate(10.00);
em.persist(employee);
ejava.examples.orm.inheritance.annotated.Customer customer = new Customer();
customer.setFirstName("jane");
customer.setLastName("johnson");
customer.setRating(Customer.Rating.SILVER);
em.persist(customer);
Hibernate: insert into ORMINH_PERSON (id, firstName, lastName) values (null, ?, ?) Hibernate: insert into ORMINH_EMPLOYEE (hireDate, payrate, id) values (?, ?, ?) Hibernate: insert into ORMINH_PERSON (id, firstName, lastName) values (null, ?, ?) Hibernate: insert into ORMINH_CUSTOMER (rating, id) values (?, ?)
Each persist() must insert into concrete class table and parent class table(s)
Figure 65.6. Join Example Usage (Get Entities)
List<Person> people = em.createQuery("select p from Person p", Person.class).getResultList();
assertTrue("unexpected number of people:" + people.size(),
people.size() == 2);
for(Person p: people) {
log.info("person found:" + p);
}
select person0_.id as id1_8_, person0_.firstName as firstNam2_8_, person0_.lastName as lastName3_8_, person0_1_.hireDate as hireDate1_6_, person0_1_.payrate as payrate2_6_, person0_2_.rating as rating1_5_, case when person0_1_.id is not null then 1 when person0_2_.id is not null then 2 when person0_.id is not null then 0 end as clazz_ from ORMINH_PERSON person0_ left outer join ORMINH_EMPLOYEE person0_1_ on person0_.id=person0_1_.id left outer join ORMINH_CUSTOMER person0_2_ on person0_.id=person0_2_.id -person found:Employee, id=1, firstName=john, lastName=doe, payrate=10.0 -person found:Customer, id=2, firstName=jane, lastName=johnson, rating=SILVER
Parent class table joined with each sub-class table during query of parent type
Figure 65.7. Join Example Usage (Verify DB Schema)
int rows = em.createNativeQuery(
"select ID, FIRSTNAME, LASTNAME from ORMINH_PERSON")
.getResultList().size();
assertEquals("unexpected number of person rows:" + rows, 2, rows);
rows = em.createNativeQuery(
"select ID, RATING from ORMINH_CUSTOMER")
.getResultList().size();
assertEquals("unexpected number of customer rows:" + rows, 1, rows);
rows = em.createNativeQuery(
"select ID, PAYRATE, HIREDATE from ORMINH_EMPLOYEE")
.getResultList().size();
assertEquals("unexpected number of employee rows:" + rows, 1, rows);
select * from ORMINH_PERSON ID FIRSTNAME LASTNAME -- --------- -------- 1 john doe 2 jane johnson select * from ORMINH_EMPLOYEE ID HIREDATE PAYRATE -- ---------- ------- 1 2006-10-08 10.0 select * from ORMINH_CUSTOMER ID RATING -- ------ 2 SILVER
Entities span multiple tables
Advantages
Allows inheritance of non-entity classes
Disadvantages
No base entity to form queries across hierarchy (unlike TABLE_PER_CLASS)
Tables not normalized (like TABLE_PER_CLASS)
Parent columns repeated in each subclass table
More suitable for ...
Independent subclasses
Figure 66.2. Non-Entity Example Database Schema
create table ORMINH_ALBUM ( ALBUM_ID bigint generated by default as identity, ALBUM_VERSION bigint, artist varchar(255), title varchar(255), primary key (ALBUM_ID) ) create table ORMINH_TOOTHPASTE ( id bigint generated by default as identity, version bigint not null, size integer not null, primary key (id) )
Non-entity base class properties appear in subclass tables
Figure 66.3. Non-Entity Example Java Mapping (Parent Class)
@MappedSuperclass
public abstract class BaseObject {
private long id;
@Access(AccessType.FIELD)
private long version;
@Transient
public long getId() { return id; }
protected void setId(long id) {
this.id = id;
}
Parent class is not a legal entity -- has no @Id
In this example, the implementation of BaseObject actually has an id attribute that the derived classes make use of. However, it is marked as @Transient in the base class and @Id in the derived Entity classes since MappedSuperClasses do not have primary keys. This specific example could have also used TABLE_PER_CLASS because of the availability of an id property in the base class.
Figure 66.4. Non-Entity Example Java Mapping (Override Defaults)
@Entity
@Table(name="ORMINH_ALBUM") //this table holds both this entity and parent class
@AttributeOverrides({
@AttributeOverride(name="version", column=@Column(name="ALBUM_VERSION"))
})
public class Album extends BaseObject {
@Access(AccessType.FIELD)
private String artist;
@Access(AccessType.FIELD)
private String title;
@Id @GeneratedValue //id is being generated independent of other siblings
@Column(name="ALBUM_ID")
public long getId() { return super.getId(); }
protected void setId(long id) {
super.setId(id);
}
"version" column name from parent being renamed
"id" property defined in this class given custom column name
Transient attribute in parent being reused to hold @Id for subclass using PROPERTY access
Figure 66.5. Non-Entity Example Java Mapping (Default Derived)
@Entity
@Table(name="ORMINH_TOOTHPASTE") //table holds this entity and parent class
public class ToothPaste extends BaseObject {
@Access(AccessType.FIELD)
private int size;
@Id @GeneratedValue //id is being generated independent of other siblings
public long getId() { return super.getId(); }
protected void setId(long id) {
super.setId(id);
}
Entity accepts mapping defaults
Figure 66.6. Non-Entity Example Usage
ejava.examples.orm.inheritance.annotated.Album album = new Album();
album.setArtist("Lynyrd Skynyrd");
album.setTitle("One More for the Road");
em.persist(album);
ejava.examples.orm.inheritance.annotated.ToothPaste toothpaste = new ToothPaste();
toothpaste.setSize(10);
em.persist(toothpaste);
Hibernate: insert into ORMINH_ALBUM (ALBUM_ID, ALBUM_VERSION, artist, title) values (null, ?, ?, ?) Hibernate: insert into ORMINH_TOOTHPASTE (id, version, size) values (null, ?, ?)
Rows are inserted into type-specific entity class tables
Figure 66.7. Non-Entity Example Usage (Get Entities)
List<BaseObject> objects = em.createQuery("select a from Album a").getResultList();
objects.addAll( em.createQuery("select tp from ToothPaste tp").getResultList());
assertTrue("unexpected number of objects:" + objects.size(), objects.size() == 2);
for(BaseObject o: objects) {
log.info("object found:" + o);
}
Hibernate: select album0_.ALBUM_ID as ALBUM1_1_, album0_.ALBUM_VERSION as ALBUM2_1_, album0_.artist as artist3_1_, album0_.title as title4_1_ from ORMINH_ALBUM album0_ Hibernate: select toothpaste0_.id as id1_12_, toothpaste0_.version as version2_12_, toothpaste0_.size as size3_12_ from ORMINH_TOOTHPASTE toothpaste0_ -object found:ejava.examples.orm.inheritance.annotated.Album@3822f407, id=1, name=Lynyrd Skynyrd:One More for the Road -object found:ejava.examples.orm.inheritance.annotated.ToothPaste@22a79bc, id=1, name=10oz toothpaste
Separate tables are accessed when obtaining each type
Figure 66.8. Non-Entity Example Usage (Verify DB Schema)
int rows = em.createNativeQuery(
"select ALBUM_ID, ALBUM_VERSION, ARTIST, TITLE " +
" from ORMINH_ALBUM")
.getResultList().size();
assertEquals("unexpected number of album rows:" + rows, 1, rows);
rows = em.createNativeQuery(
"select ID, VERSION, SIZE " +
" from ORMINH_TOOTHPASTE")
.getResultList().size();
assertEquals("unexpected number of toothpaste rows:" + rows, 1, rows);
select * from ORMINH_ALBUM ALBUM_ID ALBUM_VERSION ARTIST TITLE -------- ------------- -------------- --------------------- 1 0 Lynyrd Skynyrd One More for the Road select * from ORMINHTOOTHPASTE ID VERSION SIZE -- ------- ---- 1 0 10
Separate tables per concrete class (like TABLE_PER_CLASS)
No unused columns (like TABLE_PER_CLASS)
Figure 67.2. Generated Database Schema
create table ORMINH_SHAPE ( id bigint generated by default as identity, version bigint not null, posx integer not null, posy integer not null, primary key (id) ) create table ORMINH_CIRCLE ( radius integer not null, id bigint not null, primary key (id) ) create table ORMINH_RECTANGLE ( height integer not null, width integer not null, id bigint not null, primary key (id) ) alter table ORMINH_CIRCLE add constraint FKFF2F1F1632C97600 foreign key (id) references ORMINH_SHAPE alter table ORMINH_RECTANGLE add constraint FK1FFF614932C97600 foreign key (id) references ORMINH_SHAPE
create table ORMINH_CUBE ( depth integer not null, id bigint not null, primary key (id) ) alter table ORMINH_CUBE add constraint FK84203FB112391CE foreign key (id) references ORMINH_RECTANGLE
Provider used parent's JOIN strategy over child's TABLE_PER_CLASS specification
Table of Contents
Provide breadth coverage of JPA Queries to demonstrate options available for accessing information from a relational database using an EntityManager.
At the completion of this topic, the student shall
have an understanding of:
Query Construction
Value, ResultClass, and Entity Queries
Typed Queries
Dynamic and Named Queries
Single and Multiple Results
Parameters
Paging
Locking
Supported Query Languages
JPA Query Language
Native SQL
Java-based Criteria API
be able to:
Form a query using...
JPA-QL
Native SQL
Criteria API
Use a query to locate specific properties for one or more entities that match a criteria
Use a query to locate specific entities that match a criteria
Use paging within queries to handle large data sets
Use pessimistic locking to better support database consistency
Table of Contents
Three fundamental query types within JPA
JPA Query Language (JPA) - entity/property/relationship-based
Native SQL - table/column-based
Criteria API - entity/property/relationship-based using Java classes
Access to the *entity* model using a SQL-like text query language
Queries expressed using entities, properties, and relationships
Pros
More concise (than other query forms)
Familiar to SQL users
Abstracts query away from table, column, primary key, and relationship mapping
Can be defined within XML deployment descriptors
Produces portable SQL
Cons
Not (overly) type-safe
No help from Java compiler in constructing query expression
Don't find out most errors until runtime
Figure 68.1. Building a JPA Query using JPA-QL
String jpaqlString =
"select c from Customer c " +
"where c.firstName = :firstName " +
"order by c.lastName ASC";
//use query string to build typed JPA-QL query
TypedQuery<Customer> query = em
.createQuery(jpaqlString,Customer.class);
"c" is part of root query
"c" represents rows from Customer entity table(s)
"c.lastName" is path off root term
":firstName" is parameter placeholder
"c.firstName" is path off root term
"Customer.class" type parameter allows for a type-safe return result
Figure 68.2. Executing a JPA Query (built from JPA-QL)
//at this point we are query-type agnostic
List<Customer> customers = query
.setParameter("firstName", "thing")
.getResultList();
log.info("result=" + customers);
assertEquals("unexpected number of results", 2, customers.size());
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=? order by customer0_.LAST_NAME ASC -result=[firstName=thing, lastName=one, firstName=thing, lastName=two]
Placeholder is replaced by runtime parameter
Zero-or-more results are requested
Entities returned are managed
Figure 68.3. Condensing the JPA-QL Query
List<Customer> customers = em.createQuery(
"select c from Customer c " +
"where c.firstName = :firstName " +
"order by c.lastName ASC",
Customer.class)
.setParameter("firstName", "thing")
.getResultList();
log.info("result=" + customers);
assertEquals("unexpected number of results", 2, customers.size());
Access to power of working with native SQL
Pros
Provides full access to native SQL power
Provides full access to database-vendor SQL extensions
Easy to see when native SQL is being used within application -- target for portability review
Ability to produce managed entity as result of query
Cons
Portability of SQL not addressed by JPA
Not type-safe
No help from Java compiler in constructing query expression
Don't find out most errors until runtime
Figure 68.4. Building a JPA Query using Native SQL
Table table = Customer.class.getAnnotation(Table.class);
String sqlString =
"select c.CUSTOMER_ID, c.FIRST_NAME, c.LAST_NAME " +
String.format("from %s c ", table.name()) +
"where c.FIRST_NAME = ? " +
"order by c.LAST_NAME ASC";
//use query string to build query
Query query = em.createNativeQuery(sqlString,Customer.class);
"c" represents rows in table
specific columns (or *) are return for each row
"?" marks a positional parameter -- non-portable to use named parameters in native SQL queries
TypedQuery<T>s not supported in native SQL queries because of a conflict with legacy JPA 1.0 API
Figure 68.5. Executing a JPA Query (built from Native SQL)
//at this point we are query-type agnostic (mostly)
@SuppressWarnings("unchecked")
List<Customer> customers = query
.setParameter(1, "thing")
.getResultList();
log.info("result=" + customers);
assertEquals("unexpected number of results", 2, customers.size());
select c.CUSTOMER_ID, c.FIRST_NAME, c.LAST_NAME from JPAQL_CUSTOMER c where c.FIRST_NAME = ? order by c.LAST_NAME ASC -result=[firstName=thing, lastName=one, firstName=thing, lastName=two]
Query execution similar to other query types
User-provided SQL executed
Legacy JPA 1.0 Native SQL query syntax already used the signature of passing in a Class for createNativeQuery(). In this context, it was an entity class that contained JPA mappings for the query -- not the returned entity type. This prevented createNativeQuery() from being updated to return a typed result in JPA 2.0.
Figure 68.6. Condensing the SQL Query
@SuppressWarnings("unchecked")
List<Customer> customers = em.createNativeQuery(
"select c.CUSTOMER_ID, c.FIRST_NAME, c.LAST_NAME " +
"from JPAQL_CUSTOMER c " +
"where c.FIRST_NAME = ? " +
"order by c.LAST_NAME ASC",
Customer.class)
.setParameter(1, "thing")
.getResultList();
log.info("result=" + customers);
assertEquals("unexpected number of results", 2, customers.size());
Allow query to return mixture of managed entities and values
DAOs can use value results to plugin transient aggregate properties in parent entity without pulling entire child entities back from database
e.g., total sales for clerk
Figure 68.7. NativeQuery with SqlResultSetMapping
@SuppressWarnings("unchecked")
List<Object[]> results = em.createNativeQuery(
"select clerk.CLERK_ID, "
+ "clerk.FIRST_NAME, "
+ "clerk.LAST_NAME, "
+ "clerk.HIRE_DATE, "
+ "clerk.TERM_DATE, "
+ "sum(sales.amount) total_sales "
+ "from JPAQL_CLERK clerk "
+ "left outer join JPAQL_SALE_CLERK_LINK slink on clerk.CLERK_ID=slink.CLERK_ID "
+ "left outer join JPAQL_SALE sales on sales.SALE_ID=slink.SALE_ID "
+ "group by clerk.CLERK_ID, "
+ "clerk.FIRST_NAME, "
+ "clerk.LAST_NAME, "
+ "clerk.HIRE_DATE, "
+ "clerk.TERM_DATE "
+ "order by total_sales DESC",
"Clerk.clerkSalesResult")
.getResultList();
@Entity @Table(name="JPAQL_CLERK")
@SqlResultSetMappings({
@SqlResultSetMapping(name = "Clerk.clerkSalesResult",
entities={ @EntityResult(entityClass = Clerk.class )},
columns={@ColumnResult(name = "total_sales")}
)
})
public class Clerk {
Figure 68.8. Example NativeQuery with SqlResultSetMapping Output
for (Object[] result: results) {
Clerk clerk = (Clerk) result[0];
BigDecimal totalSales = (BigDecimal) result[1];
log.info(String.format("%s, $ %s", clerk.getFirstName(), totalSales));
}
-Manny, $ 250.00 -Moe, $ 150.00 -Jack, $ null
Figure 68.9. NamedNativeQuery with SqlResultSetMapping
@Entity @Table(name="JPAQL_CLERK")
@NamedNativeQueries({
@NamedNativeQuery(name = "Clerk.clerkSales", query =
"select clerk.CLERK_ID, "
+ "clerk.FIRST_NAME, "
+ "clerk.LAST_NAME, "
+ "clerk.HIRE_DATE, "
+ "clerk.TERM_DATE, "
+ "sum(sales.amount) total_sales "
+ "from JPAQL_CLERK clerk "
+ "left outer join JPAQL_SALE_CLERK_LINK slink on clerk.CLERK_ID=slink.CLERK_ID "
+ "left outer join JPAQL_SALE sales on sales.SALE_ID=slink.SALE_ID "
+ "group by clerk.CLERK_ID, "
+ "clerk.FIRST_NAME, "
+ "clerk.LAST_NAME, "
+ "clerk.HIRE_DATE, "
+ "clerk.TERM_DATE "
+ "order by total_sales DESC",
resultSetMapping="Clerk.clerkSalesResult")
})
@SqlResultSetMappings({
@SqlResultSetMapping(name = "Clerk.clerkSalesResult",
entities={ @EntityResult(entityClass = Clerk.class )},
columns={@ColumnResult(name = "total_sales")}
)
})
public class Clerk {
Figure 68.10. Example NamedNativeQuery with SqlResultSetMapping Usage
List<Object[]> results = em.createNamedQuery("Clerk.clerkSales").getResultList();
Somewhat parallel capability to JPAQL
Build overall query using Java types (demonstrated here with "string accessors")
Pros
Structure of query is type-safe
Allows object-level manipulation of the query versus manipulation of a query string
Useful when building total query based on runtime properties
Cons
Complex -- looses familiarity with SQL
Cannot be expressed in XML deployment descriptor
Access to properties not type-safe (addressed by Canonical Metamodel)
Figure 68.11. Building a JPA Query using Criteria API
select c from Customer c where c.firstName = :firstName order by c.lastName ASC
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c)
.where(cb.equal(c.get("firstName"),
cb.parameter(String.class,"firstName")))
.orderBy(cb.asc(c.get("lastName")));
//build query from criteria definition
TypedQuery<Customer> query = em.createQuery(qdef);
"CriteriaBuilder" used as starting point to build objects within the query tree
"CriteriaQuery<T>" used to hold the definition of query
"Root<T>" used to reference root level query terms
"CriteriaBuilder.from()" used to designate the entity that represents root query term
Result used to create path references for query body
"CriteriaBuilder.select()" officially lists the objects returned from query
"CriteriaBuilder.where()" builds a decision predicate of which entities to include
"CriteriaBuilder.equal()" builds an equals predicate for the where clause
"Root<T>.get()" returns the property referenced in path expression
"CriteriaBuilder.parameter()" builds a parameter placeholder within query. Useful with @Temporal date comparisons
Figure 68.12. Executing a JPA Query using Criteria API
//at this point we are query-type agnostic
List<Customer> customers = query
.setParameter("firstName", "thing")
.getResultList();
log.info("result=" + customers);
assertEquals("unexpected number of results", 2, customers.size());
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=? order by customer0_.LAST_NAME asc -result=[firstName=thing, lastName=one, firstName=thing, lastName=two]]
Query execution identical to JPA-QL case
Figure 68.13. Condensing the Criteria API Query
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
List<Customer> customers = em.createQuery(qdef.select(c)
.where(cb.equal(c.get("firstName"), "thing"))
.orderBy(cb.asc(c.get("lastName"))))
.getResultList();
log.info("result=" + customers);
assertEquals("unexpected number of results", 2, customers.size());
Previous Criteria API examples were string label based -- not type safe
Criteria API provides means for stronger typing
Strong typing permits automatic detection of model and query differences
Provides access to the persistent model backing each entity and its properties
Figure 68.14. Accessing JPA Metamodel
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
EntityType<Customer> c_ = c.getModel();
log.info(String.format("%7s, %10s:%-30s",
c_.getPersistenceType(),
c_.getName(),
c_.getJavaType()));
for (Attribute<? super Customer, ?> p: c_.getAttributes()) {
log.info(String.format("%7s, %10s:%-30s",
p.getPersistentAttributeType(),
p.getName(),
p.getJavaType()));
}
- ENTITY, Customer:class ejava.jpa.examples.query.Customer - BASIC, firstName:class java.lang.String - BASIC, id:long - BASIC, lastName:class java.lang.String
JPA Metamodel provides access to
Entity structure
Entity database mapping
Pros
Access properties in (a more) type-safe manner
Cons
Complex
No compiler warning of entity type re-factoring
Figure 68.15. Building Query with JPA Metamodel
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
EntityType<Customer> c_ = c.getModel();
qdef.select(c) //we are returning a single root object
.where(cb.equal(
c.get(c_.getSingularAttribute("firstName", String.class)),
cb.parameter(String.class,"firstName")))
.orderBy(cb.asc(c.get(c_.getSingularAttribute("lastName", String.class))));
TypedQuery<Customer> query = em.createQuery(qdef);
Access to properties within entities done through type-safe accessors
Figure 68.16. Executing Query with JPA Metamodel
//at this point we are query-type agnostic
List<Customer> customers = query
.setParameter("firstName", "thing")
.getResultList();
log.info("result=" + customers);
assertEquals("unexpected number of results", 2, customers.size());
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=? order by customer0_.LAST_NAME asc -result=[firstName=thing, lastName=one, firstName=thing, lastName=two]
Results identical to previous approaches
Figure 68.17. Condensing the JPA Metamodel-based Query
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
EntityType<Customer> c_ = c.getModel();
List<Customer> customers = em.createQuery(qdef.select(c)
.where(cb.equal(
c.get(c_.getSingularAttribute("firstName", String.class)), "thing"))
.orderBy(cb.asc(c.get(c_.getSingularAttribute("lastName", String.class)))))
.getResultList();
log.info("result=" + customers);
assertEquals("unexpected number of results", 2, customers.size());
Complexities of metamodel cab be simplified using metamodel classes
Pros
Easy, type-safe access to entity model
Java compiler can alert of mismatch between query and entity model
Cons
Requires either manual construct or auto-generation of separate metamodel class
Figure 68.18. Example Canonical Metamodel
package ejava.jpa.examples.query;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;
@StaticMetamodel(Customer.class)
public abstract class Customer_ {
public static volatile SingularAttribute<Customer, Long> id;
public static volatile SingularAttribute<Customer, String> lastName;
public static volatile SingularAttribute<Customer, String> firstName;
}
Construct or generate a canonical metamodel class to provide type-safe, easy access to properties
Figure 68.19. Building Query with Canonical Metamodel
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c) //we are returning a single root object
.where(cb.equal(
c.get(Customer_.firstName),
cb.parameter(String.class,"firstName")))
.orderBy(cb.asc(c.get(Customer_.lastName)));
TypedQuery<Customer> query = em.createQuery(qdef);
Use canonical metamodel class to provide type-safe, easy access to properties ("Customer_.firstName")
Figure 68.20. Executing Query with Canonical Metamodel
//at this point we are query-type agnostic
List<Customer> customers = query
.setParameter("firstName", "thing")
.getResultList();
log.info("result=" + customers);
assertEquals("unexpected number of results", 2, customers.size());
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=? order by customer0_.LAST_NAME asc -result=[firstName=thing, lastName=one, firstName=thing, lastName=two]
Result is identical to previous approaches
Figure 68.21. Condensing the Canonical Metamodel-based Query
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
List<Customer> customers = em.createQuery(qdef.select(c)
.where(cb.equal(c.get(Customer_.firstName),"thing"))
.orderBy(cb.asc(c.get(Customer_.lastName))))
.getResultList();
log.info("result=" + customers);
assertEquals("unexpected number of results", 2, customers.size());
More work to get here but clean, result
Type-safe - queries will not compile if entity changes
Canonical Metamodel classes can be manually authoried or generated
Figure 68.22. Maven Dependency Can Generate Canonical Metamodel Classes
<!-- generates JPA metadata classes -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>1.1.1.Final</version>
<scope>provided</scope>
</dependency>
Figure 68.23. Generated Source placed in target/generated-sources/annotations
`-- target |-- generated-sources `-- annotations `-- ejava `-- jpa `-- examples `-- query |-- Clerk_.java |-- Customer_.java |-- Sale_.java `-- Store_.java
Figure 68.24. Maven Plugin adds Generated Source to IDE Build Path
<!-- add generated JPA metamodel classes to classpath -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.8</version>
<executions>
<execution>
<id>add-metamodel-classes</id>
<phase>process-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>target/generated-sources/annotations</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
Three basic forms for query expression
SqlResultSetMapping
JPAQL
Native SQL
Criteria API
String-based Accessors
Metamodel Accessors
Canonical Metamodel Accessors
Entity model provides portability
JPAQL
Criteria API
Native SQL provides direct access to
full power of SQL
full access to database-specific extensions
Criteria API provides type-safe construct of query structure
JPA Metamodel provides type-safe access to entity properties
JPA Canonical Metamodel provides type-safe access to model-specific entity properties
Produces compilation error when query our of sync with entity model
Provides convenient access to model-specific properties
Create query using JPA-QL String
javax.persistence.Query createQuery(String jpaql);
<T extends Object> javax.persistence.TypedQuery<T> createQuery(String jpaql, Class<T>);
Create query using native SQL
javax.persistence.Query createNativeQuery(String sql);
javax.persistence.Query createNativeQuery(String sql, Class sqlMapping);
javax.persistence.Query createNativeQuery(String sql, String sqlMapping);
Create query using Criteria API
javax.persistence.criteria.CriteriaBuilder getCriteriaBuilder();
javax.persistence.metamodel.Metamodel getMetamodel();
<T extends Object> javax.persistence.TypedQuery<T> createQuery(javax.persistence.criteria.CriteriaQuery<T>);
Create query from Named Query
javax.persistence.Query createNamedQuery(String queryName);
<T extends java.lang.Object> javax.persistence.TypedQuery<T> createNamedQuery(String queryName, Class<T>);
Obtains exactly one result
TypedQuery returns type-safe result
Figure 69.1. Get a Unique Object based on Query
TypedQuery<Store> query = em.createQuery(
"select s from Store s where s.name='Big Al''s'", Store.class);
Store store = query.getSingleResult();
select store0_.STORE_ID as STORE1_4_, store0_.name as name2_4_ from ORMQL_STORE store0_ where store0_.name='Big Al''s'
Figure 69.2. Throws NoResultException when not Found
try {
store = em.createQuery(
"select s from Store s where s.name='A1 Sales'", Store.class)
.getSingleResult();
}
catch (NoResultException ex) { ... }
Figure 69.3. Throws NonUniqueResultException when multiple Found
try {
Clerk clerk = em.createQuery(
"select c from Clerk c where lastName='Pep'", Clerk.class)
.getSingleResult();
}
catch (NonUniqueResultException ex) { ... }
Returns zero or more results
TypedQuery returns type-safe result
Figure 69.4. Returns List of Results Based on Query
TypedQuery<Clerk> query = em.createQuery(
"select c from Clerk c where lastName='Pep'", Clerk.class);
List<Clerk> clerks = query.getResultList();
assertTrue("unexpected number of clerks:" + clerks.size(), clerks.size() > 1);
for(Clerk c : clerks) {
log.info("found clerk:" + c);
}
select clerk0_.CLERK_ID as CLERK1_0_, clerk0_.FIRST_NAME as FIRST2_0_, clerk0_.HIRE_DATE as HIRE3_0_, clerk0_.LAST_NAME as LAST4_0_, clerk0_.TERM_DATE as TERM5_0_ from JPAQL_CLERK clerk0_ where clerk0_.LAST_NAME='Pep' ... -found clerk:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, } -found clerk:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, } -found clerk:firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}
Runtime query parameters passed into query
Figure 69.5. Name-based Query Parameters
TypedQuery<Customer> query = em.createQuery(
"select c from Customer c " +
"where c.firstName=:firstName and c.lastName=:lastName",
Customer.class);
query.setParameter("firstName", "cat");
query.setParameter("lastName", "inhat");
Customer customer = query.getSingleResult();
assertNotNull(customer);
log.info("found customer for param names:" + customer);
select customer0_.CUSTOMER_ID as CUSTOMER1_1_, customer0_.FIRST_NAME as FIRST2_1_, customer0_.LAST_NAME as LAST3_1_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=? and customer0_.LAST_NAME=? -found customer for param names:firstName=cat, lastName=inhat
:firstName and :lastName act as placeholders for runtime query parameters
Runtime parameters supplied using placeholder names
A parameter for each placeholder must be supplied - no defaults
A placeholder must exist for each parameter supplied - no extras
Figure 69.6. Ordinal-based Parameters
query = em.createQuery(
"select c from Customer c " +
"where c.firstName=?1 and c.lastName like ?2", Customer.class);
query.setParameter(1, "thing");
query.setParameter(2, "%");
List<Customer> customers = query.getResultList();
assertTrue("unexpected number of customers:" + customers.size(),
customers.size() == 2);
for(Customer c : customers) {
log.info("found customer for param position:" + c);
}
select customer0_.CUSTOMER_ID as CUSTOMER1_1_, customer0_.FIRST_NAME as FIRST2_1_, customer0_.LAST_NAME as LAST3_1_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=? and ( customer0_.LAST_NAME like ? ) -found customer for param position:firstName=thing, lastName=one -found customer for param position:firstName=thing, lastName=two
Appended numbers (?1) assign an ordinal value
No numbers supplied (?) cause default value based on order
Figure 69.7. Date-based Parameters
Calendar hireDate = Calendar.getInstance();
hireDate.set(Calendar.YEAR, 1972);
TypedQuery<Clerk> query = em.createQuery(
"select c from Clerk c " +
"where c.hireDate > :date", Clerk.class);
query.setParameter("date", hireDate.getTime(), TemporalType.DATE);
Clerk clerk = query.getSingleResult();
log.info("found clerk by date(" + hireDate.getTime() + "):" + clerk);
select clerk0_.CLERK_ID as CLERK1_0_, clerk0_.FIRST_NAME as FIRST2_0_, clerk0_.HIRE_DATE as HIRE3_0_, clerk0_.LAST_NAME as LAST4_0_, clerk0_.TERM_DATE as TERM5_0_ from JPAQL_CLERK clerk0_ where clerk0_.HIRE_DATE>? ... -found clerk by date(Fri Oct 06 20:28:08 EDT 1972):firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}
Dates are specified as DATE, TIME, or TIMESTAMP
Figure 69.8.
TypedQuery<Sale> query = em.createQuery(
"select s from Sale s", Sale.class);
for(int i=0; i<2; i++) {
List<Sale> sales = query.setMaxResults(10)
.setFirstResult(i)
.getResultList();
for(Sale s: sales) {
log.info("found sale in page(" + i + "):" + s);
em.detach(s); //we are done with this
}
}
select sale0_.SALE_ID as SALE1_2_, sale0_.amount as amount2_2_, sale0_.BUYER_ID as BUYER3_2_, sale0_.date as date4_2_, sale0_.SALE_STORE as SALE5_2_ from JPAQL_SALE sale0_ limit ? ... -found sale in page(0):date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, } -found sale in page(0):date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, } select sale0_.SALE_ID as SALE1_2_, sale0_.amount as amount2_2_, sale0_.BUYER_ID as BUYER3_2_, sale0_.date as date4_2_, sale0_.SALE_STORE as SALE5_2_ from JPAQL_SALE sale0_ limit ? offset ? ... -found sale in page(1):date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Offset and limits passed to database
Database provides specified subset of rows
Obtain a locked copy of entity -- ready for modification
Required for some concurrent interactions with database
Figure 69.9. Obtaining a Pessimistic Write Lock
//get a list of clerks to update -- locked so others cannot change
List<Clerk> clerks = em.createQuery(
"select c from Clerk c " +
"where c.hireDate > :date", Clerk.class)
.setParameter("date", new GregorianCalendar(1972,Calendar.JANUARY,1).getTime())
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.setHint("javax.persistence.lock.timeout", 0)
.getResultList();
//make changes
for (Clerk c: clerks) {
c.setHireDate(new GregorianCalendar(1972, Calendar.FEBRUARY, 1).getTime());
}
select clerk0_.CLERK_ID as CLERK1_0_, clerk0_.FIRST_NAME as FIRST2_0_, clerk0_.HIRE_DATE as HIRE3_0_, clerk0_.LAST_NAME as LAST4_0_, clerk0_.TERM_DATE as TERM5_0_ from JPAQL_CLERK clerk0_ where clerk0_.HIRE_DATE>? for update ...
Provider adds database-specific technique for lock
Lock timeout (in msecs) can be expressed through query hint
Not all databases support lock timeouts
Change database -- not query it
Bypasses cache -- cached entities out of sync with database changes
Criteria API updates/deletes added in JPA 2.1
Figure 69.10. JPA-QL Bulk Update Example
Query update = em.createQuery(
"update Clerk c set c.lastName=:newlast where c.lastName=:last");
update.setParameter("last", "Pep");
update.setParameter("newlast", "Peppy");
int rows = update.executeUpdate();
assertEquals("unexpected rows updated:" + rows, clerks.size(), rows);
update JPAQL_CLERK set LAST_NAME=? where LAST_NAME=?
Change directly applied to database, not the cached entity
Number of entities changed returned
Figure 69.11. Criteria API Bulk Update Example
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaUpdate<Clerk> qdef2=cb.createCriteriaUpdate(Clerk.class);
//"update Clerk c set c.lastName=:newlast where c.lastName=:last"
Root<Clerk> c2 = qdef2.from(Clerk.class);
qdef2.set("lastName", "Peppy")
.where(cb.equal(c2.get("lastName"), "Pep"));
Query update = em.createQuery(qdef2);
int rows = update.executeUpdate();
assertEquals("unexpected rows updated:" + rows, clerks.size(), rows);
Figure 69.12. JPA-QL Bulk Delete Example
Query update = em.createQuery(
"delete from Customer c " +
"where c.firstName like :first AND c.lastName like :last");
int rows = update.setParameter("first", "thing")
.setParameter("last", "%")
.executeUpdate();
assertTrue("no rows updated", rows > 0);
delete from JPAQL_CUSTOMER where ( FIRST_NAME like ? ) and ( LAST_NAME like ? )
Bulk deletes do not trigger cascades
Entity instance exists in memory even after deleted from database
Figure 69.13. Criteria API Bulk Update Example
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaDelete<Customer> delete = cb.createCriteriaDelete(Customer.class);
//"delete from Customer c " +
//"where c.firstName like :first AND c.lastName like :last");
Root<Customer> c2 = delete.from(Customer.class);
delete.where(cb.and(
cb.like(c.<String>get("firstName"), "thing"),
cb.like(c.<String>get("lastName"), "%")
));
Query update = em.createQuery(delete);
int rows = update.executeUpdate();
assertTrue("no rows updated", rows > 0);
Figure 69.14. Refresh/Clear/Detach Stale Entit(ies)
//re-sync entity with DB changes
em.refresh(clerk);
//evict all managed entities in persistence context
em.clear();
//remove entity from persistence context
em.detach(clerk);
Keeping stale entities around will produce confusing results
"em.clear()" should be avoided except at end of transaction since un-manages everything
Register query with provider rather than ad-hoc
Available for JPA-QL and Native SQL -- not available with Criteria API
Can be registered using class annotations and orm.xml descriptor
LockMode and hints can be specified in declaration
Figure 69.15. Named Query Annotations Applied to (any) Entity Class
@Entity
@Table(name="JPAQL_CUSTOMER")
@NamedQueries({
@NamedQuery(name="Customer.getCustomersByName",
query="select c from Customer c " +
"where c.firstName like :first AND c.lastName like :last"),
@NamedQuery(name="Customer.getCustomerPurchases",
query="select s from Sale s " +
"where s.buyerId=:custId")
})
public class Customer {
Figure 69.16. Using Named Query
Customer customer =
em.createNamedQuery("Customer.getCustomersByName", Customer.class)
.setParameter("first", "cat")
.setParameter("last", "inhat")
.getResultList()
.get(0);
assertNotNull("no customer found", customer);
Figure 69.17. Named Native Query Annotation Example
@Entity
@Table(name="JPAQL_CUSTOMER")
@NamedNativeQueries({
@NamedNativeQuery(name="Customer.getCustomerRows",
query="select * from JPAQL_CUSTOMER c " +
"where c.FIRST_NAME = ?1")
})
public class Customer {
Example query uses Native SQL to return all columns for table
Figure 69.18. Using Named Native Query
@SuppressWarnings("unchecked")
List<Object[]> rows = em.createNamedQuery("Customer.getCustomerRows")
.setParameter(1, "cat")
.getResultList();
assertEquals("unexpected customers found", 1, rows.size());
log.info("found customer:" + Arrays.toString(rows.get(0)));
select * from JPAQL_CUSTOMER c where c.FIRST_NAME = ? -found customer:[1, cat, inhat]
Table of Contents
"select" defines root query objects -- all path references must start from this set
"from" defines source of root query terms
"as" (optional) identifies a variable assignment of entity in from clause
"object()" (optional) identifies what is returned for the path expressed in select clause (e.g., object(), count()) -- left over from EJBQL
no "where" clause indicates all entities are selected
Figure 70.3. Using a JPA-QL Query
TypedQuery<Customer> query = em.createQuery(
"select object(c) from Customer as c",
Customer.class);
List<Customer> results = query.getResultList();
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ -found result:firstName=cat, lastName=inhat -found result:firstName=thing, lastName=one -found result:firstName=thing, lastName=two
Allows return of simple property
"c.lastName" is called a "path"
All paths based from root query terms
Single path selects return typed list of values
Figure 70.5. Using Non-Entity Query
TypedQuery<String> query = em.createQuery(
"select c.lastName from Customer c", String.class);
List<String> results = query.getResultList();
select customer0_.LAST_NAME as col_0_0_ from JPAQL_CUSTOMER customer0_ -lastName=inhat -lastName=one -lastName=two
Query result is a List<String> because "c.lastName" is a String
Select specifies multiple terms
Terms are expressed thru a path expression
Terms must be based off paths from root terms in the FROM (or JOIN) clause
Figure 70.7. Using Object[] Multi-select Query
TypedQuery<Object[]> query = em.createQuery(
"select c.firstName, c.hireDate from Clerk c", Object[].class);
List<Object[]> results = query.getResultList();
assertTrue("no results", results.size() > 0);
for(Object[] result : results) {
assertEquals("unexpected result length", 2, result.length);
String firstName = (String) result[0];
Date hireDate = (Date) result[1];
log.info("firstName=" + firstName + " hireDate=" + hireDate);
}
select clerk0_.FIRST_NAME as col_0_0_, clerk0_.HIRE_DATE as col_1_0_ from JPAQL_CLERK clerk0_ -firstName=Manny hireDate=1970-01-01 -firstName=Moe hireDate=1970-01-01 -firstName=Jack hireDate=1973-03-01
Query defined to return elements of select in Object[]
Figure 70.8. Multi-select Query with Tuple Example
select c.firstName as firstName, c.hireDate as hireDate from Clerk c
Aliases may be assigned to select terms for named-access to results
Figure 70.9. Using Tuple Multi-select Query
TypedQuery<Tuple> query = em.createQuery(
"select c.firstName as firstName, c.hireDate as hireDate from Clerk c", Tuple.class);
List<Tuple> results = query.getResultList();
assertTrue("no results", results.size() > 0);
for(Tuple result : results) {
assertEquals("unexpected result length", 2, result.getElements().size());
String firstName = result.get("firstName", String.class);
Date hireDate = result.get("hireDate", Date.class);
log.info("firstName=" + firstName + " hireDate=" + hireDate);
}
select clerk0_.FIRST_NAME as col_0_0_, clerk0_.HIRE_DATE as col_1_0_ from JPAQL_CLERK clerk0_ -firstName=Manny hireDate=1970-01-01 -firstName=Moe hireDate=1970-01-01 -firstName=Jack hireDate=1973-03-01
Query defined to return instances of Tuple class
Tuples provide access using
get(index) - simular to Object[]
get(index, Class<T> resultType) - typed access by index
get(alias) - access by alias
get(alias, Class<T> resultType) - typed access by alias
getElements() - access thru collection interface
Figure 70.10. Multi-select Query with Constructor Example
select new ejava.jpa.examples.query.Receipt(s.id, s.buyerId, s.date, s.amount) from Sale s
Individual elements of select are matched up against class constructor
Figure 70.11. Example ResultClass
package ejava.jpa.examples.query;
...
public class Receipt {
private long saleId;
private long customerId;
private Date date;
private double amount;
public Receipt(long saleId, long customerId, Date date, BigDecimal amount) {
this(customerId, saleId, date, amount.doubleValue());
}
public Receipt(long saleId, long customerId, Date date, double amount) {
this.customerId = customerId;
this.saleId = saleId;
this.date = date;
this.amount = amount;
}
...
Constructed class may be simple POJO -- no need to be an entity
Instances are not managed
Suitable for use as Data Transfer Objects (DTOs)
Figure 70.12. Using Constructor Multi-select Query
TypedQuery<Receipt> query = em.createQuery(
String.format("select new %s(", Receipt.class.getName()) +
"s.id,s.buyerId,s.date, s.amount) " +
"from Sale s", Receipt.class);
List<Receipt> results = query.getResultList();
for(Receipt receipt : results) {
assertNotNull("no receipt", receipt);
log.info("receipt=" + receipt);
}
select sale0_.SALE_ID as col_0_0_, sale0_.BUYER_ID as col_1_0_, sale0_.date as col_2_0_, sale0_.amount as col_3_0_ from JPAQL_SALE sale0_ -receipt=sale=1, customer=1, date=1998-04-10 10:13:35, amount=$100.00 -receipt=sale=2, customer=2, date=1999-06-11 14:15:10, amount=$150.00
Each row returned as instance of provided class
All paths based off root-level FROM (or JOIN) terms
Paths use dot (".") notation to change contexts
Paths -- used this way -- must always express a single element. Must use JOINs for paths involving collections
Paths that cross entity boundaries automatically add a join to SQL query
Figure 70.14. Using Single Element Path Expression
TypedQuery<Object[]> query = em.createQuery(
"select s.id, s.store.name from Sale s", Object[].class);
List<Object[]> results = query.getResultList();
assertTrue("no results", results.size() > 0);
for(Object[] result : results) {
assertEquals("unexpected result length", 2, result.length);
Long id = (Long) result[0];
String name = (String) result[1];
log.info("sale.id=" + id + ", sale.store.name=" + name);
}
select sale0_.SALE_ID as col_0_0_, store1_.name as col_1_0_ from JPAQL_SALE sale0_, ORMQL_STORE store1_ where sale0_.SALE_STORE=store1_.STORE_ID -sale.id=1, sale.store.name=Big Al's -sale.id=2, sale.store.name=Big Al's
Automatic INNER JOIN formed between Sale and Store because of the cross-entity path
Cannot directly navigate a XxxToMany relationship without a join
Figure 70.16. Correct Collection Path Expression
select sale.date from Clerk c INNER JOIN c.sales sale
Collection ("sales") is brought in as a root term ("sale") of the query through a JOIN expression
JOINs will match entities by their defined primary/foreign keys
INNER JOIN will return only those entities where there is a match
INNER JOIN is the default
Figure 70.19. Collection Path Expression SQL Output
select sale2_.date as col_0_0_ from JPAQL_CLERK clerk0_ inner join JPAQL_SALE_CLERK_LINK sales1_ on clerk0_.CLERK_ID=sales1_.CLERK_ID inner join JPAQL_SALE sale2_ on sales1_.SALE_ID=sale2_.SALE_ID -found result:1998-04-10 10:13:35.0 -found result:1999-06-11 14:15:10.0 -found result:1999-06-11 14:15:10.0
(Many-to-Many) Link table used during JOIN
Tables automatically joined on primary keys
Only Sales sold by our Clerks are returned
Figure 70.20. LEFT OUTER JOIN Example
select c.id, c.firstName, sale.amount from Clerk c LEFT OUTER JOIN c.sales sale
LEFT is the default for OUTER JOIN
Figure 70.21. Alternate LEFT OUTER JOIN Form
select c.id, c.firstName, sale.amount from Clerk c LEFT JOIN c.sales sale
LEFT OUTER JOIN will return root with or without related entities
Figure 70.22. LEFT OUTER JOIN Runtime SQL Output
select clerk0_.CLERK_ID as col_0_0_, clerk0_.FIRST_NAME as col_1_0_, sale2_.amount as col_2_0_ from JPAQL_CLERK clerk0_ left outer join JPAQL_SALE_CLERK_LINK sales1_ on clerk0_.CLERK_ID=sales1_.CLERK_ID left outer join JPAQL_SALE sale2_ on sales1_.SALE_ID=sale2_.SALE_ID -clerk.id=1, clerk.firstName=Manny, amount=100.00 -clerk.id=1, clerk.firstName=Manny, amount=150.00 -clerk.id=2, clerk.firstName=Moe, amount=150.00 -clerk.id=3, clerk.firstName=Jack, amount=null
(Many-to-Many) Link table used during JOIN
Tables automatically joined on primary keys
All clerks, with or without a Sale, are returned
Figure 70.23. Explicit Collection Path Example
select c from Sale s, Customer c where c.id = s.buyerId
Permits JOINs without relationship in entity model
Figure 70.24. Explicit Collection Path SQL Output
select customer1_.CUSTOMER_ID as CUSTOMER1_3_, customer1_.FIRST_NAME as FIRST2_3_, customer1_.LAST_NAME as LAST3_3_ from JPAQL_SALE sale0_ cross join JPAQL_CUSTOMER customer1_ where customer1_.CUSTOMER_ID=sale0_.BUYER_ID -found result:firstName=cat, lastName=inhat -found result:firstName=thing, lastName=one
Returns all Customers that are identified by a Sale
Figure 70.25. Example Query Resulting in Lazy Fetch
select s from Store s JOIN s.sales where s.name='Big Al''s'
A normal JOIN (implicit or explicit) may honor the fetch=LAZY property setting of the relation
Can be exactly what is desired
Can also cause problems or extra work if not desired
Figure 70.26. Example Entity with Lazy Fetch Declared for Relation
@Entity @Table(name="ORMQL_STORE")
public class Store {
...
@OneToMany(mappedBy="store",
cascade={CascadeType.REMOVE},
fetch=FetchType.LAZY)
private List<Sale> sales = new ArrayList<Sale>();
Sales are lazily fetched when obtaining Store
Figure 70.27. Example Lazy Fetch Problem
Store store = em2.createQuery(
"select s from Store s JOIN s.sales " +
"where s.name='Big Al''s'",
Store.class).getSingleResult();
em2.close();
try {
store.getSales().get(0).getAmount();
fail("did not trigger lazy initialization exception");
} catch (LazyInitializationException expected) {
log.info("caught expected exception:" + expected);
}
select store0_.STORE_ID as STORE1_0_, store0_.name as name0_ from ORMQL_STORE store0_ inner join JPAQL_SALE sales1_ on store0_.STORE_ID=sales1_.SALE_STORE where store0_.name='Big Al''s' limit ? -caught expected exception:org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ejava.jpa.examples.query.Store.sales, no session or session was closed
Accessing the Sale properties causes a LazyInitializationException when persistence context no longer active or accessible
Note that only a single row is required to be returned from the database for a fetch=LAZY relation. Although it requires more queries to the database, it eliminates duplicate parent information for each child row and can eliminate the follow-on query all together when not accessed.
Figure 70.28. Example Eager Fetch Query
select s from Store s JOIN FETCH s.sales where s.name='Big Al''s'
A JOIN FETCH used to eager load related entities as side-effect of query
Can be used as substitute for fetch=EAGER specification on relation
Figure 70.29. Example Eager Fetch SQL Output
select store0_.STORE_ID as STORE1_0_0_, sales1_.SALE_ID as SALE1_1_1_, store0_.name as name0_0_, sales1_.amount as amount1_1_, sales1_.BUYER_ID as BUYER3_1_1_, sales1_.date as date1_1_, sales1_.SALE_STORE as SALE5_1_1_, sales1_.SALE_STORE as SALE5_0_0__, sales1_.SALE_ID as SALE1_0__ from ORMQL_STORE store0_ inner join JPAQL_SALE sales1_ on store0_.STORE_ID=sales1_.SALE_STORE where store0_.name='Big Al''s'
Sales are eagerly fetched when obtaining Store
Note that adding JOIN FETCH to parent query causes the parent rows to be repeated for each eagerly loaded child row and eliminated by the provider. This requires fewer database queries but results in more (and redundant) data to be returned from the query.
Limits output to unique value combinations
Figure 70.31. Distinct Example Output
select distinct customer0_.LAST_NAME as col_0_0_ from JPAQL_CUSTOMER customer0_ -found result:two -found result:inhat -found result:one
Found three unique last names
Figure 70.33. Another Distinct Example Output
select distinct customer0_.FIRST_NAME as col_0_0_ from JPAQL_CUSTOMER customer0_ -found result:cat -found result:thing
Found two unique first names
Figure 71.1. Example Equality Test
select c from Customer c where c.firstName='cat'
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME='cat' -found result:firstName=cat, lastName=inhat
Return entities where there is an equality match
Figure 71.2. Escaping Special Characters
select s from Store s where s.name='Big Al''s'
select store0_.STORE_ID as STORE1_0_, store0_.name as name0_ from ORMQL_STORE store0_ where store0_.name='Big Al''s' ... -found result:name=Big Al's, sales(2)={1, 2, }
Escaped special character is passed through to the database
Figure 71.3. Like Test Literal
select c from Clerk c where c.firstName like 'M%'
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME like 'M%' ... -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, } -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }
Figure 71.5. Using Like Test Literal Parameter
TypedQuery<T> query = em.createQuery(ejbqlString, resultType);
query.setParameter("firstName", "M%");
List<T> objects = query.getResultList();
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME like ? -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, } -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }
Figure 71.6. Like Test Concatenated String
select c from Clerk c where c.firstName like concat(:firstName,'%')
Figure 71.7. Using Like Test Concatenated String
TypedQuery<T> query = em.createQuery(ejbqlString, resultType);
query.setParameter("firstName", "M");
List<T> objects = query.getResultList();
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME like (?||'%') -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, } -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }
Figure 71.8. Like Test Single Character Wildcard
select c from Clerk c where c.firstName like '_anny'
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME like '_anny' -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, }
Figure 71.10. Using Formula
String jpaql = "select count(s) from Sale s " +
"where (s.amount * :tax) > :amount";
TypedQuery<Number> query = em.createQuery(jpaql, Number.class)
.setParameter("amount", new BigDecimal(10.00));
//keep raising taxes until somebody pays $10.00 in tax
double tax = 0.05;
for (;query.setParameter("tax", new BigDecimal(tax))
.getSingleResult().intValue()==0;
tax += 0.01) {
log.debug("tax=" + NumberFormat.getPercentInstance().format(tax));
}
log.info("raise taxes to: " + NumberFormat.getPercentInstance().format(tax));
select count(sale0_.SALE_ID) as col_0_0_ from JPAQL_SALE sale0_ where sale0_.amount*?>? limit ? -tax=5% select count(sale0_.SALE_ID) as col_0_0_ from JPAQL_SALE sale0_ where sale0_.amount*?>? limit ? -tax=6% select count(sale0_.SALE_ID) as col_0_0_ from JPAQL_SALE sale0_ where sale0_.amount*?>? limit ? -raise taxes to: 7%
Figure 71.11. Logic Operator Example
select c from Customer c where (c.firstName='cat' AND c.lastName='inhat') OR c.firstName='thing'
Figure 71.12. Logic Operator Example Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME='cat' and customer0_.LAST_NAME='inhat' or customer0_.FIRST_NAME='thing' -found result:firstName=cat, lastName=inhat -found result:firstName=thing, lastName=one -found result:firstName=thing, lastName=two
Figure 71.13. Another Logic Operator Example
select c from Customer c where (NOT (c.firstName='cat' AND c.lastName='inhat')) OR c.firstName='thing'
Figure 71.14. Another Logic Operator Example Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME<>'cat' or customer0_.LAST_NAME<>'inhat' or customer0_.FIRST_NAME='thing' -found result:firstName=thing, lastName=one -found result:firstName=thing, lastName=two
Must compare values
Of same type
Of legal promotion type
Can compare 123:int to 123:long
Cannot compare 123:int to "123":string
Can compare entities
Compare entities and not primary/foreign key values
Figure 71.16. Using Entity Equality Query
//get a clerk entity
Clerk clerk = em.createQuery(
"select c from Clerk c where c.firstName = 'Manny'",
Clerk.class)
.getSingleResult();
//find all sales that involve this clerk
List<Sale> sales = em.createQuery(
"select s from Sale s " +
"JOIN s.clerks c " +
"where c = :clerk",
Sale.class)
.setParameter("clerk", clerk)
.getResultList();
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME='Manny' limit ?
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ inner join JPAQL_SALE_CLERK_LINK clerks1_ on sale0_.SALE_ID=clerks1_.SALE_ID inner join JPAQL_CLERK clerk2_ on clerks1_.CLERK_ID=clerk2_.CLERK_ID where clerk2_.CLERK_ID=? ... -found=date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, } ... -found=date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Figure 71.17. Example Between Query
select s from Sale s where s.amount BETWEEN :low AND :high
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.amount between ? and ? ... -found result:date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, }
Figure 71.18. Another Example Between Query
select s from Sale s where s.amount NOT BETWEEN :low AND :high
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.amount not between ? and ? ... -found result:date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Can be used to test for unassigned value or relationship
Figure 71.19. Example Test for Null
select s from Sale s where s.store IS NULL
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.SALE_STORE is null
Figure 71.20. Example Test for Not Null
select s from Sale s where s.store IS NOT NULL
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.SALE_STORE is not null ... -found result:date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, } ... -found result:date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Can be used to test for an empty collection
Figure 71.21. Example Empty Collection Test
select c from Clerk c where c.sales IS EMPTY
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where not (exists ( select sale2_.SALE_ID from JPAQL_SALE_CLERK_LINK sales1_, JPAQL_SALE sale2_ where clerk0_.CLERK_ID=sales1_.CLERK_ID and sales1_.SALE_ID=sale2_.SALE_ID)) ... -found result:firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}
Sub-select returns values from collection under test
Outer query tests for no existing (EMPTY)values
Figure 71.22. Example Non-Empty Test
select c from Clerk c where c.sales IS NOT EMPTY
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where exists ( select sale2_.SALE_ID from JPAQL_SALE_CLERK_LINK sales1_, JPAQL_SALE sale2_ where clerk0_.CLERK_ID=sales1_.CLERK_ID and sales1_.SALE_ID=sale2_.SALE_ID ) ... -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, } ... -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }
Sub-select returns values from collection under test
Outer query tests for existing (NOT EMPTY)values
Can be used to determine membership in a collection
Figure 71.23. Example Membership Test
select c from Clerk c where c.firstName = 'Manny'
select s from Sale s where :clerk MEMBER OF s.clerks
Defines a shorthand for a subquery
Figure 71.24. Using Membership Test
//get a clerk entity
Clerk clerk = em.createQuery(
"select c from Clerk c where c.firstName = 'Manny'",
Clerk.class)
.getSingleResult();
//find all sales that involve this clerk
List<Sale> sales = em.createQuery(
"select s from Sale s " +
"where :clerk MEMBER OF s.clerks",
Sale.class)
.setParameter("clerk", clerk)
.getResultList();
Figure 71.25. Using Membership Test Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME='Manny' limit ?
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where ? in ( select clerk2_.CLERK_ID from JPAQL_SALE_CLERK_LINK clerks1_, JPAQL_CLERK clerk2_ where sale0_.SALE_ID=clerks1_.SALE_ID and clerks1_.CLERK_ID=clerk2_.CLERK_ID ) ... -found=date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, } ... -found=date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Useful when query cannot be expressed through JOINs
Figure 71.26. Example Subquery
select c from Customer c where c.id IN (select s.buyerId from Sale s where s.amount > 100)
Figure 71.27. Example Subquery Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.CUSTOMER_ID in ( select sale1_.BUYER_ID from JPAQL_SALE sale1_ where sale1_.amount>100 ) -found result:firstName=thing, lastName=one
All existing values must meet criteria (i.e., no value may fail criteria)
Zero values is the lack of failure (i.e., meets criteria)
Figure 71.28. Example ALL Query
select c from Clerk c where 125 < ALL (select s.amount from c.sales s)
List all clerks that have all sales above $125.00 or none at all
-or- List all clerks with no sale <= $125.00
Figure 71.29. Example ALL Query Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where 125<all ( select sale2_.amount from JPAQL_SALE_CLERK_LINK sales1_, JPAQL_SALE sale2_ where clerk0_.CLERK_ID=sales1_.CLERK_ID and sales1_.SALE_ID=sale2_.SALE_ID ) ... -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, } ... -found result:firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}
Manny excluded because has 1 sale below $125.00
Moe included because has only $150.00 sale
Jack included because has no sales that fail criteria
Figure 71.30. Another ALL Query Example
select c from Clerk c where 125 > ALL (select s.amount from c.sales s)
List all clerks that have all sales below $125.00 or none at all
-or- List all clerks with no sale >= $125.00
Figure 71.31. Another ALL Query Example Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where 125>all ( select sale2_.amount from JPAQL_SALE_CLERK_LINK sales1_, JPAQL_SALE sale2_ where clerk0_.CLERK_ID=sales1_.CLERK_ID and sales1_.SALE_ID=sale2_.SALE_ID ) -found result:firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}
Manny excluded because has 1 sale above $125.00
Moe excluded because has only $150.00 sale
Jack included because has no sales that fail criteria
Any matching value meets criteria (i.e., one match and you are in)
Zero values fails to meet the criteria (i.e., must have at least one matching value)
Figure 71.32. Example ANY Query
select c from Clerk c where 125 < ANY (select s.amount from c.sales s)
List all clerks that have at least one sale above $125.00
Figure 71.33. Example ANY Query Runtime Output
-executing query:select c from Clerk c where 125 < ANY (select s.amount from c.sales s) select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where 125<any ( select sale2_.amount from JPAQL_SALE_CLERK_LINK sales1_, JPAQL_SALE sale2_ where clerk0_.CLERK_ID=sales1_.CLERK_ID and sales1_.SALE_ID=sale2_.SALE_ID ) ... -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, } ... -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }
Manny included because has 1 sale above $125.00
Moe included because $150.00 sale qualifies him as well
Jack excluded because has no sales that meet criteria
Figure 71.34. Another Example ANY Query
select c from Clerk c where 125 > ANY (select s.amount from c.sales s)
List all clerks that have at least one sale below $125.00
Figure 71.35. Another Example ANY Query Runtime Output
-executing query:select c from Clerk c where 125 > ANY (select s.amount from c.sales s) select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where 125>any ( select sale2_.amount from JPAQL_SALE_CLERK_LINK sales1_, JPAQL_SALE sale2_ where clerk0_.CLERK_ID=sales1_.CLERK_ID and sales1_.SALE_ID=sale2_.SALE_ID ) -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, }
Manny included because has 1 sale below $125.00
Moe excluded because his only $150.00 sale above criteria
Jack excluded because has no sales that meet criteria
Figure 72.1. Example String Compare
select c from Customer c where c.firstName='CAT'
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME='CAT'
No rows found because 'CAT' does not match anything because of case
Figure 72.2. Example LOWER Function
select c from Customer c where c.firstName=LOWER('CAT')
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=lower('CAT') -found result:firstName=cat, lastName=inhat
One customer found because case-sensitive compare now correct
Figure 72.3. Example UPPER Function
select UPPER(c.firstName) from Customer c where c.firstName=LOWER('CAT')
select upper(customer0_.FIRST_NAME) as col_0_0_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=lower('CAT') -found result:CAT
First name of customer located returned in upper case
Figure 72.4. Example TRIM Function
select TRIM(LEADING 'c' FROM c.firstName) from Customer c where c.firstName='cat')
select trim(LEADING 'c' FROM customer0_.FIRST_NAME) as col_0_0_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME='cat' -found result:at
Customer's name, excluding initial 'c' character, returned
Figure 72.5. Example CONCAT Function]
select c from Customer c where CONCAT(CONCAT(c.firstName,' '),c.lastName) ='cat inhat')
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where ( (customer0_.FIRST_NAME||' ')||customer0_.LAST_NAME)='cat inhat' -found result:firstName=cat, lastName=inhat
Customer located after concatenation of fields yields match
Figure 72.6. Example LENGTH Function
select c from Customer c where LENGTH(c.firstName) = 3
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where length(customer0_.FIRST_NAME)=3 -found result:firstName=cat, lastName=inhat
Customer found where length of firstName matches specified length criteria
Figure 72.7. Example LOCATE Function
select c from Customer c where LOCATE('cat',c.firstName,2) > 0
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where locate('cat', customer0_.FIRST_NAME, 2)>0
No firstName found with 'cat' starting at position=2
Figure 72.8. Another Example LOCATE Function
select c from Customer c where LOCATE('at',c.firstName,2) > 1
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where locate('at', customer0_.FIRST_NAME, 2)>1 -found result:firstName=cat, lastName=inhat
firstName found with 'at' starting at a position 2
Figure 72.9. Example SUBSTRING Function
select SUBSTRING(c.firstName,2,2) from Customer c where c.firstName = 'cat'
select substring(customer0_.FIRST_NAME, 2, 2) as col_0_0_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME='cat' -found result:at
Return the two character substring of firstName starting at position two
Figure 72.10. Another Example SUBSTRING Function
select c from Customer c where SUBSTRING(c.firstName,2,2) = 'at'
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where substring(customer0_.FIRST_NAME, 2, 2)='at' -found result:firstName=cat, lastName=inhat
Find the customer with a two characters starting a position two of firstName equaling 'at'
CURRENT_DATE
CURRENT_TIME
CURRENT_TIMESTAMP
Figure 72.11. CURRENT_DATE Query Example
select s from Sale s where s.date < CURRENT_DATE
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.date<CURRENT_DATE ... -found result:date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, } ... -found result:date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Located two Sales that occurred prior to today's date
Figure 72.12. Another CURRENT_DATE Query Example
select s from Sale s where s.date = CURRENT_DATE
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.date=CURRENT_DATE
Located no sales on today's date
Figure 72.13. Using Bulk Update to Change Date
update Sale s set s.date = CURRENT_DATE
update JPAQL_SALE set date=CURRENT_DATE
Update all sales to today
Figure 72.14. Retrying CURRENT_DATE Query After Bulk Update
select s from Sale s where s.date = CURRENT_DATE
-executing query:select s from Sale s where s.date = CURRENT_DATE select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.date=CURRENT_DATE ... -found result:date=2013-06-05 00:00:00, amount=$100.00, buyer=1, clerks(1)={1, } ... -found result:date=2013-06-05 00:00:00, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Now locating sales for today's date
Bulk commands (i.e., update) invalidate cached entities. You must refresh their state with the database or detach/clear them from the persistence context to avoid using out-dated information.
ASC - ascending order
DESC - descending order
Figure 72.15. Example Order By
select s from Sale s ORDER BY s.amount ASC
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ order by sale0_.amount ASC ... -found result:date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, } ... -found result:date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Note the ASC order on amount
Figure 72.16. Another Example Order By
select s from Sale s ORDER BY s.amount DESC
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ order by sale0_.amount DESC -found result:date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, } -found result:date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, }
Note the DESC order on amount
Figure 72.17. Example COUNT Aggregate Function
select COUNT(s) from Sale s
select count(*) as col_0_0_ from JPAQL_SALE sale0_ -found result:2
Figure 72.18. Example MIN Aggregate Function
select min(s.amount) from Sale s
select min(sale0_.amount) as col_0_0_ from JPAQL_SALE sale0_ -found result:100.00
Figure 72.19. Example MAX Aggregate Function
select max(s.amount) from Sale s
select max(sale0_.amount) as col_0_0_ from JPAQL_SALE sale0_ -found result:150.00
Figure 72.20. Example SUM Aggregate Function
select sum(s.amount) from Sale s
select sum(sale0_.amount) as col_0_0_ from JPAQL_SALE sale0_ -found result:250.00
Figure 72.21. Example AVE Aggregate Function
select ave(s.amount) from Sale s
select avg(cast(sale0_.amount as double)) as col_0_0_ from JPAQL_SALE sale0_ -found result:125.0
Get count of sales for each clerk
Figure 72.23. Example Group By Runtime Output
select clerk0_.CLERK_ID as col_0_0_, count(sale2_.SALE_ID) as col_1_0_, clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ left outer join JPAQL_SALE_CLERK_LINK sales1_ on clerk0_.CLERK_ID=sales1_.CLERK_ID left outer join JPAQL_SALE sale2_ on sales1_.SALE_ID=sale2_.SALE_ID group by clerk0_.CLERK_ID ... -found=[firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, }, 2] ... -found=[firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }, 1] ... -found=[firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}, 0]
Figure 72.24. Example Having Aggregate Function
select c, COUNT(s) from Clerk c LEFT JOIN c.sales s GROUP BY c HAVING COUNT(S) <= 1
Provide a list of Clerks and their count of Sales for counts <= 1
Figure 72.25. Example Having Aggregate Function Runtime Output
select clerk0_.CLERK_ID as col_0_0_, count(sale2_.SALE_ID) as col_1_0_, clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ left outer join JPAQL_SALE_CLERK_LINK sales1_ on clerk0_.CLERK_ID=sales1_.CLERK_ID left outer join JPAQL_SALE sale2_ on sales1_.SALE_ID=sale2_.SALE_ID group by clerk0_.CLERK_ID having count(sale2_.SALE_ID)<=1 ... -found=[firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }, 1] ... -found=[firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}, 0]
Wed matched on Moe (1 sale) and Jack (0 sales)
Table of Contents
Following sections contain but may not show the following code in all cases
Figure 73.1. Boiler-plate code
CriteriaBuilder cb = em.getCriteriaBuilder();
//example-specific criteria API definition goes here
CriteriaQuery<T> qdef = ...
//repeated display loop eliminated
TypedQuery<T> query = em.createQuery(qdef);
List<T> objects = query.getResultList();
for(T o: objects) {
log.info("found result:" + o);
}
Get CriteriaBuilder from EntityManager
Build example-specific query definition in CriteriaQuery using CriteriaBuilder
Execute Query/Print results
Figure 73.3. Criteria API Definition
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c);
"from"
defines source of root query terms
returns object leveraged in query body
"select" defines root query objects -- all path references must start from this set
no "where" clause indicates all entities are selected
Figure 73.4. In Programming Context
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c);
TypedQuery<Customer> query = em.createQuery(qdef);
List<Customer> results = query.getResultList();
Figure 73.5. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ -found result:firstName=cat, lastName=inhat -found result:firstName=thing, lastName=one -found result:firstName=thing, lastName=two
Figure 73.7. Criteria API Definition
CriteriaQuery<String> qdef = cb.createQuery(String.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c.<String>get("lastName"));
Allows return of simple property
c.get("lastName") is called a "path"
All paths based from root query terms (thus requirement for Root<Customer> c object)
Single path selects return typed list of values
Figure 73.8. In Programming Context
CriteriaQuery<String> qdef = cb.createQuery(String.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c.<String>get("lastName"));
TypedQuery<String> query = em.createQuery(qdef);
List<String> results = query.getResultList();
Query result is a List<String> because "c.lastName" is a String
Figure 73.9. Runtime Output
select customer0_.LAST_NAME as col_0_0_ from JPAQL_CUSTOMER customer0_ -lastName=inhat -lastName=one -lastName=two
Figure 73.11. Criteria API Definition
CriteriaQuery<Object[]> qdef = cb.createQuery(Object[].class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(cb.array(c.get("firstName"), c.get("hireDate")));
Select specifies multiple terms within array()
Terms are expressed thru a path expression
Terms must be based off paths from root terms in the FROM (or JOIN) clause -- thus why Root<Clerk> c was retained from cb.from() call
Figure 73.12. In Programming Context
CriteriaQuery<Object[]> qdef = cb.createQuery(Object[].class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(cb.array(c.get("firstName"), c.get("hireDate")));
TypedQuery<Object[]> query = em.createQuery(qdef);
List<Object[]> results = query.getResultList();
assertTrue("no results", results.size() > 0);
for(Object[] result : results) {
assertEquals("unexpected result length", 2, result.length);
String firstName = (String) result[0];
Date hireDate = (Date) result[1];
log.info("firstName=" + firstName + " hireDate=" + hireDate);
}
Query defined to return elements of select in Object[]
Figure 73.13. Runtime Output
select clerk0_.FIRST_NAME as col_0_0_, clerk0_.HIRE_DATE as col_1_0_ from JPAQL_CLERK clerk0_ -firstName=Manny hireDate=1970-01-01 -firstName=Moe hireDate=1970-01-01 -firstName=Jack hireDate=1973-03-01
Figure 73.15. Criteria API Definition
CriteriaQuery<Tuple> qdef = cb.createTupleQuery();
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(cb.tuple(
c.get("firstName").alias("firstName"),
c.get("hireDate").alias("hireDate")));
Aliases may be assigned to select terms for named-access to results
Figure 73.16. In Programming Context
CriteriaQuery<Tuple> qdef = cb.createTupleQuery();
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(cb.tuple(
c.get("firstName").alias("firstName"),
c.get("hireDate").alias("hireDate")));
TypedQuery<Tuple> query = em.createQuery(qdef);
List<Tuple> results = query.getResultList();
assertTrue("no results", results.size() > 0);
for(Tuple result : results) {
assertEquals("unexpected result length", 2, result.getElements().size());
String firstName = result.get("firstName", String.class);
Date hireDate = result.get("hireDate", Date.class);
log.info("firstName=" + firstName + " hireDate=" + hireDate);
}
Query defined to return instances of Tuple class
Tuples provide access using
get(index) - simular to Object[]
get(index, Class<T> resultType) - typed access by index
get(alias) - access by alias
get(alias, Class<T> resultType) - typed access by alias
getElements() - access thru collection interface
Figure 73.17. Runtime Output
select clerk0_.FIRST_NAME as col_0_0_, clerk0_.HIRE_DATE as col_1_0_ from JPAQL_CLERK clerk0_ -firstName=Manny hireDate=1970-01-01 -firstName=Moe hireDate=1970-01-01 -firstName=Jack hireDate=1973-03-01
Figure 73.18. Equivalent JPAQL
select new ejava.jpa.examples.query.Receipt(s.id,s.buyerId,s.date, s.amount) from Sale s
Figure 73.19. Criteria API Definition
CriteriaQuery<Receipt> qdef = cb.createQuery(Receipt.class);
Root<Sale> s = qdef.from(Sale.class);
qdef.select(cb.construct(
Receipt.class,
s.get("id"),
s.get("buyerId"),
s.get("date"),
s.get("amount")));
Individual elements of select() are matched up against class constructor
Figure 73.20. In Programming Context
CriteriaQuery<Receipt> qdef = cb.createQuery(Receipt.class);
Root<Sale> s = qdef.from(Sale.class);
qdef.select(cb.construct(
Receipt.class,
s.get("id"),
s.get("buyerId"),
s.get("date"),
s.get("amount")));
TypedQuery<Receipt> query = em.createQuery(qdef);
List<Receipt> results = query.getResultList();
assertTrue("no results", results.size() > 0);
for(Receipt receipt : results) {
assertNotNull("no receipt", receipt);
log.info("receipt=" + receipt);
}
Constructed class may be simple POJO -- no need to be an entity
Instances are not managed
Suitable for use as Data Transfer Objects (DTOs)
Figure 73.21. Runtime Output
select sale0_.SALE_ID as col_0_0_, sale0_.BUYER_ID as col_1_0_, sale0_.date as col_2_0_, sale0_.amount as col_3_0_ from JPAQL_SALE sale0_ -receipt=sale=1, customer=1, date=1998-04-10 10:13:35, amount=$100.00 -receipt=sale=2, customer=2, date=1999-06-11 14:15:10, amount=$150.00
Figure 73.23. Criteria API Definition
CriteriaQuery<Object[]> qdef = cb.createQuery(Object[].class);
Root<Sale> s = qdef.from(Sale.class);
qdef.select(cb.array(s.get("id"),
s.get("store").get("name")));
All paths based off root-level FROM (or JOIN) terms
Paths use call chaining to change contexts
Paths -- used this way -- must always express a single element. Must use JOINs for paths involving collections
All paths based off root-level FROM (or JOIN) terms
Figure 73.24. In Programming Context
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Object[]> qdef = cb.createQuery(Object[].class);
Root<Sale> s = qdef.from(Sale.class);
qdef.select(cb.array(s.get("id"),
s.get("store").get("name")));
TypedQuery<Object[]> query = em.createQuery(qdef);
List<Object[]> results = query.getResultList();
assertTrue("no results", results.size() > 0);
for(Object[] result : results) {
assertEquals("unexpected result length", 2, result.length);
Long id = (Long) result[0];
String name = (String) result[1];
log.info("sale.id=" + id + ", sale.store.name=" + name);
}
Figure 73.25. Runtime Output
select sale0_.SALE_ID as col_0_0_, store1_.name as col_1_0_ from JPAQL_SALE sale0_, ORMQL_STORE store1_ where sale0_.SALE_STORE=store1_.STORE_ID -sale.id=1, sale.store.name=Big Al's -sale.id=2, sale.store.name=Big Al's
Automatic INNER JOIN formed between Sale and Store because of the cross-entity path
Figure 73.27. Criteria API Definition
CriteriaQuery<Date> qdef = cb.createQuery(Date.class);
Root<Clerk> c = qdef.from(Clerk.class);
Join<Clerk, Sale> sale = c.join("sales", JoinType.INNER);
qdef.select(sale.<Date>get("date"));
Collection is brought in as a root term of the query through a JOIN expression
JOINs will match entities by their defined primary/foreign keys
INNER JOIN will return only those entities where there is a match
INNER JOIN is default JoinType when none specified
Figure 73.28. Runtime Output
select sale2_.date as col_0_0_ from JPAQL_CLERK clerk0_ inner join JPAQL_SALE_CLERK_LINK sales1_ on clerk0_.CLERK_ID=sales1_.CLERK_ID inner join JPAQL_SALE sale2_ on sales1_.SALE_ID=sale2_.SALE_ID -found result:1998-04-10 10:13:35.0 -found result:1999-06-11 14:15:10.0 -found result:1999-06-11 14:15:10.0
(Many-to-Many) Link table used during JOIN
Tables automatically joined on primary keys
Only Sales sold by our Clerks are returned
Figure 73.29. Equivalent JPAQL
select c.id, c.firstName, sale.amount from Clerk c LEFT JOIN c.sales sale
Figure 73.30. Criteria API Definition
CriteriaQuery<Object[]> qdef = cb.createQuery(Object[].class);
Root<Clerk> c = qdef.from(Clerk.class);
Join<Clerk, Sale> sale = c.join("sales", JoinType.LEFT);
qdef.select(cb.array(c.get("id"),
c.get("firstName"),
sale.get("amount")));
LEFT OUTER JOIN will return root with or without related entities
Figure 73.31. Runtime Output
select clerk0_.CLERK_ID as col_0_0_, clerk0_.FIRST_NAME as col_1_0_, sale2_.amount as col_2_0_ from JPAQL_CLERK clerk0_ left outer join JPAQL_SALE_CLERK_LINK sales1_ on clerk0_.CLERK_ID=sales1_.CLERK_ID left outer join JPAQL_SALE sale2_ on sales1_.SALE_ID=sale2_.SALE_ID -clerk.id=1, clerk.firstName=Manny, amount=100.00 -clerk.id=1, clerk.firstName=Manny, amount=150.00 -clerk.id=2, clerk.firstName=Moe, amount=150.00 -clerk.id=3, clerk.firstName=Jack, amount=null
(Many-to-Many) Link table used during JOIN
Tables automatically joined on primary keys
All clerks, with or without a Sale, are returned
Figure 73.33. Criteria API Definition
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Sale> s = qdef.from(Sale.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c)
.where(cb.equal(c.get("id"), s.get("buyerId")));
Permits JOINs without relationship in entity model
Figure 73.34. Runtime Output
select customer1_.CUSTOMER_ID as CUSTOMER1_3_, customer1_.FIRST_NAME as FIRST2_3_, customer1_.LAST_NAME as LAST3_3_ from JPAQL_SALE sale0_ cross join JPAQL_CUSTOMER customer1_ where customer1_.CUSTOMER_ID=sale0_.BUYER_ID -found result:firstName=cat, lastName=inhat -found result:firstName=thing, lastName=one
Returns all Customers that are identified by a Sale
Figure 73.36. Criteria API Definition
CriteriaQuery<Store> qdef = cb.createQuery(Store.class);
Root<Store> s = qdef.from(Store.class);
s.join("sales");
qdef.select(s)
.where(cb.equal(s.get("name"), "Big Al's"));
A normal JOIN (implicit or explicit) may honor the fetch=LAZY property setting of the relation
Can be exactly what is desired
Can also cause problems or extra work if not desired
Figure 73.37. In Programming Context
@Entity @Table(name="ORMQL_STORE")
public class Store {
...
@OneToMany(mappedBy="store",
cascade={CascadeType.REMOVE},
fetch=FetchType.LAZY)
private List<Sale> sales = new ArrayList<Sale>();
Sales are lazily fetched when obtaining Store
Figure 73.38. In Programming Context (con.t)
CriteriaBuilder cb = em2.getCriteriaBuilder();
CriteriaQuery<Store> qdef = cb.createQuery(Store.class);
Root<Store> s = qdef.from(Store.class);
s.join("sales");
qdef.select(s)
.where(cb.equal(s.get("name"), "Big Al's"));
Store store = em2.createQuery(qdef).getSingleResult();
em2.close();
try {
store.getSales().get(0).getAmount();
fail("did not trigger lazy initialization exception");
} catch (LazyInitializationException expected) {
log.info("caught expected exception:" + expected);
}
Accessing the Sale properties causes a LazyInitializationException when persistence context no longer active or accessible
Figure 73.39. Runtime Output
select store0_.STORE_ID as STORE1_0_, store0_.name as name0_ from ORMQL_STORE store0_ inner join JPAQL_SALE sales1_ on store0_.STORE_ID=sales1_.SALE_STORE where store0_.name=? limit ? -caught expected exception:org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: ejava.jpa.examples.query.Store.sales, no session or session was closed
Note that only a single row is required to be returned from the database for a fetch=LAZY relation. Although it requires more queries to the database, it eliminates duplicate parent information for each child row and can eliminate the follow-on query all together when not accessed.
Figure 73.41. Criteria API Definition
CriteriaQuery<Store> qdef = cb.createQuery(Store.class);
Root<Store> s = qdef.from(Store.class);
s.fetch("sales");
qdef.select(s)
.where(cb.equal(s.get("name"), "Big Al's"));
A JOIN FETCH used to eager load related entities as side-effect of query
Can be used as substitute for fetch=EAGER specification on relation
Figure 73.42. Runtime Output
select store0_.STORE_ID as STORE1_0_0_, sales1_.SALE_ID as SALE1_1_1_, store0_.name as name0_0_, sales1_.amount as amount1_1_, sales1_.BUYER_ID as BUYER3_1_1_, sales1_.date as date1_1_, sales1_.SALE_STORE as SALE5_1_1_, sales1_.SALE_STORE as SALE5_0_0__, sales1_.SALE_ID as SALE1_0__ from ORMQL_STORE store0_ inner join JPAQL_SALE sales1_ on store0_.STORE_ID=sales1_.SALE_STORE where store0_.name=?
Sales are eagerly fetched when obtaining Store
Note that adding JOIN FETCH to parent query causes the parent rows to be repeated for each eagerly loaded child row and eliminated by the provider. This requires fewer database queries but results in more (and redundant) data to be returned from the query.
Figure 73.44. Criteria API Definition
CriteriaQuery<String> qdef = cb.createQuery(String.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c.<String>get("lastName"))
.distinct(true);
Limits output to unique value combinations
Figure 73.45. Runtime Output
select distinct customer0_.LAST_NAME as col_0_0_ from JPAQL_CUSTOMER customer0_ -found result:two -found result:inhat -found result:one
Figure 73.47. Criteria API Definition
CriteriaQuery<String> qdef = cb.createQuery(String.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c.<String>get("firstName"))
.distinct(true);
Figure 73.48. Runtime Output
select distinct customer0_.FIRST_NAME as col_0_0_ from JPAQL_CUSTOMER customer0_ -found result:cat -found result:thing
Figure 74.2. Criteria API Definition
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c)
.where(cb.equal(c.get("firstName"), "cat"));
Return entities where there is an equality match
Figure 74.3. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=? -found result:firstName=cat, lastName=inhat
JPAQL requires special characters to be escaped
Figure 74.5. Criteria API Definition
CriteriaQuery<Store> qdef = cb.createQuery(Store.class);
Root<Store> s = qdef.from(Store.class);
qdef.select(s)
.where(cb.equal(s.get("name"), "Big Al's"));
Literal values automatically escaped
Figure 74.6. Runtime Output
select store0_.STORE_ID as STORE1_0_, store0_.name as name0_ from ORMQL_STORE store0_ where store0_.name=? ... -found result:name=Big Al's, sales(2)={1, 2, }
Figure 74.8. Criteria API Definition
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.like(c.<String>get("firstName"), "M%"));
Figure 74.9. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME like ? ... -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, } -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }
Figure 74.11. Criteria API Definition
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.like(c.<String>get("firstName"),
cb.parameter(String.class, "firstName")));
Figure 74.12. In Programming Context
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.like(c.<String>get("firstName"),
cb.parameter(String.class, "firstName")));
TypedQuery<Clerk> query = em.createQuery(qdef)
.setParameter("firstName", "M%");
List<Clerk> results = query.getResultList();
Figure 74.13. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME like ? -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, } -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }
Figure 74.15. Criteria API Definition
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.like(c.<String>get("firstName"),
cb.concat(cb.parameter(String.class, "firstName"), "%")));
Figure 74.16. In Programming Context
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.like(c.<String>get("firstName"),
cb.concat(cb.parameter(String.class, "firstName"), "%")));
TypedQuery<Clerk> query = em.createQuery(qdef)
.setParameter("firstName", "M");
List<Clerk> results = query.getResultList();
Figure 74.17. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME like (?||?) -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, } -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }
Figure 74.19. Criteria API Definition
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.like(c.<String>get("firstName"),"_anny"));
Figure 74.20. In Programming Context
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.like(c.<String>get("firstName"),"_anny"));
TypedQuery<Clerk> query = em.createQuery(qdef);
List<Clerk> results = query.getResultList();
Figure 74.21. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME like ? -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, }
Figure 74.23. Criteria API Definition
CriteriaQuery<Number> qdef = cb.createQuery(Number.class);
Root<Sale> s = qdef.from(Sale.class);
qdef.select(cb.count(s))
.where(cb.greaterThan(
cb.prod(s.<BigDecimal>get("amount"), cb.parameter(BigDecimal.class, "tax")),
new BigDecimal(10.0)));
Figure 74.24. In Programming Context
CriteriaQuery<Number> qdef = cb.createQuery(Number.class);
//select count(s) from Sale s
//where (s.amount * :tax) > :amount"
Root<Sale> s = qdef.from(Sale.class);
qdef.select(cb.count(s))
.where(cb.greaterThan(
cb.prod(s.<BigDecimal>get("amount"), cb.parameter(BigDecimal.class, "tax")),
new BigDecimal(10.0)));
TypedQuery<Number> query = em.createQuery(qdef);
//keep raising taxes until somebody pays $10.00 in tax
double tax = 0.05;
for (;query.setParameter("tax", new BigDecimal(tax))
.getSingleResult().intValue()==0;
tax += 0.01) {
log.debug("tax=" + NumberFormat.getPercentInstance().format(tax));
}
log.info("raise taxes to: " + NumberFormat.getPercentInstance().format(tax));
Figure 74.25. Runtime Output
select count(*) as col_0_0_ from JPAQL_SALE sale0_ where sale0_.amount*?>10 limit ? -tax=5% select count(*) as col_0_0_ from JPAQL_SALE sale0_ where sale0_.amount*?>10 limit ? -tax=6% select count(*) as col_0_0_ from JPAQL_SALE sale0_ where sale0_.amount*?>10 limit ? -raise taxes to: 7%
Figure 74.26. Equivalent JPAQL
select c from Customer c where (c.firstName='cat' AND c.lastName='inhat') OR c.firstName='thing'
Figure 74.27. Criteria API Definition
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c)
.where(cb.or(
cb.and(cb.equal(c.get("firstName"), "cat"),
cb.equal(c.get("lastName"), "inhat")),
cb.equal(c.get("firstName"), "thing")));
Figure 74.28. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=? and customer0_.LAST_NAME=? or customer0_.FIRST_NAME=? -found result:firstName=cat, lastName=inhat -found result:firstName=thing, lastName=one -found result:firstName=thing, lastName=two
Figure 74.29. Equivalent JPAQL
select c from Customer c where (NOT (c.firstName='cat' AND c.lastName='inhat')) OR c.firstName='thing'
Figure 74.30. Criteria API Definition
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c)
.where(cb.or(
cb.not(cb.and(cb.equal(c.get("firstName"), "cat"),
cb.equal(c.get("lastName"), "inhat"))),
cb.equal(c.get("firstName"), "thing"))
);
Figure 74.31. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME<>? or customer0_.LAST_NAME<>? or customer0_.FIRST_NAME=? -found result:firstName=thing, lastName=one -found result:firstName=thing, lastName=two
Must compare values
Of same type
Of legal promotion type
Can compare 123:int to 123:long
Cannot compare 123:int to "123":string
Can compare entities
Figure 74.32. Equivalent JPAQL
select c from Clerk c where c.firstName = 'Manny'
select s from Sale s JOIN s.clerks c where c = :clerk
Figure 74.33. Criteria API Definition
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.equal(c.get("firstName"), "Manny"));
CriteriaQuery<Sale> qdef2 = cb.createQuery(Sale.class);
Root<Sale> s = qdef2.from(Sale.class);
Join<Sale, Clerk> c2 = s.join("clerks");
qdef2.select(s)
.where(cb.equal(c2, clerk));
Compare entities and not primary/foreign key values
Figure 74.34. In Programming Context
//find clerk of interest
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.equal(c.get("firstName"), "Manny"));
Clerk clerk = em.createQuery(qdef).getSingleResult();
//find all sales that involve this clerk
CriteriaQuery<Sale> qdef2 = cb.createQuery(Sale.class);
Root<Sale> s = qdef2.from(Sale.class);
Join<Sale, Clerk> c2 = s.join("clerks");
qdef2.select(s)
.where(cb.equal(c2, clerk));
List<Sale> sales = em.createQuery(qdef2).getResultList();
Figure 74.35. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME='Manny' limit ?
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ inner join JPAQL_SALE_CLERK_LINK clerks1_ on sale0_.SALE_ID=clerks1_.SALE_ID inner join JPAQL_CLERK clerk2_ on clerks1_.CLERK_ID=clerk2_.CLERK_ID where clerk2_.CLERK_ID=? ... -found=date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, } ... -found=date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Figure 74.37. Criteria API Definition
CriteriaQuery<Sale> qdef = cb.createQuery(Sale.class);
Root<Sale> s = qdef.from(Sale.class);
qdef.select(s)
.where(cb.between(s.<BigDecimal>get("amount"),
new BigDecimal(90.00),
new BigDecimal(110.00)));
Figure 74.38. Runtime Output
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.amount between 90 and 110 ... -found=date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, }
Figure 74.40. Criteria API Definition
CriteriaQuery<Sale> qdef = cb.createQuery(Sale.class);
Root<Sale> s = qdef.from(Sale.class);
qdef.select(s)
.where(cb.not(cb.between(s.<BigDecimal>get("amount"),
new BigDecimal(90.00),
new BigDecimal(110.00))));
Figure 74.41. Runtime Output
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.amount not between 90 and 110 ... -found=date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Can be used to test for unassigned value or relationship
Figure 74.43. Criteria API Definition
CriteriaQuery<Sale> qdef = cb.createQuery(Sale.class);
Root<Sale> s = qdef.from(Sale.class);
qdef.select(s)
.where(cb.isNull(s.get("store")));
//.where(cb.equal(s.get("store"), cb.nullLiteral(Store.class)));
Figure 74.44. Runtime Output
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.SALE_STORE is null
Figure 74.46. Criteria API Definition
CriteriaQuery<Sale> qdef = cb.createQuery(Sale.class);
Root<Sale> s = qdef.from(Sale.class);
qdef.select(s)
.where(cb.isNotNull(s.get("store")));
//.where(cb.not(cb.equal(s.get("store"), cb.nullLiteral(Store.class))));
Figure 74.47. Runtime Output
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.SALE_STORE is not null ... -found result:date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, } ... -found result:date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Can be used to test for an empty collection
Figure 74.49. Criteria API Definition
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.isEmpty(c.<List<Sale>>get("sales")));
Figure 74.50. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where not (exists ( select sale2_.SALE_ID from JPAQL_SALE_CLERK_LINK sales1_, JPAQL_SALE sale2_ where clerk0_.CLERK_ID=sales1_.CLERK_ID and sales1_.SALE_ID=sale2_.SALE_ID) ) ... -found result:firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}
Sub-select returns values from collection under test
Outer query tests for no existing (EMPTY)values
Figure 74.52. Criteria API Definition
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.isNotEmpty(c.<List<Sale>>get("sales")));
Figure 74.53. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where exists ( select sale2_.SALE_ID from JPAQL_SALE_CLERK_LINK sales1_, JPAQL_SALE sale2_ where clerk0_.CLERK_ID=sales1_.CLERK_ID and sales1_.SALE_ID=sale2_.SALE_ID ) ... -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, } ... -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }
Sub-select returns values from collection under test
Outer query tests for existing (NOT EMPTY)values
Can be used to determine membership in a collection
Figure 74.54. Equivalent JPAQL
select c from Clerk c where c.firstName = 'Manny'
select s from Sale s where :clerk MEMBER OF s.clerks
Figure 74.55. Criteria API Definition
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.equal(c.get("firstName"), "Manny"));
CriteriaQuery<Sale> qdef2 = cb.createQuery(Sale.class);
Root<Sale> s = qdef2.from(Sale.class);
qdef2.select(s)
.where(cb.isMember(clerk, s.<List<Clerk>>get("clerks")));
Defines a shorthand for a subquery
Figure 74.56. In Programming Context
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c)
.where(cb.equal(c.get("firstName"), "Manny"));
Clerk clerk = em.createQuery(qdef).getSingleResult();
//find all sales that involve this clerk
CriteriaQuery<Sale> qdef2 = cb.createQuery(Sale.class);
Root<Sale> s = qdef2.from(Sale.class);
qdef2.select(s)
.where(cb.isMember(clerk, s.<List<Clerk>>get("clerks")));
List<Sale> sales = em.createQuery(qdef2).getResultList();
Figure 74.57. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where clerk0_.FIRST_NAME=? limit ?
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where ? in ( select clerk2_.CLERK_ID from JPAQL_SALE_CLERK_LINK clerks1_, JPAQL_CLERK clerk2_ where sale0_.SALE_ID=clerks1_.SALE_ID and clerks1_.CLERK_ID=clerk2_.CLERK_ID ) ... -found=date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, } ... -found=date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Useful when query cannot be expressed through JOINs
Figure 74.58. Equivalent JPAQL
select c from Customer c where c.id IN (select s.buyerId from Sale s where s.amount > 100)
Figure 74.59. Criteria API Definition
CriteriaQuery<Customer> qdef = cb.createQuery(Customer.class);
//form subquery
Subquery<Long> sqdef = qdef.subquery(Long.class);
Root<Sale> s = sqdef.from(Sale.class);
sqdef.select(s.<Long>get("buyerId"))
.where(cb.greaterThan(s.<BigDecimal>get("amount"), new BigDecimal(100)));
//form outer query
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c)
.where(cb.in(c.get("id")).value(sqdef));
Figure 74.60. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.CUSTOMER_ID in ( select sale1_.BUYER_ID from JPAQL_SALE sale1_ where sale1_.amount>100 ) -found result:firstName=thing, lastName=one
All existing values must meet criteria (i.e., no value may fail criteria)
Zero values is the lack of failure (i.e., meets criteria)
Figure 74.61. Equivalent JPAQL
select c from Clerk c where 125 < ALL (select s.amount from c.sales s)
Figure 74.62. Criteria API Definition
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c);
Subquery<BigDecimal> sqdef = qdef.subquery(BigDecimal.class);
Root<Clerk> c1 = sqdef.from(Clerk.class);
Join<Clerk,Sale> s = c1.join("sales");
sqdef.select(s.<BigDecimal>get("amount"))
.where(cb.equal(c, c1));
Predicate p1 = cb.lessThan(
cb.literal(new BigDecimal(125)),
cb.all(sqdef));
qdef.where(p1);
List all clerks that have all sales above $125.00 or none at all
-or- List all clerks with no sale <= $125.00
Figure 74.63. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where 125<all ( select sale3_.amount from JPAQL_CLERK clerk1_ inner join JPAQL_SALE_CLERK_LINK sales2_ on clerk1_.CLERK_ID=sales2_.CLERK_ID inner join JPAQL_SALE sale3_ on sales2_.SALE_ID=sale3_.SALE_ID where clerk0_.CLERK_ID=clerk1_.CLERK_ID ) ... -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, } ... -found result:firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}
Manny excluded because has 1 sale below $125.00
Moe included because has only $150.00 sale
Jack included because has no sales that fail criteria
Figure 74.64. Equivalent JPAQL
select c from Clerk c where 125 > ALL (select s.amount from c.sales s)
Figure 74.65. Criteria API Definition
Predicate p2 = cb.greaterThan(
cb.literal(new BigDecimal(125)),
cb.all(sqdef));
qdef.where(p2);
List all clerks that have all sales below $125.00 or none at all
-or- List all clerks with no sale >= $125.00
Figure 74.66. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where 125>all ( select sale3_.amount from JPAQL_CLERK clerk1_ inner join JPAQL_SALE_CLERK_LINK sales2_ on clerk1_.CLERK_ID=sales2_.CLERK_ID inner join JPAQL_SALE sale3_ on sales2_.SALE_ID=sale3_.SALE_ID where clerk0_.CLERK_ID=clerk1_.CLERK_ID ) -found result:firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}
Manny excluded because has 1 sale above $125.00
Moe excluded because has only $150.00 sale
Jack included because has no sales that fail criteria
Any matching value meets criteria (i.e., one match and you are in)
Zero values fails to meet the criteria (i.e., must have at least one matching value)
Figure 74.67. Equivalent JPAQL
select c from Clerk c where 125 < ANY (select s.amount from c.sales s)
Figure 74.68. Criteria API Definition
CriteriaQuery<Clerk> qdef = cb.createQuery(Clerk.class);
Root<Clerk> c = qdef.from(Clerk.class);
qdef.select(c);
//select c from Clerk c
//where 125 < ALL " +
//(select s.amount from c.sales s)",
Subquery<BigDecimal> sqdef = qdef.subquery(BigDecimal.class);
Root<Clerk> c1 = sqdef.from(Clerk.class);
Join<Clerk,Sale> s = c1.join("sales");
sqdef.select(s.<BigDecimal>get("amount"))
.where(cb.equal(c, c1));
Predicate p1 = cb.lessThan(
cb.literal(new BigDecimal(125)),
cb.any(sqdef));
qdef.where(p1);
List all clerks that have at least one sale above $125.00
Figure 74.69. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where 125<any ( select sale3_.amount from JPAQL_CLERK clerk1_ inner join JPAQL_SALE_CLERK_LINK sales2_ on clerk1_.CLERK_ID=sales2_.CLERK_ID inner join JPAQL_SALE sale3_ on sales2_.SALE_ID=sale3_.SALE_ID where clerk0_.CLERK_ID=clerk1_.CLERK_ID ) ... -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, } ... -found result:firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }
Manny included because has 1 sale above $125.00
Moe included because $150.00 sale qualifies him as well
Jack excluded because has no sales that meet criteria
Figure 74.70. Equivalent JPAQL
select c from Clerk c where 125 > ANY (select s.amount from c.sales s)
Figure 74.71. Criteria API Definition
Predicate p2 = cb.greaterThan(
cb.literal(new BigDecimal(125)),
cb.any(sqdef));
qdef.where(p2);
List all clerks that have at least one sale below $125.00
Figure 74.72. Runtime Output
select clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ where 125>any ( select sale3_.amount from JPAQL_CLERK clerk1_ inner join JPAQL_SALE_CLERK_LINK sales2_ on clerk1_.CLERK_ID=sales2_.CLERK_ID inner join JPAQL_SALE sale3_ on sales2_.SALE_ID=sale3_.SALE_ID where clerk0_.CLERK_ID=clerk1_.CLERK_ID ) -found result:firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, }
Manny included because has 1 sale below $125.00
Moe excluded because his only $150.00 sale above criteria
Jack excluded because has no sales that meet criteria
Figure 75.2. Criteria API Definition
CriteriaQuery qdef = cb.createQuery();
Root<Customer> c = qdef.from(Customer.class);
qdef.select(c)
.where(cb.equal(c.get("firstName"),"CAT"));
Using an untyped CriteriaQuery to be able to switch between different query output types within example
Figure 75.3. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=?
No rows found because 'CAT' does not match anything because of case
Figure 75.5. Criteria API Definition
qdef.select(c)
.where(cb.equal(c.get("firstName"),cb.lower(cb.literal("CAT"))));
Figure 75.6. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=lower(?) -found result:firstName=cat, lastName=inhat
One customer found because case-sensitive compare now correct
Figure 75.7. Equivalent JPAQL
select UPPER(c.firstName) from Customer c where c.firstName=LOWER('CAT')
Figure 75.8. Criteria API Definition
qdef.select(cb.upper(c.<String>get("firstName")))
.where(cb.equal(c.get("firstName"),cb.lower(cb.literal("CAT"))));
Figure 75.9. Runtime Output
select upper(customer0_.FIRST_NAME) as col_0_0_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=lower(?) -found result:CAT
First name of customer located returned in upper case
Figure 75.10. Equivalent JPAQL
select TRIM(LEADING 'c' FROM c.firstName) from Customer c where c.firstName='cat')
Figure 75.11. Criteria API Definition
qdef.select(cb.trim(Trimspec.LEADING, 'c', c.<String>get("firstName")))
.where(cb.equal(c.get("firstName"),"cat"));
Figure 75.12. Runtime Output
select trim(LEADING ? from customer0_.FIRST_NAME) as col_0_0_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=? -found result:at
Customer's name, excluding initial 'c' character, returned
Figure 75.13. Equivalent JPAQL
select c from Customer c where CONCAT(CONCAT(c.firstName,' '),c.lastName) ='cat inhat')
Figure 75.14. Criteria API Definition
qdef.select(c)
.where(cb.equal(
cb.concat(
cb.concat(c.<String>get("firstName"), " "),
c.<String>get("lastName")),
"cat inhat"));
Figure 75.15. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where (customer0_.FIRST_NAME||?||customer0_.LAST_NAME)=? -found result:firstName=cat, lastName=inhat
Customer located after concatenation of fields yields match
Figure 75.17. Criteria API Definition
qdef.select(c)
.where(cb.equal(cb.length(c.<String>get("firstName")),3));
Figure 75.18. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where length(customer0_.FIRST_NAME)=3 -found result:firstName=cat, lastName=inhat
Customer found where length of firstName matches specified length criteria
Figure 75.20. Criteria API Definition
qdef.select(c)
.where(cb.greaterThan(cb.locate(c.<String>get("firstName"), "cat", 2),0));
Figure 75.21. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where locate(?, customer0_.FIRST_NAME, 2)>0
No firstName found with 'cat' starting at position=2
Figure 75.22. Equivalent JPAQL
select c from Customer c where LOCATE('at',c.firstName,2) > 1
qdef.select(c)
.where(cb.greaterThan(cb.locate(c.<String>get("firstName"), "at", 2),1));
Figure 75.23. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where locate(?, customer0_.FIRST_NAME, 2)>1 -found result:firstName=cat, lastName=inhat
firstName found with 'at' starting at a position 2
Figure 75.24. Equivalent JPAQL
select SUBSTRING(c.firstName,2,2) from Customer c where c.firstName = 'cat'
Figure 75.25. Criteria API Definition
qdef.select(cb.substring(c.<String>get("firstName"), 2, 2))
.where(cb.equal(c.get("firstName"), "cat"));
Figure 75.26. Runtime Output
select substring(customer0_.FIRST_NAME, 2, 2) as col_0_0_ from JPAQL_CUSTOMER customer0_ where customer0_.FIRST_NAME=? -found result:at
Return the two character substring of firstName starting at position two
Figure 75.28. Criteria API Definition
qdef.select(c)
.where(cb.equal(
cb.substring(c.<String>get("firstName"), 2, 2),
"at"));
Figure 75.29. Runtime Output
select customer0_.CUSTOMER_ID as CUSTOMER1_3_, customer0_.FIRST_NAME as FIRST2_3_, customer0_.LAST_NAME as LAST3_3_ from JPAQL_CUSTOMER customer0_ where substring(customer0_.FIRST_NAME, 2, 2)=? -found result:firstName=cat, lastName=inhat
Find the customer with a two characters starting a position two of firstName equaling 'at'
CriteriaBuilder.currentDate()
CriteriaBuilder.currentTime()
CriteriaBuilder.currentTimestamp()
Figure 75.31. Criteria API Definition
CriteriaQuery<Sale> qdef = cb.createQuery(Sale.class);
Root<Sale> s = qdef.from(Sale.class);
qdef.select(s);
qdef.where(cb.lessThan(s.<Date>get("date"), cb.currentDate()));
Figure 75.32. Runtime Output
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.date<current_date() ... -found result:date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, } ... -found result:date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Located two Sales that occurred prior to today's date
Figure 75.35. Runtime Output
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.date=current_date()
Located no sales on today's date
Update with a bulk query
Criteria API added Bulk Updates in JPA 2.1
Figure 75.37. Criteria API Definition
CriteriaUpdate<Sale> qupdate = cb.createCriteriaUpdate(Sale.class);
Root<Sale> s2 = qupdate.from(Sale.class);
qupdate.set(s2.<Date>get("date"), cb.currentDate());
int rows = em.createQuery(qupdate).executeUpdate();
Update all sales to today
Figure 75.41. Runtime Output
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ where sale0_.date=current_date() ... -found result:date=2013-06-05 00:00:00, amount=$100.00, buyer=1, clerks(1)={1, } ... -found result:date=2013-06-05 00:00:00, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Now locating sales for today's date
Bulk commands (i.e., update) invalidate cached entities. You must refresh their state with the database or detach/clear them from the persistence context to avoid using out-dated information.
ASC - ascending order
DESC - descending order
Figure 75.43. Criteria API Definition
CriteriaQuery<Sale> qdef = cb.createQuery(Sale.class);
Root<Sale> s = qdef.from(Sale.class);
qdef.select(s);
qdef.orderBy(cb.asc(s.get("amount")));
Figure 75.44. Runtime Output
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ order by sale0_.amount ASC ... -found result:date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, } ... -found result:date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, }
Note the ASC order on amount
Figure 75.47. Runtime Output
select sale0_.SALE_ID as SALE1_1_, sale0_.amount as amount1_, sale0_.BUYER_ID as BUYER3_1_, sale0_.date as date1_, sale0_.SALE_STORE as SALE5_1_ from JPAQL_SALE sale0_ order by sale0_.amount DESC -found result:date=1999-06-11 14:15:10, amount=$150.00, buyer=2, clerks(2)={1, 2, } -found result:date=1998-04-10 10:13:35, amount=$100.00, buyer=1, clerks(1)={1, }
Note the DESC order on amount
Figure 75.49. Criteria API Definition
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Number> qdef = cb.createQuery(Number.class); Root<Sale> s = qdef.from(Sale.class); qdef.select(cb.count(s));
Figure 75.52. Criteria API Definition
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Number> qdef = cb.createQuery(Number.class); Root<Sale> s = qdef.from(Sale.class); qdef.select(cb.min(s.<BigDecimal>get("amount")));
Figure 75.53. Runtime Output
select min(sale0_.amount) as col_0_0_ from JPAQL_SALE sale0_ -found result:100.00
Figure 75.55. Criteria API Definition
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Number> qdef = cb.createQuery(Number.class); Root<Sale> s = qdef.from(Sale.class); qdef.select(cb.max(s.<BigDecimal>get("amount")));
Figure 75.56. Runtime Output
select max(sale0_.amount) as col_0_0_ from JPAQL_SALE sale0_ -found result:150.00
Figure 75.58. Criteria API Definition
CriteriaQuery<Number> qdef = cb.createQuery(Number.class); Root<Sale> s = qdef.from(Sale.class); //select sum(s.amount) from Sale s qdef.select(cb.sum(s.<BigDecimal>get("amount")));
Figure 75.59. Runtime Output
select sum(sale0_.amount) as col_0_0_ from JPAQL_SALE sale0_ -found result:250.0
Figure 75.61. Criteria API Definition
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Number> qdef = cb.createQuery(Number.class); Root<Sale> s = qdef.from(Sale.class); qdef.select(cb.avg(s.<BigDecimal>get("amount")));
Figure 75.62. Runtime Output
select avg(cast(sale0_.amount as double)) as col_0_0_ from JPAQL_SALE sale0_ -found result:125.0
Figure 75.64. Criteria API Definition
CriteriaQuery<Object[]> qdef = cb.createQuery(Object[].class);
Root<Clerk> c = qdef.from(Clerk.class);
Join<Clerk,Sale> s = c.join("sales", JoinType.LEFT);
qdef.select(cb.array(c, cb.count(s)))
.groupBy(c);
Get count of sales for each clerk
Figure 75.65. Runtime Output
select clerk0_.CLERK_ID as col_0_0_, count(sale2_.SALE_ID) as col_1_0_, clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ left outer join JPAQL_SALE_CLERK_LINK sales1_ on clerk0_.CLERK_ID=sales1_.CLERK_ID left outer join JPAQL_SALE sale2_ on sales1_.SALE_ID=sale2_.SALE_ID group by clerk0_.CLERK_ID ... -found=[firstName=Manny, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(2)={1, 2, }, 2] ... -found=[firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }, 1] ... -found=[firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}, 0]
Figure 75.66. Equivalent JPAQL
select c, COUNT(s) from Clerk c LEFT JOIN c.sales s GROUP BY c HAVING COUNT(S) <= 1
Figure 75.67. Criteria API Definition
CriteriaQuery<Object[]> qdef = cb.createQuery(Object[].class);
Root<Clerk> c = qdef.from(Clerk.class);
Join<Clerk,Sale> s = c.join("sales", JoinType.LEFT);
qdef.select(cb.array(c, cb.count(s)))
.groupBy(c)
.having(cb.le(cb.count(s), 1));
Provide a list of Clerks and their count of Sales for counts <= 1
Figure 75.68. Runtime Output
select clerk0_.CLERK_ID as col_0_0_, count(sale2_.SALE_ID) as col_1_0_, clerk0_.CLERK_ID as CLERK1_2_, clerk0_.FIRST_NAME as FIRST2_2_, clerk0_.HIRE_DATE as HIRE3_2_, clerk0_.LAST_NAME as LAST4_2_, clerk0_.TERM_DATE as TERM5_2_ from JPAQL_CLERK clerk0_ left outer join JPAQL_SALE_CLERK_LINK sales1_ on clerk0_.CLERK_ID=sales1_.CLERK_ID left outer join JPAQL_SALE sale2_ on sales1_.SALE_ID=sale2_.SALE_ID group by clerk0_.CLERK_ID having count(sale2_.SALE_ID)<=1 ... -found=[firstName=Moe, lastName=Pep, hireDate=1970-01-01, termDate=null, sales(1)={2, }, 1] ... -found=[firstName=Jack, lastName=Pep, hireDate=1973-03-01, termDate=null, sales(0)={}, 0]
Wed matched on Moe (1 sale) and Jack (0 sales)
Copyright © 2015 jim stafford (jim.stafford@jhu.edu)
Built on: 2015-11-18 02:21 EST
Abstract
This presentation provides information for JPA/SQL developers to better understand how database constructs and database access decisions can impact application performance. It provides a brief discussion of tools that can be used and how to review an execution plan. Relative comparisons between approaches are provided to help show the costs and benefits of different approaches.
Table of Contents
Why DB access can be inefficient
What is an execution plan
How to access an execution plan
How to analyze an execution plan
Impact of Indexes
to access tables
to access joins
JPA performance pitfalls
The material in this paper/presentation is geared towards making JPA/SQL developers more aware of issues that can impact performance in the applications they develop. The material in this paper is not intended to be a performance benchmark for any database or JPA provider -- thus the environment used is not described (and quite far from production standards). Use all reported measurements as relative across the alternatives within the example they are presented. The material covered in this paper should be an excellent start for developers to better understand what costs what, how you can better know when there are issues or success, and where to look for solutions. Some topics were purposely left out to help concentrate on the primary scenarios.
Poorly constructed data model
Poorly constructed access for the data model
Missing access structures
Indexes
Materialized views
Missing constraints
Unique Constraints
NOT null
Sub-optimal execution plan
Incorrect estimates of cost, cardinality, or predictive selectivity
Stale or missing optimizer statistics
Output of the optimizer
Instructions of what execution engine must perform to complete query
Parent-child relationship between steps showing
Ordering of tables referenced
Table access methods used
Join methods used
Data operations (e.g., filter, sort, aggregation)
Cardinality = number of rows selected (based on count of unique columns)
Cost = disk access, I/O and CPU cost estimates for number of rows selected
Partition access
Parallel execution
Figure 76.1. JUnit Benchmark Tool
NoColumnIndex.queryForMovieAndDir: [measured 1 out of 1 rounds, threads: 1 (sequential)] round: 3.85 [+- 0.00], round.block: 0.00 [+- 0.00], round.gc: 0.00 [+- 0.00], GC.calls: 0, GC.time: 0.00, time.total: 3.85, time.warmup: 0.00, time.bench: 3.85
Helps you determine which execution plan selected by optimizer
Statement is not executed
Plan is theoretical
Figure 76.3. EXPLAIN PLAN Example using Text SQL Commands
EXPLAIN PLAN FOR select * from ( select movie0_.TITLE as col_0_0_, person2_.FIRST_NAME as col_1_0_, person2_.LAST_NAME as col_2_0_ from JPATUNE_MOVIE movie0_ inner join JPATUNE_DIRECTOR director1_ on movie0_.DIRECTOR_ID=director1_.PERSON_ID inner join JPATUNE_PERSON person2_ on director1_.PERSON_ID=person2_.ID order by title DESC ) where rownum <= :limit; SET LINESIZE 100 SET PAGESIZE 0 select * from table(DBMS_XPLAN.DISPLAY());
plan FOR succeeded. Plan hash value: 857441453 ---------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 774K| 477M| | 25766 (1)| 00:05:10 | |* 1 | COUNT STOPKEY | | | | | | | | 2 | VIEW | | 774K| 477M| | 25766 (1)| 00:05:10 | |* 3 | SORT ORDER BY STOPKEY | | 774K| 45M| 53M| 25766 (1)| 00:05:10 | |* 4 | HASH JOIN | | 774K| 45M| 11M| 14333 (1)| 00:02:52 | |* 5 | HASH JOIN | | 271K| 8746K| 5568K| 5115 (1)| 00:01:02 | | 6 | INDEX FAST FULL SCAN| DIRECTOR_PK | 271K| 2385K| | 191 (1)| 00:00:03 | | 7 | TABLE ACCESS FULL | JPATUNE_PERSON | 1637K| 37M| | 1854 (1)| 00:00:23 | |* 8 | TABLE ACCESS FULL | JPATUNE_MOVIE | 774K| 20M| | 7169 (1)| 00:01:27 | ---------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter(ROWNUM<=TO_NUMBER(:LIMIT)) 3 - filter(ROWNUM<=TO_NUMBER(:LIMIT)) 4 - access("MOVIE0_"."DIRECTOR_ID"="DIRECTOR1_"."PERSON_ID") 5 - access("DIRECTOR1_"."PERSON_ID"="PERSON2_"."ID") 8 - filter("MOVIE0_"."DIRECTOR_ID" IS NOT NULL)
Figure 76.4. EXPLAIN PLAN Example using Named Plan
EXPLAIN PLAN SET STATEMENT_ID='myplan01' FOR select * from ( select movie0_.TITLE as col_0_0_, person2_.FIRST_NAME as col_1_0_, person2_.LAST_NAME as col_2_0_ ...
select * from table(DBMS_XPLAN.DISPLAY('PLAN_TABLE','myplan01','typical',null));
Produces execution plan and statistics after running statement(s)
Statement(s) are actually run -- not theoretical like EXPLAIN PLAN
grant SELECT_CATALOG_ROLE to (user); grant SELECT ANY DICTIONARY to (user);
Runtime cursors store execution plans within V$PLAN
DISPLAY_CURSOR requires select privileges on: V$SQL_PLAN, V$SESSION and V$SQL_PLAN_STATISTICS_ALL
Figure 76.6. Example: Connection Generates SQL Commands
select poo.first_name, poo.last_name from jpatune_person poo;
Figure 76.7. Example: SQL Commands of Interest Located in V$PLAN
select sql_id, sql_fulltext from V$SQL where sql_fulltext like '%from jpatune_person poo%';
87d246wux9qag "select poo.first_name, poo.last_name from jpatune_person poo" 9bzam4xu5q7p5 select sql_id, sql_fulltext from V$SQL where sql_fulltext like '%from jpatune_person poo%'
Figure 76.8. Example: SQL_ID from V$PLAN Used to Display Execution Plan
select PLAN_TABLE_OUTPUT from TABLE(DBMS_XPLAN.DISPLAY_CURSOR('87d246wux9qag',null, 'TYPICAL'));
SQL_ID 87d246wux9qag, child number 0 ------------------------------------- select poo.first_name, poo.last_name from jpatune_person poo Plan hash value: 1628338048 ------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | | | 3 (100)| | | 1 | TABLE ACCESS FULL| JPATUNE_PERSON | 10 | 680 | 3 (0)| 00:00:01 | ------------------------------------------------------------------------------------ Note ----- - dynamic sampling used for this statement (level=2)
Figure 76.9. Example: DISPLAY_CURSOR witin Single Command
SELECT t.* FROM v$sql s, table(DBMS_XPLAN.DISPLAY_CURSOR(s.sql_id, s.child_number)) t WHERE sql_text LIKE '%JPATUNE_MOVIEGENRE%';
Access paths - strategies used to access data within table
Indexes - good for when accessing small amounts of data out of much larger data set
Full table scans - good for small tables and unrestricted row access
Any row can be *functionally* accessed with either method
Use execution plans to help use the appropriate technique
Database engine derives all information from the table directly from the physical storage location for the table. Table storage is not arranged in any guaranteed order.
Request N rows of data from a table with no defined order
Full Table Scan selected when:
There is no where clause
There is no suitable index
calling functions on row data (ex. upper(title)='TREMORS')
comparing column against data type requiring conversion
using wildcard in leading character (ex. title like '%emors')
searching for null column values (ex. rating is null) with single column indexes
Full Table Scan optimal when:
Using small tables
Majority of the rows in a larger table must be read (e.g., no where clause)
Figure 78.2. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ ) where rownum <= ?
DB scans table as a part of getting data to resolve select clause
No added cost for where or order-by clauses
Figure 78.4. Relative Test Result
Table Access without Index.Unrestricted Scan:warmups=2, rounds=11, ave=0.73 [+- 0.03]
Since the DB's job in this case is to obtain data from all rows of the table in any order -- an index would not add value.
Request N rows of data matching a condition on an un-indexed column
Figure 78.5. JPA Query
"select m from Movie m where m.rating = :rating", params={rating=R}, limit=1000
Figure 78.6. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ where movie0_.RATING=? ) where rownum <= ?
Where constraint adds cost to unconstrained query
DB must scan the rows in the table for a match to query
Figure 78.8. Relative Test Result
Table Access without Index.Value Access:warmups=2, rounds=11, ave=0.80 [+- 0.04]
Full table scans are the most inefficient way to obtain certain rows from a large DB table.
Request N rows of data matching a condition on an indexed column
Uses an exact location for a row -- normally obtained from an index
Fastest way to locate an individual row in a table
Note: rows can be moved during an update -- requiring multiple locations to be accessed when using former rowId
Figure 78.10. JPA Query
"select m from Movie m where m.rating = :rating", params={rating=R}, limit=1000
Figure 78.11. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ where movie0_.RATING=? ) where rownum <= ?
Index on where clause search term avoids scanning table -- index scanned instead
DB row is accessed to satisfy select clause
DB row is located by rowId from index
Figure 78.13. Relative Test Result
Table Access with Single Indexes.Value Access:warmups=2, rounds=11, ave=0.75 [+- 0.03]
An index will speed access to rows within a table when selecting a small subset of a larger table.
Request N rows of data matching a condition on an indexed column invalidated by a function
Figure 78.15. JPA Query
"select m from Movie m where upper(m.rating) = :rating", params={rating=R}, limit=1000
Figure 78.16. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ where upper(movie0_.RATING)=? ) where rownum <= ?
Applying function to column indexed by value invalidates use of index
DB reverts to full table scan to locate row
Figure 78.18. Relative Test Result
Table Access with Single Indexes.Unindexed Function Access:warmups=2, rounds=11, ave=0.79 [+- 0.04]
Calling a function on a DB column within the where clause will invalidate the use of an index on that column -- unless it is a function-based index (matching what is being used in the where)
Request N rows of data matching a functional condition on a function-indexed column
Figure 78.20. JPA Query
"select m from Movie m where lower(m.rating) = :rating", params={rating=r}, limit=1000
Figure 78.21. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ where lower(movie0_.RATING)=? ) where rownum <= ?
Where clause satisfied by scanning function index
DB row accessed to satisfy select clause
DB row accessed by RowId
COST is same order of magnitude (but )as non-function index
Figure 78.23. Relative Test Result
Table Access with Single Indexes.Indexed Function Access:warmups=2, rounds=11, ave=0.76 [+- 0.04]
The DB will store the pre-calculated value in the index for use during the query.
Request N rows of data matching a like condition containing a leading wildcard
Figure 78.25. JPA Query
"select m from Movie m where m.title like :title", params={title=%eventeen: The Faces for Fall}, limit=1000
Figure 78.26. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ where movie0_.TITLE like ? ) where rownum <= ?
Row located using full table scan
Leading wildcard in like invalidates use of indexed column
Figure 78.28. Relative Test Result
Table Access with Single Indexes.Ending Wildcard:warmups=2, rounds=11, ave=0.22 [+- 0.04] Table Access with Single Indexes.Exact Equals:warmups=2, rounds=11, ave=0.20 [+- 0.02] Table Access with Single Indexes.Exact Like:warmups=2, rounds=11, ave=0.22 [+- 0.05] Table Access with Single Indexes.Leading Wildcard:warmups=2, rounds=11, ave=0.66 [+- 0.05]
Leading wildcards invalidate the use of column indexes. Indexes are still used with non-leading wildcards.
Order returned results by one or more columns
Request N rows of data ordered by (ASC) a column
Figure 78.31. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ order by movie0_.TITLE ASC ) where rownum <= ?
DB table is scanned as before when unconstrained by where clause
Output is sorted at a noticeable cost increase
Figure 78.33. Relative Test Result
Table Access with Single Indexes.OrderBy Non-Null Column:warmups=2, rounds=11, ave=0.76 [+- 0.03]
Look to reduce the number of rows required to be sorted or to use an index (see next topic).
Request N rows of data ordered by (ASC) indexed column (ASC) from where
Figure 78.35. JPA Query
"select m from Movie m where m.title like :title order by title ASC", params={title=A%}, limit=1000
Figure 78.36. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ where movie0_.TITLE like ? order by movie0_.TITLE ASC ) where rownum <= ?
Range scan of index is used to satisfy where clause
Order of index used to satisfy order by clause
Cost significantly less that sort
Figure 78.38. Relative Test Result
Table Access with Single Indexes.OrderBy Non-null Where Column ASC:warmups=2, rounds=11, ave=0.69 [+- 0.02]
It is more efficient to sort results by the indexed values in the where clause because the index is already maintained in sort order.
Request N rows of data ordered by (DESC) indexed column (ASC) from where
Figure 78.40. JPA Query
"select m from Movie m where m.title like :title order by title DESC", params={title=A%}, limit=1000
Figure 78.41. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ where movie0_.TITLE like ? order by movie0_.TITLE DESC ) where rownum <= ?
Normal (ASC) indexes can be efficiently traversed in DESC order
Figure 78.43. Relative Test Result
Table Access with Single Indexes.OrderBy Non-null Where Column DESC:warmups=2, rounds=11, ave=0.72 [+- 0.04]
Request N rows of data ordered by (DESC) indexed column (DESC) from where
Figure 78.45. JPA Query
"select m from Movie m where m.title like :title order by title DESC", params={title=A%}, limit=1000
Figure 78.46. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ where movie0_.TITLE like ? order by movie0_.TITLE DESC ) where rownum <= ?
DESC index implementedas a function index
Function indexes are not handled as efficiently as normal indexes ["...cardinality estimates are not as accurate and the SYS_OP_DESCEND and SYS_OP_UNDESCEND functions are used as access/filter conditions as they’re the functions implemented in the function-based index" http://richardfoote.wordpress.com/category/descending-indexes/]
Figure 78.48. Relative Test Result
Table Access with Single Indexes DESC.OrderBy Non-null Where Column DESC:warmups=2, rounds=11, ave=0.99 [+- 0.03]
Ascending indexes can be efficiently traversed in ascending and descending order.
Full table scans efficient when required to access most rows
Indexes access more efficient when selecting small subset of table
Indexes can be invalidated - resulting in full table scans
using functions on columns (without a function index)
using leading wildcards
searching/counting for null column values (single column indexes do not store null values)
Indexes can be used to bypass expensive sort operations. Either:
Column must be non-nullable
Where clause excludes null values
Can be unique or non-unique
Can be simple or composite
Can be normal (ascending) or descending
Can be reverse key (for monotonically incrementing column values) to balance B*-tree
Can be function-based (to address normalization uses)
Can be used to implement sort for "order by"
Can be used to implement the entire table (Index-organized Table(IOT))
Can be traversed in different ways
Unique Scan - used with "unique" or "primary key" indexes to return a single value
Range Scan - used with "unique" and "non-unique" indexes to return multiple matching rows
Full Scan - used with composite indexes where leading where column is not part of index (i.e., can use col2 of composite)
Fast Full Scan - used when all columns of query (where and select) are contained within composite index -- table is skipped
...
Can be coalesced or rebuilt with
ALTER INDEX (index-name) (COALESCE | REBUILD)
Coalesce - repairs index in place. Good for small repairs
Rebuild - totally rebuilds index. Good for large repairs
Request N rows matching a specific criteria using a non-unique index
Figure 79.1. DB Index(es)
alter table jpatune_movie add utitle varchar2(256) update jpatune_movie set utitle=concat(concat(concat(title,'('),id),')') create index movie_utitle_idx on jpatune_movie(utitle)
* "utitle" was added as the concatenation of title(id) to create a unique column.
** we are not yet taking advantage of the uniqueness of the column
Figure 79.2. JPA Query
"select m from Movie m where m.title = :title", params={title=Tremors(m836199)}
Figure 79.3. Generated SQL
select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.utitle as utitle6_2_ from JPATUNE_MOVIE movie0_ where movie0_.utitle=?
Matching row located using index
Since index is non-unique multiple entries must be scanned
Figure 79.5. Relative Test Result
Non-Unique Index.Values By Index:warmups=2, rounds=11, ave=0.17 [+- 0.01]
Request N rows matching a specific criteria using a unique index
* we are now taking advantage of the unique column values.
Figure 79.7. JPA Query
"select m from Movie m where m.title = :title", params={title=Tremors(m836199)}
Figure 79.8. Generated SQL
select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.utitle as utitle6_2_ from JPATUNE_MOVIE movie0_ where movie0_.utitle=?
Index used to locate table rowId
Unique index scan used since index contains unique values
Figure 79.10. Relative Test Result
Nullable Unique Index.Values By Index:warmups=2, rounds=10, ave=0.18 [+- 0.01]
* the measurements in this test do not prove the following assertion which was based on the execution plan.
DB knows there can be only a single match and stops index scan after first match found
Indexes with multiple columns to match the where clause and optionally the select and join clauses as well.
Selecting N columns for M rows matching two (2) criteria terms
Figure 79.11. JPA Query
"select m from Movie m where m.title = :title and m.releaseDate = :releaseDate", params={title=Apollo 13, releaseDate=1995-07-01}
Figure 79.12. Generated SQL
select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ where movie0_.TITLE=? and movie0_.RELEASE_DATE=?
First term of a two term where clause indexed
Candidate rows located using a range scan of the index on first condition
Final rows filtered from candidate rows using second condition
Figure 79.15. Relative Test Result
Dual Indexes.Get By Term1 and Term2:warmups=2, rounds=11, ave=0.21 [+- 0.01]
DB will re-order the query to try to take advantage of an index that will result in the least number of rows moving forward to next step.
All terms in the where clause part of same composite index
Rows located using range scan of the composite index, applying the two conditions
Select clause satisfied using table rows accessed by rowId
Figure 79.18. Relative Test Result
Compound Index.Get By Term1 and Term2:warmups=2, rounds=11, ave=0.25 [+- 0.02]
Adding all terms from the where clause may add some efficiency over a single term index -- but not a significant upgrade from a single index when the contents of the row must be returned anyway.
Incorporate terms from select clause into composite index to bypass table access.
Selecting N columns from M rows matching Q criteria terms
Figure 79.19. JPA Query
"select m.rating from Movie m where m.title like :title", params={title=A%}, limit=2000
Figure 79.20. Generated SQL
select * from ( select movie0_.RATING as col_0_0_ from JPATUNE_MOVIE movie0_ where movie0_.TITLE like ? ) where rownum <= ?
Where clause satisfied using an index range scan
Select clause satisfied using table access by rowId from index
Figure 79.23. Relative Test Result
Where Column Index.Query for Values:warmups=2, rounds=11, ave=1.25 [+- 0.06]
* this index contains columns from both the where and select clauses
Where clause satisfied using an index range scan
Select clause satisfied using same index since composite index also contains all necessary columns
Figure 79.26. Relative Test Result
Where, Select Index.Query for Values:warmups=2, rounds=11, ave=1.26 [+- 0.06]
DB will bypass rowId access to row if index already contains all columns necessary to satisfy the select clause.
DB can make use of secondary terms within composite index.
* this index is the mirror image of previous example and contains columns from both the select and where clauses
Index did not start with terms from where clause, but usable
Where clause satisfied using a fast full scan of composite index
Select clause satisfied using same index since composite index also contains all necessary columns
Figure 79.29. Relative Test Result
Select, Where Index.Query for Values:warmups=2, rounds=11, ave=1.35 [+- 0.04]
Consider leveraging secondary columns of an existing composite index (over adding another index) to satisfy low-priority queries.
Indexes speed access to specific rows
Indexes should match where clause of high priority queries
Composite indexes can be formed with multiple terms from the where and select clause
Secondary columns of composite indexes can be used (over creating additional index) in lower priority queries
Unique indexes can be searched faster than non-unique indexes -- take advantage of unique column values
Improving performance of foreign key joins.
Select N properties for object M matching certain criteria
Figure 80.1. JPA Query
"select r from MovieRole r join r.movie m where m.title=:title and m.releaseDate=:releaseDate", params={title=Tremors, releaseDate=1990-07-01}
Figure 80.2. Generated SQL
select movierole0_.ID as ID1_3_, movierole0_.ACTOR_ID as ACTOR3_3_, movierole0_.MOVIE_ID as MOVIE4_3_, movierole0_.MOVIE_ROLE as MOVIE2_3_ from JPATUNE_MOVIEROLE movierole0_ inner join JPATUNE_MOVIE movie1_ on movierole0_.MOVIE_ID=movie1_.ID where movie1_.TITLE=? and movie1_.RELEASE_DATE=?
Perform query without any index support for where, select, or join clauses
Full table scan of Movie to satisfy where clause and to obtain movie_id for join
Full table scan of MovieRole to locate MovieRoles.movie_id and to obtain rows to satisfy select clause
* Large portion of cost spent looking for children
The cost of performing a full table scan of the child table looking for children to match a specific parent is magnified when the overall query selects multiple parent rows. The child table must be scanned for each parent.
Address the biggest cost item in the execution plan by adding an index on the foreign key column.
Full table scan of Movie to satisfy where clause and to obtain movie_id for join
Range scan of foreign key index used to locate MovieRoles.movie_id
MovieRole rows accessed by rowId to satisfy select clause
* Eliminated much of the cost to locate children. Still have cost to locate parent
Add indexes to foreign key columns of child tables when the child table is frequently joined with parent rows. The index will go unused if the child is simply searched alone.
Figure 80.8. DB Index(es)
create index movie_title_rdate_idx on jpatune_movie(title, release_date) create index movierole_movie_fkx on jpatune_movierole(movie_id)
Range scan of composite used to satisfy where clause
Movie rows accessed by rowId to obtain Movie.id for join
(remaining steps are same as prior approach)
Range scan of foreign key index used to locate MovieRoles.movie_id
MovieRole rows accessed by rowId to satisfy select clause
* Eliminated much of the cost to locate children. Still have cost small cost obtaining Movie.id
Figure 80.10. Relative Test Result
FKs and Where Indexed.Get Children:warmups=2, rounds=11, ave=0.18 [+- 0.01]
Figure 80.11. DB Index(es)
create index movie_title_rdate_id_idx on jpatune_movie(title, release_date, id) create index movierole_movie_fkx on jpatune_movierole(movie_id)
Range scan of composite used to satisfy where clause and obtain Movie.id for join
No access to Movie table required
(remaining steps are same as prior approach)
Range scan of foreign key index used to locate MovieRoles.movie_id
MovieRole rows accessed by rowId to satisfy select clause
* The last rowId access is necessary to return the full MovieRole object
Figure 80.13. Relative Test Result
FKs, Where, and Join Indexed.Get Children:warmups=2, rounds=11, ave=0.20 [+- 0.01]
By adding the join column of the parent to the composite index used to locate the parent rows, you can save row accesses to the parent table (looking for the join column value). This is a small improvement relative to the other indexes added earlier because you are only eliminating an already efficient (but not the most efficient) means of locating row data.
Nested Loop Join - small driving table, join condition used to access second table
Hash Join - larger data joins based on equality matches through hashing functions
Sort-merge Join - larger data joins based on non-equality joins
Used when driving table is small and join condition used to access second table
Select all from table M joined with table P where P.cols match narrow condition
Figure 80.14. JPA Query
"select m from Movie m join m.cast as r where r.role=:role", params={role=Valentine McKee}, limit=2
Figure 80.15. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ inner join JPATUNE_MOVIEROLE cast1_ on movie0_.ID=cast1_.MOVIE_ID where cast1_.MOVIE_ROLE=? ) where rownum <= ?
No indexes on where clause or foreign key columns
Full table scan of MovieRoles table performed to satisfy role from where clause
Nested Loop using driving table (MovieRole) foreign key (MovieRole.movie_id)
Unique Scan of Movie PK Index (SYS_C0012387) used to locate join rows (MovieRole.movie_id = Movie.id)
RowId scan used to access row data for Movie
Figure 80.17. Relative Test Result
No Table or FK Indexes.Small Driving Table:warmups=2, rounds=11, ave=0.28 [+- 0.04]
Most of the time spent within this query plan is attributed to the full table scan due to the lack of an index matching the where clause.
Single index on where clause column(s)
Range scan on index of MovieRoles.roles column used to satisfy where clause
MoveRoles row accessed using RowId from MoviesRoles.roles index to obtain movie_id
(from here is is the same as the previous full table scan case)
Nested Loop using driving table (MovieRole) foreign key (MovieRole.movie_id)
Unique Scan of Movie PK Index (SYS_C0012387) used to locate join rows (MovieRole.movie_id = Movie.id)
RowId scan used to access row data for Movie
Figure 80.20. Relative Test Result
Table Indexes Only.Small Driving Table:warmups=2, rounds=11, ave=0.18 [+- 0.03]
Adding the foreign key to an (composite) index will eliminate need to access row for foreign key during join.
Separate indexes; one for where clause and second for FK for join
Figure 80.21. DB Index(es)
create index movie_role_idx on jpatune_movierole(movie_role) create index movie_role_movie_fdx on jpatune_movierole(movie_id)
There is no difference between this and the previous case
Foreign Key indexes help locate child rows when join coming from parent
Index identifies rowId of the child having the parent value -- not the rowId of the parent
Figure 80.23. Relative Test Result
Individual Table and FK Indexes.Small Driving Table:warmups=2, rounds=11, ave=0.19 [+- 0.03]
A foreign key index (from the child to the parent) is not used when the child is the driving table and the parent is being located. In this case, the primary key index of the parent is used instead.
Single composite index containing columns from where clause and foreign key
Figure 80.24. DB Index(es)
create index movie_role_movie_cdx on jpatune_movierole(movie_role, movie_id)
Range scan on composite index of MovieRoles used to satisfy where clause
Composite index provides movie_id without having to access MovieRole row
(from here is is the same as the previous cases)
Nested Loop using driving table (MovieRole) foreign key (MovieRole.movie_id)
Unique Scan of Movie PK Index (SYS_C0012387) used to locate join rows (MovieRole.movie_id = Movie.id)
RowId scan used to access row data for Movie
Figure 80.26. Relative Test Result
Composite with FK Index.Small Driving Table:warmups=2, rounds=11, ave=0.19 [+- 0.03]
Adding the foreign key column to an index used to satisfy the where clause can eliminate the need for the DB to access the row for the foreign key during a join operation.
Used when driving table too large to fit into memory and join based on equality
Select all from table M joined with table P where P.cols match broad condition
Figure 80.27. JPA Query
-- Bounded "select m from Movie m join m.cast as r where r.role like :role", params={role=V%}, limit=2
-- Offset Bounded ... offset=1000, limit=2
-- Unbounded ... params={role=V%}
* Used multiple forms of query since Hash algorithm seemed to better detect an early completion limit than the nested loop approach.
Figure 80.28. Generated SQL
select * from ( select movie0_.ID as ID1_2_, movie0_.DIRECTOR_ID as DIRECTOR7_2_, movie0_.MINUTES as MINUTES2_2_, movie0_.PLOT as PLOT3_2_, movie0_.RATING as RATING4_2_, movie0_.RELEASE_DATE as RELEASE5_2_, movie0_.TITLE as TITLE6_2_ from JPATUNE_MOVIE movie0_ inner join JPATUNE_MOVIEROLE cast1_ on movie0_.ID=cast1_.MOVIE_ID where cast1_.MOVIE_ROLE like ? ) where rownum <= ?
No indexes on where clause or foreign key columns
Used When:
Joining large amounts of data
Joining on equality (equi-join) and not relative or function-based
Full table scan of MovieRoles table performed to satisfy role from where clause
DB creates in-memory hash table of foreign key values (i.e., multiple rows/FKs may hash into single hash entry)
Full table scan of Movie table performed to locate and hash(movie.id) -- primary key index is not used
Unique rows located within each hash bucket -- empties discarded
Matching Movies forwarded to next level
Figure 80.30. Relative Test Result
No Table or FK Indexes.Large Driving Table (Bounded):warmups=2, rounds=11, ave=0.17 [+- 0.01] No Table or FK Indexes.Large Driving Table Offset (Bounded):warmups=2, rounds=11, ave=0.26 [+- 0.02] No Table or FK Indexes.Large Driving Table (Unbounded:warmups=2, rounds=11, ave=31.51 [+- 1.25]
Single index on where clause column(s)
Range scan on index of MovieRoles.roles column used to satisfy where clause
MoveRoles row accessed using RowId from MoviesRoles.roles index to obtain movie_id
DB creates in-memory hash table of foreign key values
(from here is is the same as the previous full table scan case)
Full table scan of Movie table performed to locate and hash(movie.id) -- primary key index is not used
Unique rows located within each hash bucket -- empties discarded
Matching Movies forwarded to next level
Figure 80.33. Relative Test Result
Table Indexes Only.Large Driving Table (Bounded):warmups=2, rounds=11, ave=0.19 [+- 0.03] Table Indexes Only.Large Driving Table Offset (Bounded):warmups=2, rounds=11, ave=0.26 [+- 0.04] Table Indexes Only.Large Driving Table (Unbounded:warmups=2, rounds=11, ave=31.40 [+- 1.38]
Single composite index containing columns from where clause and foreign key
Figure 80.34. DB Index(es)
create index movie_role_movie_cdx on jpatune_movierole(movie_role, movie_id)
Range scan on composite index of MovieRoles used to satisfy where clause
Composite index provides movie_id without having to access MovieRole row
(from here is is the same as the previous cases)
Full table scan of Movie table performed to locate and hash(movie.id) -- primary key index is not used
Unique rows located within each hash bucket -- empties discarded
Matching Movies forwarded to next level
Figure 80.36. Relative Test Result
Composite with FK Index.Large Driving Table (Bounded):warmups=2, rounds=11, ave=0.18 [+- 0.01] Composite with FK Index.Large Driving Table Offset (Bounded):warmups=2, rounds=11, ave=0.22 [+- 0.03] Composite with FK Index.Large Driving Table (Unbounded:warmups=2, rounds=11, ave=30.20 [+- 1.24]
Used:
Over Nested Loops when no suitable driving table can be selected (i.e. fit in memory)
Over Hash Join when join is by non-equality comparison
Basic Algorithm
No driving table selected
Both tables ordered according to a common key
Ordered rows from both tables are then merged
Figure 80.37. Altered SQL
select * from ( select /*+ use_merge(cast1_ movie0_) */ movie0_.ID as ID1_2_, ... where cast1_.MOVIE_ROLE like ? ) where rownum <= ?
Results from MovieRole (movie_role=:role) and Movie (id=MovieRole.id) sorted at considerable cost
Sorted results merged to form result
Non-equijoins eliminate DB-flexibility to locate matching rows between tables and force the database to order tables based on the key column prior to forming joined result.
Avoiding inefficient uses of JPA.
Cost impacts of lazy and eager fetch joins
Get just the root parent of an object graph
Figure 81.1. DB Index(es)
alter table jpatune_moviegenre add constraint moviegenre_unique UNIQUE (movie_id, genre) create index movie_role_movie_fdx on jpatune_movierole(movie_id)
Figure 81.2. Service Tier Code
for (String id: movieIds) {
Movie m = dao.getMovieById(id);
m.getTitle();
}
Get just the root parent of an object graph with fetch=LAZY relationships
Figure 81.4. Relationships
public class Movie {
...
@ElementCollection(fetch=FetchType.LAZY)
@CollectionTable(
name="JPATUNE_MOVIEGENRE",
joinColumns=@JoinColumn(name="MOVIE_ID"),
uniqueConstraints=@UniqueConstraint(columnNames={"MOVIE_ID", "GENRE"}))
@Column(name="GENRE", length=20)
private Set<String> genres;
@ManyToOne(fetch=FetchType.LAZY,
cascade={CascadeType.PERSIST, CascadeType.DETACH})
@JoinColumn(name="DIRECTOR_ID")
private Director director;
@OneToMany(fetch=FetchType.LAZY,mappedBy="movie",
cascade={CascadeType.PERSIST, CascadeType.DETACH, CascadeType.REMOVE})
private Set<MovieRole> cast;
Two one-to-many relationships (cast and genres) -- genres is a non-entity ElementCollection
One one-to-one relationship (director)
All use fetch=LAZY
Figure 81.5. Generated SQL
select movie0_.ID as ID1_2_0_, movie0_.DIRECTOR_ID as DIRECTOR6_2_0_, movie0_.MINUTES as MINUTES2_2_0_, movie0_.RATING as RATING3_2_0_, movie0_.RELEASE_DATE as RELEASE4_2_0_, movie0_.TITLE as TITLE5_2_0_ from JPATUNE_MOVIE movie0_ where movie0_.ID=?
Only parent table is queried
Children are lazily loaded
Unique scan of Movie primary key index to satisfy where clause and locate rowId
Row access by rowId to satisfy select clause
Figure 81.7. Relative Test Result
- 20 movies Fetch Lazy Thin Parent.Get Parent:warmups=2, rounds=10, ave=0.32 [+- 0.03] Fetch Lazy Thick Parent.Get Parent:warmups=2, rounds=10, ave=0.58 [+- 0.03]
* "Thick" parent has an extra Movie.plot text property mapped to potentially make child joins more expensive. Thin parent does not have Movie plot mapped so the results can focus on access to smaller, related entities.
Choosing fetch=LAZY for relationships allows efficient access to the core information for that entity without paying a price for loading unnecessary relations.
Get just the root parent of an object graph with fetch=EAGER relationships
Figure 81.8. Relationships
<entity class="ejava.jpa.examples.tuning.bo.Movie">
<attributes>
<many-to-one name="director" fetch="EAGER">
<join-column name="DIRECTOR_ID"/>
</many-to-one>
<one-to-many name="cast" fetch="EAGER" mapped-by="movie"/>
<element-collection name="genres" fetch="EAGER">
<column name="GENRE"/>
<collection-table name="JPATUNE_MOVIEGENRE">
<join-column name="MOVIE_ID"/>
<unique-constraint>
<column-name>MOVIE_ID</column-name>
<column-name>GENRE</column-name>
</unique-constraint>
</collection-table>
</element-collection>
<transient name="plot"/>
</attributes>
</entity>
public class MovieRole {
...
@ManyToOne(optional=false, fetch=FetchType.LAZY,
cascade={CascadeType.DETACH})
@JoinColumn(name="ACTOR_ID")
private Actor actor;
<entity class="ejava.jpa.examples.tuning.bo.MovieRole">
<attributes>
<many-to-one name="actor" fetch="EAGER">
<join-column name="ACTOR_ID"/>
</many-to-one>
</attributes>
</entity>
public class Actor {
...
@OneToOne(optional=false, fetch=FetchType.EAGER,
cascade={CascadeType.PERSIST, CascadeType.DETACH})
@MapsId
@JoinColumn(name="PERSON_ID")
private Person person;
public class Director {
...
@OneToOne(optional=false, fetch=FetchType.EAGER,
cascade={CascadeType.PERSIST, CascadeType.DETACH})
@JoinColumn(name="PERSON_ID")
@MapsId
private Person person;
Movie.director, Movie.genres, Movie.cast relationships overridden in XML to be fetch=EAGER
MovieRole.actor relationship overridden in XML to be fetch=EAGER
Director.person and Actor.person already fetch=EAGER
Figure 81.9. Generated SQL
select movie0_.ID as ID1_2_5_, movie0_.DIRECTOR_ID as DIRECTOR6_2_5_, movie0_.MINUTES as MINUTES2_2_5_, movie0_.RATING as RATING3_2_5_, movie0_.RELEASE_DATE as RELEASE4_2_5_, movie0_.TITLE as TITLE5_2_5_, cast1_.MOVIE_ID as MOVIE4_2_7_, cast1_.ID as ID1_4_7_, cast1_.ID as ID1_4_0_, cast1_.ACTOR_ID as ACTOR3_4_0_, cast1_.MOVIE_ID as MOVIE4_4_0_, cast1_.MOVIE_ROLE as MOVIE2_4_0_, actor2_.PERSON_ID as PERSON1_0_1_, person3_.ID as ID1_5_2_, person3_.BIRTH_DATE as BIRTH2_5_2_, person3_.FIRST_NAME as FIRST3_5_2_, person3_.LAST_NAME as LAST4_5_2_, person3_.MOD_NAME as MOD5_5_2_, director4_.PERSON_ID as PERSON1_1_3_, person5_.ID as ID1_5_4_, person5_.BIRTH_DATE as BIRTH2_5_4_, person5_.FIRST_NAME as FIRST3_5_4_, person5_.LAST_NAME as LAST4_5_4_, person5_.MOD_NAME as MOD5_5_4_, genres6_.MOVIE_ID as MOVIE1_2_8_, genres6_.GENRE as GENRE2_3_8_ from JPATUNE_MOVIE movie0_ left outer join JPATUNE_MOVIEROLE cast1_ on movie0_.ID=cast1_.MOVIE_ID left outer join JPATUNE_ACTOR actor2_ on cast1_.ACTOR_ID=actor2_.PERSON_ID left outer join JPATUNE_PERSON person3_ on actor2_.PERSON_ID=person3_.ID left outer join JPATUNE_DIRECTOR director4_ on movie0_.DIRECTOR_ID=director4_.PERSON_ID left outer join JPATUNE_PERSON person5_ on director4_.PERSON_ID=person5_.ID left outer join JPATUNE_MOVIEGENRE genres6_ on movie0_.ID=genres6_.MOVIE_ID where movie0_.ID=?
Single query (as before), but this time it also brings in the entire object graph
Explain plan shows use of indexes and no full table scans (a missing FK index for MovieRole.movie could have been costly)
Execution plan is significantly more costly than lazy alternative
Figure 81.11. Relative Test Result
- 20 movies Fetch Eager Thin Parent.Get Parent:warmups=2, rounds=10, ave=2.43 [+- 0.47] Fetch Eager Thick Parent.Get Parent:warmups=2, rounds=10, ave=2.59 [+- 0.06]
Choosing fetch=EAGER for relationships will make access to a single entity within the relationship more expensive to access. Consider creating a value query, value query with a result class, or a native query when querying for only single entity under these conditions.
Get just the root parent and *ONLY* the parent of an object graph with fetch=EAGER relationships by using a NativeQuery and Result Class
Figure 81.13. DAO Code
public Movie getMovieByIdUnfetched(String id) {
List<Movie> movies=createQuery(
String.format("select new %s(m.id, m.minutes, m.rating, m.releaseDate, m.title) ", Movie.class.getName()) +
"from Movie m " +
"where id=:id", Movie.class)
.setParameter("id", id)
.getResultList();
return movies.isEmpty() ? null : movies.get(0);
}
public Movie(String id, Integer minutes, String rating, Date releaseDate, String title) {
this.id=id;
this.minutes = minutes;
this.rating=rating;
this.mrating=MovieRating.getFromMpaa(rating);
this.releaseDate = releaseDate;
this.title=title;
}
We define an alternate JPA-QL query for provider to use that does not include relationships
Result class not mapped beyond this query -- does not have to be an entity class
Figure 81.14. Generated JPAQL and SQL
"select new ejava.jpa.examples.tuning.bo.Movie(m.id, m.minutes, m.rating, m.releaseDate, m.title) from Movie m where id=:id", params={id=m270003}
select movie0_.ID as col_0_0_, movie0_.MINUTES as col_1_0_, movie0_.RATING as col_2_0_, movie0_.RELEASE_DATE as col_3_0_, movie0_.TITLE as col_4_0_ from JPATUNE_MOVIE movie0_ where movie0_.ID=?
Value query with result class constructor permits a bypass of fetch=EAGER relationships
Execution plan identical to fetch=LAZY case
Figure 81.16. Relative Test Result
Fetch Eager Thin Parent.Get Just Parent:warmups=2, rounds=10, ave=0.32 [+- 0.02] Fetch Eager Thick Parent.Get Just Parent:warmups=2, rounds=10, ave=0.32 [+- 0.02] Fetch Lazy Thin Parent.Get Just Parent:warmups=2, rounds=10, ave=0.34 [+- 0.03] Fetch Lazy Thick Parent.Get Just Parent:warmups=2, rounds=10, ave=0.32 [+- 0.02]
* Thick/Thin or Eager/Lazy should not matter to this approach since we only access a specific set of fields in the Movie that does not include Movie.plot.
JPA provides many escape hatches to achieve desired results. Use JPAQL value queries with Object[], Tuple, or custom result class -or- use SQL to provide efficient override of fetch=EAGER definitions.
Even though we used an entity class in this example, it is being used as a simple POJO and the returned instance is not managed. Result Classes for value queries provide convenient type-safe access to results. However, they are unmanaged and cannot be used for follow-on entity operations.
Get parent and children in an object graph
Figure 81.17. DB Index(es)
alter table jpatune_moviegenre add constraint moviegenre_unique UNIQUE (movie_id, genre) create index movie_role_movie_fdx on jpatune_movierole(movie_id)
Figure 81.18. Service Tier Code
for (String id: movieIds) {
Movie m = dao.getMovieById(id);
if (m.getDirector()!=null) { m.getDirector().getPerson().getLastName(); }
m.getGenres().iterator().next();
for (MovieRole role: m.getCast()) {
if (role.getActor()!=null) { role.getActor().getPerson().getLastName(); }
}
}
Get parent and children in an object graph with fetch=LAZY relationships
Query for parent
select movie0_.ID as ID1_2_0_, movie0_.DIRECTOR_ID as DIRECTOR6_2_0_, movie0_.MINUTES as MINUTES2_2_0_, movie0_.RATING as RATING3_2_0_, movie0_.RELEASE_DATE as RELEASE4_2_0_, movie0_.TITLE as TITLE5_2_0_ from JPATUNE_MOVIE movie0_ where movie0_.ID=?
Query for Movie.director role
select director0_.PERSON_ID as PERSON1_1_0_ from JPATUNE_DIRECTOR director0_ where director0_.PERSON_ID=?;
Query for Movie.director.person identity
select person0_.ID as ID1_5_0_, person0_.BIRTH_DATE as BIRTH2_5_0_, person0_.FIRST_NAME as FIRST3_5_0_, person0_.LAST_NAME as LAST4_5_0_, person0_.MOD_NAME as MOD5_5_0_ from JPATUNE_PERSON person0_ where person0_.ID=?;
Query for Movie.genre values
select genres0_.MOVIE_ID as MOVIE1_2_0_, genres0_.GENRE as GENRE2_3_0_ from JPATUNE_MOVIEGENRE genres0_ where genres0_.MOVIE_ID=?;
Query for Movie.cast entities
select cast0_.MOVIE_ID as MOVIE4_2_1_, cast0_.ID as ID1_4_1_, cast0_.ID as ID1_4_0_, cast0_.ACTOR_ID as ACTOR3_4_0_, cast0_.MOVIE_ID as MOVIE4_4_0_, cast0_.MOVIE_ROLE as MOVIE2_4_0_ from JPATUNE_MOVIEROLE cast0_ where cast0_.MOVIE_ID=?;
Query for MovieRole.actor entity
select actor0_.PERSON_ID as PERSON1_0_0_ from JPATUNE_ACTOR actor0_ where actor0_.PERSON_ID=? ... --repeated for each member of the Movie.cast
Query for Actor.person entity
select person0_.ID as ID1_5_0_, person0_.BIRTH_DATE as BIRTH2_5_0_, person0_.FIRST_NAME as FIRST3_5_0_, person0_.LAST_NAME as LAST4_5_0_, person0_.MOD_NAME as MOD5_5_0_ from JPATUNE_PERSON person0_ where person0_.ID=?; ... --repeated for each member of the Movie.cast
Fetch Lazy Thin Parent.Get Parent and Children:warmups=2, rounds=10, ave=7.25 [+- 0.91] Fetch Lazy Thick Parent.Get Parent and Children:warmups=2, rounds=10, ave=7.25 [+- 0.92]
Fetch=LAZY is intended for access to a minimal set of objects within a graph. Although each individual query by primary key is quite inexpensive, the sum total (factoring in cardinality) is significant. Consider adding a JOIN FETCH query to your DAO when accessing entire object graph declared with fetch=LAZY.
Get parent and children in an object graph with fetch=EAGER relationships
Loading of the object graph occurs in single DB call
Same cost loading object graph -- whether accessing single or all entities
Figure 81.29. Relative Test Result
Fetch Eager Thin Parent.Get Parent and Children:warmups=2, rounds=10, ave=2.26 [+- 0.06] Fetch Eager Thick Parent.Get Parent and Children:warmups=2, rounds=10, ave=2.57 [+- 0.06]
Use fetch=EAGER if related entity *always*/*mostly* accessed. Suggest using a JOIN FETCH query when fetch=LAZY is defined and need entire object graph.
Get parent and children in an object graph with JOIN FETCH query
Figure 81.31. DAO Code/JPAQL
public Movie getMovieFetchedByIdFetched(String id) {
List<Movie> movies = createQuery(
"select m from Movie m " +
"left join fetch m.genres " +
"left join fetch m.director d " +
"left join fetch d.person " +
"left join fetch m.cast role " +
"left join fetch role.actor a " +
"left join fetch a.person " +
"where m.id=:id", Movie.class)
.setParameter("id", id)
.getResultList();
return movies.isEmpty() ? null : movies.get(0);
}
EAGER aspects adding by query adding FETCH to JOIN query construct
Figure 81.32. Generated SQL
select movie0_.ID as ID1_2_0_, director2_.PERSON_ID as PERSON1_1_1_, person3_.ID as ID1_5_2_, cast4_.ID as ID1_4_3_, actor5_.PERSON_ID as PERSON1_0_4_, person6_.ID as ID1_5_5_, movie0_.DIRECTOR_ID as DIRECTOR6_2_0_, movie0_.MINUTES as MINUTES2_2_0_, movie0_.RATING as RATING3_2_0_, movie0_.RELEASE_DATE as RELEASE4_2_0_, movie0_.TITLE as TITLE5_2_0_, genres1_.MOVIE_ID as MOVIE1_2_0__, genres1_.GENRE as GENRE2_3_0__, person3_.BIRTH_DATE as BIRTH2_5_2_, person3_.FIRST_NAME as FIRST3_5_2_, person3_.LAST_NAME as LAST4_5_2_, person3_.MOD_NAME as MOD5_5_2_, cast4_.ACTOR_ID as ACTOR3_4_3_, cast4_.MOVIE_ID as MOVIE4_4_3_, cast4_.MOVIE_ROLE as MOVIE2_4_3_, cast4_.MOVIE_ID as MOVIE4_2_1__, cast4_.ID as ID1_4_1__, person6_.BIRTH_DATE as BIRTH2_5_5_, person6_.FIRST_NAME as FIRST3_5_5_, person6_.LAST_NAME as LAST4_5_5_, person6_.MOD_NAME as MOD5_5_5_ from JPATUNE_MOVIE movie0_ left outer join JPATUNE_MOVIEGENRE genres1_ on movie0_.ID=genres1_.MOVIE_ID left outer join JPATUNE_DIRECTOR director2_ on movie0_.DIRECTOR_ID=director2_.PERSON_ID left outer join JPATUNE_PERSON person3_ on director2_.PERSON_ID=person3_.ID left outer join JPATUNE_MOVIEROLE cast4_ on movie0_.ID=cast4_.MOVIE_ID left outer join JPATUNE_ACTOR actor5_ on cast4_.ACTOR_ID=actor5_.PERSON_ID left outer join JPATUNE_PERSON person6_ on actor5_.PERSON_ID=person6_.ID where movie0_.ID=?;
Query identical to fetch=EAGER case
Figure 81.34. Relative Test Result
Fetch Lazy Thin Parent.Get Parent with Fetched Children:warmups=2, rounds=10, ave=2.56 [+- 0.80] Fetch Lazy Thick Parent.Get Parent with Fetched Children:warmups=2, rounds=10, ave=2.54 [+- 0.08] Fetch Eager Thin Parent.Get Parent with Fetched Children:warmups=2, rounds=10, ave=2.24 [+- 0.06] Fetch Eager Thick Parent.Get Parent with Fetched Children:warmups=2, rounds=10, ave=2.58 [+- 0.05]
JPA provides several means to access an entity and its relationships. The properties defined for the relationship help define the default query semantics -- which may not be appropriate for all uses. Leverage JOIN FETCH queries when needing to fully load specific relationships prior to ending the transaction.
Pulling back entire rows from table rather than just the count
Figure 81.35. DB Index(es)
alter table jpatune_moviegenre add constraint moviegenre_unique UNIQUE (movie_id, genre) create index movie_role_movie_fdx on jpatune_movierole(movie_id)
Get size of a relationship collection.
Figure 81.37. DAO Code/JPAQL
public int getMovieCastCountByDAORelation(String movieId) {
Movie m = em.find(Movie.class, movieId);
return m==null ? 0 : m.getCast().size();
}
DAO first gets Movie by primary key
DAO follows up by accessing size of relationship
Figure 81.38. Generated SQL - Fetch=LAZY
select movie0_.ID as ID1_2_0_, movie0_.DIRECTOR_ID as DIRECTOR6_2_0_, movie0_.MINUTES as MINUTES2_2_0_, movie0_.RATING as RATING3_2_0_, movie0_.RELEASE_DATE as RELEASE4_2_0_, movie0_.TITLE as TITLE5_2_0_ from JPATUNE_MOVIE movie0_ where movie0_.ID=?
select cast0_.MOVIE_ID as MOVIE4_2_1_, cast0_.ID as ID1_4_1_, cast0_.ID as ID1_4_0_, cast0_.ACTOR_ID as ACTOR3_4_0_, cast0_.MOVIE_ID as MOVIE4_4_0_, cast0_.MOVIE_ROLE as MOVIE2_4_0_ from JPATUNE_MOVIEROLE cast0_ where cast0_.MOVIE_ID=?
Provider will issue follow-on query for all entities in relation
Entity data not used by DAO -- i.e. wasted
Provider will issue one query for entire object graph, to include entities in relation
Entity data not used by DAO -- i.e. even more data is wasted
Figure 81.40. Relative Test Result
Size Lazy.DAO Relation Count:warmups=2, rounds=10, ave=1.83 [+- 0.82] Size Eager.DAO Relation Count:warmups=2, rounds=10, ave=5.40 [+- 0.83]
Pulling back relationship graphs to just obtain the count in that relationship is inefficient and requires DB to do much more work than it has to.
Query for relationships and count resulting rows
Figure 81.41. DAO Code/JPAQL
public int getMovieCastCountByDAO(String movieId) {
return createQuery(
"select role " +
"from Movie m " +
"join m.cast role " +
"where m.id=:id", MovieRole.class)
.setParameter("id", movieId)
.getResultList().size();
}
"select role from Movie m join m.cast role where m.id=:id", params={id=m48306}
DAO forms query for just related entities
Figure 81.42. Generated SQL - Fetch=LAZY
select cast1_.ID as ID1_4_, cast1_.ACTOR_ID as ACTOR3_4_, cast1_.MOVIE_ID as MOVIE4_4_, cast1_.MOVIE_ROLE as MOVIE2_4_ from JPATUNE_MOVIE movie0_ inner join JPATUNE_MOVIEROLE cast1_ on movie0_.ID=cast1_.MOVIE_ID where movie0_.ID=?
Provider forms query for related entities
Figure 81.43. Generated SQL - Fetch=EAGER
select cast1_.ID as ID1_4_, cast1_.ACTOR_ID as ACTOR3_4_, cast1_.MOVIE_ID as MOVIE4_4_, cast1_.MOVIE_ROLE as MOVIE2_4_ from JPATUNE_MOVIE movie0_ inner join JPATUNE_MOVIEROLE cast1_ on movie0_.ID=cast1_.MOVIE_ID where movie0_.ID=?
--this is repeated for each MovieRole.actor select actor0_.PERSON_ID as PERSON1_0_0_ from JPATUNE_ACTOR actor0_ where actor0_.PERSON_ID=? select person0_.ID as ID1_5_0_, person0_.BIRTH_DATE as BIRTH2_5_0_, person0_.FIRST_NAME as FIRST3_5_0_, person0_.LAST_NAME as LAST4_5_0_, person0_.MOD_NAME as MOD5_5_0_ from JPATUNE_PERSON person0_ where person0_.ID=?
Provider forms query for related entities and fetch=EAGER relationship specification causes additional MovieRole.actor to be immediately obtained -- except through separate queries by primary key.
Figure 81.44. Relative Test Result
Size Lazy.DAO Count:warmups=2, rounds=10, ave=1.37 [+- 0.03] Size Eager.DAO Count:warmups=2, rounds=10, ave=16.82 [+- 1.33]
When the entity model over-uses fetch=EAGER, the negative results can be magnified when pulling back unnecessary information from the database.
Issue query to DB to return relationship count
Figure 81.45. DAO Code/JPAQL
public int getMovieCastCountByDB(String movieId) {
return createQuery(
"select count(role) " +
"from Movie m " +
"join m.cast role " +
"where m.id=:id", Number.class)
.setParameter("id", movieId)
.getSingleResult().intValue();
}
"select count(role) from Movie m join m.cast role where m.id=:id", params={id=m48306}
Requesting count(role) rather than returning all roles and counting in DAO.
Figure 81.46. Generated SQL
select count(cast1_.ID) as col_0_0_ from JPATUNE_MOVIE movie0_ inner join JPATUNE_MOVIEROLE cast1_ on movie0_.ID=cast1_.MOVIE_ID where movie0_.ID=?
Produces a single result/row
DB has already optimized the query to remove unnecessary JOIN
Figure 81.48. Relative Test Result
Size Lazy.DB Count:warmups=2, rounds=10, ave=0.50 [+- 0.04] Size Eager.DB Count:warmups=2, rounds=10, ave=0.45 [+- 0.02]
* fetch=LAZY or EAGER has no impact in this solution
It is much more efficient to perform counts within database than to count rows from data returned.
Restructure query to DB to count relations without JOIN
Figure 81.49. DAO Code/JPAQL
public int getCastCountForMovie(String movieId) {
return createQuery(
"select count(*) " +
"from MovieRole role " +
"where role.movie.id=:id", Number.class)
.setParameter("id", movieId)
.getSingleResult().intValue();
}
"select count(*) from MovieRole role where role.movie.id=:id", params={id=m48306}
Querying from MovieRole side allows removal of unnecessary JOIN
Figure 81.50. Generated SQL
select count(*) as col_0_0_ from JPATUNE_MOVIEROLE movierole0_ where movierole0_.MOVIE_ID=?
SQL generated queries single table
No improvement in execution plan since DB already performed this optimization in our last attempt
Figure 81.52. Relative Test Result
Size Lazy.DB Count no Join:warmups=2, rounds=10, ave=0.47 [+- 0.01] Size Eager.DB Count no Join:warmups=2, rounds=10, ave=0.44 [+- 0.02]
* fetch=LAZY or EAGER has no impact in this solution
Although in this and the previous case the DB looks to have already optimized the query, try to eliminate unnecessary complexity within the query.
Performing separate subqueries off initial query
Get people who have acted in same movie as a specific actor
Figure 81.53. DB Index(es)
create index movierole_actor_movie_cdx on jpatune_movierole(actor_id, movie_id) create index movierole_movie_actor_cdx on jpatune_movierole(movie_id, actor_id)
Using DAO to perform initial query and follow-on queries based on results
Figure 81.55. DAO Code/JPAQL
public Collection<Person> oneStepFromPersonByDAO(Person p) {
Collection<Person> result = new HashSet<Person>();
//performing core query
List<String> movieIds = createQuery(
"select role.movie.id from MovieRole role " +
"where role.actor.person.id=:personId", String.class)
.setParameter("personId", p.getId())
.getResultList();
//loop through results and issue sub-queries
for (String mid: movieIds) {
List<Person> people = createQuery(
"select role.actor.person from MovieRole role " +
"where role.movie.id=:movieId", Person.class)
.setParameter("movieId", mid)
.getResultList();
result.addAll(people);
}
return result;
}
"select role.movie.id from MovieRole role where role.actor.person.id=:personId", params={personId=p73897}
//repeated for each returned movie above "select role.actor.person from MovieRole role where role.movie.id=:movieId", params={movieId=m239584}
DAO makes N separate queries based on results of first query
Distinct was not required in query since it was issued per-movie. Distinct was handed within the DAO using a Java Set to hold the results and hashcode()/equals() implemented within Person class.
Figure 81.56. Generated SQL
select movierole0_.MOVIE_ID as col_0_0_ from JPATUNE_MOVIEROLE movierole0_, JPATUNE_ACTOR actor1_ where movierole0_.ACTOR_ID=actor1_.PERSON_ID and actor1_.PERSON_ID=?
--repeated for each returned movie above select person2_.ID as ID1_5_, person2_.BIRTH_DATE as BIRTH2_5_, person2_.FIRST_NAME as FIRST3_5_, person2_.LAST_NAME as LAST4_5_, person2_.MOD_NAME as MOD5_5_ from JPATUNE_MOVIEROLE movierole0_, JPATUNE_ACTOR actor1_ inner join JPATUNE_PERSON person2_ on actor1_.PERSON_ID=person2_.ID where movierole0_.ACTOR_ID=actor1_.PERSON_ID and movierole0_.MOVIE_ID=?
SQL generated queries single table
Range scan of compound index(actor_id, person_id) used to satisfy the person_id in where clause and movie_id for select clause
* Query was optimized to bypass Actor table
Range scan on compound_index(movie_id, actor_id) to satisfy where condition for Movie.id and locate MovieRole rowId
Unique scan of Person primary key index to obtain rowId for Person.id=MovieRole.actor.id
Person row accessed by rowId to satisfy select clause
* Query was optimized to bypass Actor table
** Compound index(movie_id, actor_id) permitted MovieRole table and lookup of MovieRole.actorId to be bypassed.
Figure 81.59. Relative Test Result
-3924 people found Loop Lazy.DAO Loop:warmups=2, rounds=10, ave=4.91 [+- 0.88]
Query loops driven off of previous query results can be a sign the follow-queries could have been combined with the initial query to be accomplished within the database within a single statement.
Also beware of how the loop size can grow over the lifetime of the application. The number may grow significantly after testing/initial deployment to a significant size (i.e., from 100 to 100K loops). Defining as a single statement and applying paging limits can help speed the originally flawed implementation.
Expressing nest query as subquery to resolve within database.
Figure 81.60. DAO Code/JPAQL
public List<Person> oneStepFromPersonByDB(Person p) {
return createQuery(
"select distinct role.actor.person from MovieRole role " +
"where role.movie.id in (" +
"select m.id from Movie m " +
"join m.cast role2 " +
"where role2.actor.person.id=:id)", Person.class)
.setParameter("id", p.getId())
.getResultList();
}
"select distinct role.actor.person from MovieRole role where role.movie.id in ( select m.id from Movie m join m.cast role2 where role2.actor.person.id=:id )", params={id=p73897}
Distinct was required to remove duplicates
Figure 81.61. Generated SQL
select distinct person2_.ID as ID1_5_, person2_.BIRTH_DATE as BIRTH2_5_, person2_.FIRST_NAME as FIRST3_5_, person2_.LAST_NAME as LAST4_5_, person2_.MOD_NAME as MOD5_5_ from JPATUNE_MOVIEROLE movierole0_, JPATUNE_ACTOR actor1_ inner join JPATUNE_PERSON person2_ on actor1_.PERSON_ID=person2_.ID where movierole0_.ACTOR_ID=actor1_.PERSON_ID and ( movierole0_.MOVIE_ID in ( select movie3_.ID from JPATUNE_MOVIE movie3_ inner join JPATUNE_MOVIEROLE cast4_ on movie3_.ID=cast4_.MOVIE_ID, JPATUNE_ACTOR actor5_ where cast4_.ACTOR_ID=actor5_.PERSON_ID and actor5_.PERSON_ID=? ) )
SQL generated uses a non-correlated subquery
Range scan of MovieRole composite(actor_id, movie_id) used to satisfy subquery where clause for person_id and obtain movie_id
Range scan of MovieRole composite(movie_id, actor_id) used to join movie_ids with root query and obtain person_id
Unique scan of Person primary key index to locate rowId
Person row accessed by rowId
* cast4_ and movierole0_ are both MovieRoles. cast4_ is joined with Movie and subject Person in subquery. movierole0_ is joined with Movie and associated Person in root query.
** execution plan indicates subquery could have been re-written as a JOIN
Figure 81.63. Relative Test Result
-3924 people found Loop Lazy.DB Loop Distinct:warmups=2, rounds=10, ave=2.52 [+- 0.09]
If possible, express query as a single transaction to the database rather than pulling data back and re-issuing subqueries from the DAO. Apply paging constraints in order to address issues with excess growth over time.
Placing reasonable limits on amount of data returned
Figure 81.64. DB Index(es)
alter table jpatune_moviegenre add constraint moviegenre_unique UNIQUE (movie_id, genre) create index movie_role_movie_fdx on jpatune_movierole(movie_id)
Figure 81.65. Service Tier Code
dao.oneStepFromPerson(kevinBacon, offset, PAGE_SIZE, "role.actor.person.lastName ASC")
* Service tier provides not only offset and limit information, but would also benefit from using a sort. The DAO would have to pull back all rows in order to implement the sort within the DAO -- while the DB-based solution can leave all data within the database. Sort will only be implemented here in the DB-based solution.
Implementing paging within the DAO
Figure 81.66. DAO Code/JPAQL
public Collection<Person> oneStepFromPersonByDAO(Person p, Integer offset, Integer limit, String orderBy) {
Collection<Person> result = new HashSet<Person>();
//performing core query
List<String> movieIds = createQuery(
"select role.movie.id from MovieRole role " +
"where role.actor.person.id=:personId", String.class)
.setParameter("personId", p.getId())
.getResultList();
//loop through results and issue sub-queries
int pos=0;
for (String mid: movieIds) {
List<Person> people = createQuery(
"select role.actor.person from MovieRole role " +
"where role.movie.id=:movieId", Person.class)
.setParameter("movieId", mid)
.getResultList();
if (offset==null || (offset!=null && pos+people.size() > offset)) {
for(Person pp: people) {
if (offset==null || pos>=offset) {
result.add(pp);
if (limit!=null && result.size() >= limit) { break; }
}
pos+=1;
}
} else {
pos+=people.size();
}
if (limit!=null && result.size() >= limit) { break; }
}
return result;
}
DAO logic gets complicated when self-implementing paging.
OrderBy is not implemented by this DAO algorithm. All rows in the table would be required in order to perform ordering within the DAO.
Figure 81.69. Relative Test Result
Loop Lazy.DAO Page 0:warmups=1, rounds=10, ave=0.39 [+- 0.09] Loop Lazy.DAO Page 10:warmups=1, rounds=10, ave=1.01 [+- 0.45] Loop Lazy.DAO Page 50:warmups=1, rounds=10, ave=2.80 [+- 0.79] Loop Eager.DAO Page 0:warmups=1, rounds=10, ave=0.38 [+- 0.10] Loop Eager.DAO Page 10:warmups=1, rounds=10, ave=1.10 [+- 0.64] Loop Eager.DAO Page 50:warmups=1, rounds=10, ave=2.53 [+- 0.06]
This example performs better or equal to previous loop/subquery example when earlier pages are requested -- thus fewer rows returned
fetch=EAGER/LAZY have no impact on this test since the entity queried for has no relationships
Paging within the Java code starts off near equivalent to the database solution for small row sizes but gradually gets worse when row sizes increase and later pages are requested.
Implementing paging within the database
Figure 81.70. DAO Code/JPAQL
public List<Person> oneStepFromPersonByDB(Person p, Integer offset, Integer limit, String orderBy) {
return withPaging(createQuery(
"select distinct role.actor.person from MovieRole role " +
"where role.movie.id in (" +
"select m.id from Movie m " +
"join m.cast role2 " +
"where role2.actor.person.id=:id)", Person.class), offset, limit, orderBy)
.setParameter("id", p.getId())
.getResultList();
}
"select distinct role.actor.person from MovieRole role where role.movie.id in ( select m.id from Movie m join m.cast role2 where role2.actor.person.id=:id ) order by role.actor.person.lastName ASC", params={id=p73897}, offset=500, limit=50
JPA calls to TypedQuery.setFirstResult(), setMaxResults(), and adding the orderBy to the text of the query are abstracted behind withQuery() and createQuery() helper functions within the DAO example class and are not shown here
Figure 81.71. Generated SQL
select * from ( select row_.*, rownum rownum_ from ( select distinct person2_.ID as ID1_5_, person2_.BIRTH_DATE as BIRTH2_5_, person2_.FIRST_NAME as FIRST3_5_, person2_.LAST_NAME as LAST4_5_, person2_.MOD_NAME as MOD5_5_ from JPATUNE_MOVIEROLE movierole0_, JPATUNE_ACTOR actor1_ inner join JPATUNE_PERSON person2_ on actor1_.PERSON_ID=person2_.ID, JPATUNE_PERSON person7_ where movierole0_.ACTOR_ID=actor1_.PERSON_ID and actor1_.PERSON_ID=person7_.ID and ( movierole0_.MOVIE_ID in ( select movie3_.ID from JPATUNE_MOVIE movie3_ inner join JPATUNE_MOVIEROLE cast4_ on movie3_.ID=cast4_.MOVIE_ID, JPATUNE_ACTOR actor5_ where cast4_.ACTOR_ID=actor5_.PERSON_ID and actor5_.PERSON_ID=? ) ) order by person7_.LAST_NAME ASC ) row_ where rownum <= ?) where rownum_ > ?
Generated SQL is essentially the same as the looping/subquery example except with the addition of "order by" and paging constructs.
Execution plan is similar as described in the looping/subquery example
Extra cost to sort rows prior to paging operation added
Figure 81.73. Relative Test Result
Loop Lazy.DB Page 0:warmups=1, rounds=10, ave=0.32 [+- 0.02] Loop Lazy.DB Page 10:warmups=1, rounds=10, ave=0.31 [+- 0.01] Loop Lazy.DB Page 50:warmups=1, rounds=10, ave=0.32 [+- 0.01] Loop Eager.DB Page 0:warmups=1, rounds=10, ave=0.32 [+- 0.01] Loop Eager.DB Page 10:warmups=1, rounds=10, ave=0.32 [+- 0.01] Loop Eager.DB Page 50:warmups=1, rounds=10, ave=0.32 [+- 0.00]
Since only the requested page of rows is returned, delegating paging to the database provides consistent performance for varying row quantities.
Implementing paging across multiple subquery calls to the database can get ugly and error-prone. Implementing paging for a single query issued to the database is quite trivial.
Hidden performance costs for fetch and resolving with JOIN FETCH and value queries
Hidden performance costs for row counts and resolving with a query function
Performance costs when issuing repeated queries of an initial query from DAO and resolving using subqueries in database
Performance and functional costs when implementing paging in DAO and resolving by delegating paging to database
Figure 82.1. Explain Plan: Where Column Not Indexed
EXPLAIN select m.title, role.movie_role from queryex_movie m join queryex_movierole role on role.movie_id = m.id where m.rating='R' and role.movie_role='Chip Diller';
PLAN SELECT M.TITLE, ROLE.MOVIE_ROLE FROM PUBLIC.QUERYEX_MOVIEROLE ROLE /* PUBLIC.QUERYEX_MOVIEROLE.tableScan */ /* WHERE ROLE.MOVIE_ROLE = 'Chip Diller' */ INNER JOIN PUBLIC.QUERYEX_MOVIE M /* PUBLIC.PRIMARY_KEY_C7B: ID = ROLE.MOVIE_ID */ ON 1=1 WHERE (ROLE.MOVIE_ID = M.ID) AND ((M.RATING = 'R') AND (ROLE.MOVIE_ROLE = 'Chip Diller'))
Figure 82.2. Index Where Column
create index movierole_role_movie_idx on queryex_movierole(movie_role)
Figure 82.3. Explain Plan: Where Column Indexed
PLAN SELECT M.TITLE, ROLE.MOVIE_ROLE FROM PUBLIC.QUERYEX_MOVIEROLE ROLE /* PUBLIC.MOVIEROLE_ROLE_MOVIE_IDX: MOVIE_ROLE = 'Chip Diller' */ /* WHERE ROLE.MOVIE_ROLE = 'Chip Diller' */ INNER JOIN PUBLIC.QUERYEX_MOVIE M /* PUBLIC.PRIMARY_KEY_C7B: ID = ROLE.MOVIE_ID */ ON 1=1 WHERE (ROLE.MOVIE_ID = M.ID) AND ((M.RATING = 'R') AND (ROLE.MOVIE_ROLE = 'Chip Diller'))
Some topics of possible interest that we did not cover.
Dynamic SQL - parsing penalties paid by building complete SQL strings rather than using a prepared statement template and value parameters.
Cluster Indexes - structurally organize parent and child tables within same data block when commonly accessed together through join operations and rarely alone.
Concurrency - sessions blocked inadvertently by concurrent sessions
...
Table of Contents
Introduce the purpose of an EJB (why?)
Introduce the different EJB types
Introduce different EJB interface options
Introduce different deployment options for EJBs
At the completion of this topic, the student shall be able to:
State reasons for/against adding an EJB to a JavaEE application
State the four different types of EJBs and reasons for/against using each
State the three different built-in interface types and when to/not-to use each
State the three different deployment options and when to/not-to use each
Potential tight coupling between clients and complex business objects
Too many method invocations between clients and business objects
Business methods exposed for misuse by clients
Hide complex interactions behind a simple client interface
Reduce the number of business objects exposed to the client across the network
Hide implementation, interactions, and dependencies of business components
Use a business logic class to encapsulate the interactions required with the business objects
Simplifies complex systems
May appear to be a no value pass-thru in simple systems
Should involve more than one business object per facade
Should have more than one facade per system
Decouples the business objects from being aware of one another
Improves perceived network performance for remote interfaces
Centralizes security and transactions in some cases
Remote clients require access to server-side resources.
Core service layer (business logic and its business objects) contains fine grain objects and methods
Significant number of fine-grain remote calls will not work
Provide a Remote Facade
Coarse-grain facade over service layer
Contains no detailed business logic -- it calls it
Translates course-grain methods and objects into fine-grain method calls and objects to/from service layer
Bulk accessors wrap fine-grain access methods
Service Layer does not have a remote interface
Fine-grain business objects mapped to database might not be serializable
Business Objects represent too much information or behavior to transfer to remote client
Client may get information they don't need
Client may get information they can't handle
Client may get information they are not authorized to use
Client may get too much information/behavior to be useful (e.g., entire database serialized to client)
Some clients are local and can share object references with business logic
Handling specifics of remote clients outside of core scope of business logic
Layer a Remote Facade over Business Logic
Remote Facade constructs Data Transfer Objects (DTOs) from Business Objects that are appropriate for remote client view
Remote Facade uses DTOs to construct or locate Business Objects to communicate with Business Logic
Clients only get what they need
Clients only get what they understand
Clients only get what they are authorized to use
Remote and Local interfaces to services are different
Makes it harder to provide location transparency
Lightweight Business Objects can be used as DTOs
Remote Facade must make sure they are “pruned” of excess related items before transferring to client
Remote Facade must make sure they are “cleaned” of DAO persistence classes before transferring to client
Implement a task
Interact with other beans
Examples:
TaxDistrict.calcTax(double amount)
Teller.transfer(long fromAccount, long toAccount, double amount)
Registrar.listStudents(String course, int offset, int limit)
Session EJB per Use Case too fine
CreateAccountEJB
DepositEJB
WithdrawEJB
TransferEJB
Group cohesive Use Cases into larger-grain Session EJBs
Teller
createAccount()
deposit()
withdraw()
transfer()
All are POJO-based
i.e., most Java classes can be made into an EJB
Figure 85.1. Example Entity
@javax.persistence.Entity
@javax.persistence.Queries({
@javax.persistence.Query(name="Reservation.getReservationsByName", query="
select r from Reservation r
where r.person.firstName like :firstName and
r.person.lastName like :lastName")
})
public class Reservation {
@javax.persistence.Id
private long id;
private String confirmation;
@javax.persistence.Temporal(javax.persistence.TemporalType.DATE)
private Date startDate;
@javax.persistence.Temporal(javax.persistence.TemporalType.DATE)
private Date endDate;
@javax.persistence.ManyToOne
@javax.persistence.JoinColumn
private Person person;
Represents shared data in the database
Typically interact at row/object level
Moved to Java Persistence API (JPA) Spec in EJB 3.0/JavaEE 5
Not constrained to server-side
Examples:
Account, Owner
Account.deposit(double amount)
Account.getOwner()
Good for representing persistent information
Contains no means to access external resources other than directly related information
Figure 85.3. Example Stateless EJB
@javax.ejb.Stateless
public class HotelMgmtEJB implements HotelMgmtRemote, HotelMgmtLocal {
@PersistenceContext(unitName="ejbjpa-hotel")
private EntityManager em;
private HotelDAO dao;
private HotelMgmt hotelMgmt;
@PostConstruct
public void init() {
dao = new JPAHotelDAO();
((JPAHotelDAO)dao).setEntityManager(em);
hotelMgmt = new HotelMgmtImpl();
((HotelMgmtImpl)hotelMgmt).setHotelDao(dao);
}
@Override
public Room getRoom(int number) {
return hotelMgmt.getRoom(number);
}
}
Performs stateless actions on server-side
Maintains no conversational state
Each method ignorant of what went before it and after it
Persistence Context re-instantiated with each call (Transaction-Scope)
Methods take in a set of parameters and return a result
Maintains implementation state
example: DataSource
example: JMS connections
example: References to other EJBs
Examples:
TaxCalculator.calcTax(doubt amount)
BuyerMgmt.placeBid(int auctionId, double amount)
Dmv.getExpiredLicenses()
Good for implementing stateless access to resources
Contains no means to provide individualized access other than through separate deployments and call parameters
Figure 85.5. Example Stateful EJB
@javax.ejb.Stateful
public class ReservationEJB implements ReservationRemote {
@PersistenceContext(unitName="ejbjpa-hotel", type=PersistenceContextType.EXTENDED)
private EntityManager em;
@EJB
private HotelMgmtLocal hotelMgmt;
@Resource
private SessionContext ctx;
private List<Guest> guests = new LinkedList<Guest>();
@Override
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public int addGuest(Guest guest) {
if (guest!=null) {
guests.add(guest);
em.persist(guest); //<== no transaction active yet
}
return guests.size();
}
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Remove
public List<Guest> reserveRooms() throws RoomUnavailableExcepton {
List<Room> rooms = hotelMgmt.getAvailableRooms(null, 0, guests.size());
if (rooms.size() < guests.size()) {
//no rollback needed, we didn't do anything
throw new RoomUnavailableExcepton(String.format("found on %d out of %d required",
rooms.size(), guests.size()));
}
//assign each one of them a room
List<Guest> completed = new ArrayList<Guest>(guests.size());
Iterator<Room> roomItr = rooms.iterator();
for (Guest guest: guests) {
Room room = roomItr.next();
try {
//the room could be unavailable -- depending on whether pessimistic lock created
guest = hotelMgmt.checkIn(guest, room); //<== will attempt to also persist guest
completed.add(guest);
} catch (RoomUnavailableExcepton ex) {
//rollback any previous reservations
ctx.setRollbackOnly();
throw ex;
}
}
return completed;
}
}
Used to cache resources for client on server-side
Maintains conversational state
Object can cache values between calls
example: iterator
Persistence Context (with cache of entities) can be retained between calls (Extended-Scope)
Lifetime ends on timeout or specific client call
Maintains implementation state
Not sharable between clients
All resources allocated to perform work for one instance
Able to react to transaction completion/rollback (i.e., get callback)
example: commit data cache
example: issue message
Good for caching client state and back-end resource state over a multi-call session
Inefficient to scale cached state for each user and across multiple servers
Figure 85.7. Example Singleton EJB
@javax.ejb.Singleton
@javax.ejb.Startup
@javax.ejb.ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@javax.ejb.AccessTimeout(value=3000)
public class StatsEJB implements StatsLocal, StatsRemote {
private int total;
@Override
@javax.ejb.Lock(LockType.WRITE)
public void increment() { ... }
@Override
@javax.ejb.Lock(LockType.READ)
public int getTotal() { ... }
...
}
Provides a single Session EJB instance on server-side
Single instance shared across all clients
Has READ/WRITE lock and timeout concerns
Good for tracking shared state in memory
Creates a bottleneck for concurrent access
Figure 85.8. Example Message Driven EJB
@javax.ejb.MessageDriven(activationConfig={
@javax.ejb.ActivationConfigProperty(
propertyName="destinationType",
propertyValue="javax.jms.Topic"),
@javax.ejb.ActivationConfigProperty(
propertyName="destination",
propertyValue="java:/topic/ejava/examples/asyncMarket/topic1"),
@javax.ejb.ActivationConfigProperty(
propertyName="messageSelector",
propertyValue="JMSType in ('forSale', 'saleUpdate')"),
@javax.ejb.ActivationConfigProperty(
propertyName="acknowledgeMode",
propertyValue="Auto-acknowledge")
})
public class BuyerMDB implements MessageListener {
@javax.ejb.EJB
private BuyerLocal buyer;
@javax.annotation.security.PermitAll
public void onMessage(javax.jms.Message message) {
...
}
}
Used to receive JMS messages
Similar to Stateless EJB, but with no callable interface
Typically provides a JMS facade for injected Session EJBs
Good for implementing a JMS interface facade to business logic
Unable to be called outside the scope of a JMS/JCA call -- should delegate to Session EJBs
Has no client context (i.e., anonymous)
Figure 85.9. Example Business Interface
public interface Teller {
Account createAccount(String accNum) throws BankException;
void updateAccount(Account account) throws BankException;
Account getAccount(String acctNum) throws BankException;
}
Pure POJO
No ties to EJB
No sense of local or remote
Figure 85.10. Example Remote Interface
@javax.ejb.Remote
public interface TellerRemote extends Teller {
Ledger getLedger() throws BankException;
List<Owner> getOwners(int index, int count) throws BankException;
String whoAmI();
}
public class Ledger implements java.io.Serializable {
...
}
Uses RMI-based technology to provide built-in implementation
Data types must be built-in types or Serializable types
Pass-by-value semantics between client and EJB
Usable by local (same application/EAR) and remote (outside of same application/EAR) endpoints
Pass-by-reference semantics between client and EJB
client and EJB share the same object instance
Must load class from same classloader
Data types need not be built-in or Serializable types
Restricted to be from application (e.g., must be from same EAR to share reference)
Remote clients are different from local clients and require different considerations when designing the remote interface. Although JavaEE/EJB make it technically possible to implement location independence the interface hierarchies, this is usually not feasible when dealing with non-trivial interface requirements. Design local interfaces for local clients (within the deployed application) and design remote interfaces for remote clients.
Figure 85.12. Example No Interface EJB
@javax.ejb.Singleton
@javax.ejb.Startup
@javax.ejb.ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@javax.ejb.AccessTimeout(value=3000)
public class StatsEJB {
private int total;
@javax.ejb.Lock(LockType.WRITE)
public void increment() { ... }
@javax.ejb.Lock(LockType.READ)
public int getTotal() { ... }
...
}
Interfaces are an encapsulation issue and not an EJB issue
If you do not need the encapsulation of an interface -- EJB will *not* make you add one for local access
Must have interface when using remote clients
JAX-RS support
SOAP support
CORBA/IIOP support
Figure 85.13. Example JAX-RS Interface
import java.net.URI;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
@Path("/products")
public class ProductsResource {
@Inject
private InventoryMgmtEJB ejb;
@Context
private UriInfo uriInfo;
@POST @Path("")
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@Produces(MediaType.APPLICATION_XML)
public Response createProduct(
@FormParam("name") String name,
@FormParam("quantity") Integer quantity,
@FormParam("price") Double price,
@FormParam("category") String category) {
Product product = new Product(name, quantity, price);
Product p = ejb.addProduct(product, category);
URI uri = UriBuilder.fromUri(uriInfo.getAbsolutePath())
.path(ProductsResource.class, "getProduct")
.build(p.getId());
return Response.created(uri)
.entity(p)
.build();
}
}
EJB modules are deployable by themselves
Contain no mechanism to bring in dependencies
Can only be communicated with using remote interface
Can deploy multiple EJB modules, multiple WARs, and utility libraries
Provides means to use local interfaces/pass by reference between EJBs and WAR/WEB
Convenient wrapper for entire application
Can deploy multiple EJB modules within WEB-INF/lib
Can deploy multiple EJB beans within WEB-INF/classes
Realistically useful when Servlet or JSP wishes to use declarative JTA transactions or security access control within a helper class
Ideal use for No-interface Session EJB
Eliminates need for EAR in some cases
Arranged in a parent/child relationship
May access classes/resources loaded by local and parent class loaders
Do not have access to classes/resources loaded by sibling class loaders
Requests for classes are first delegated to parent class loader
Classes loaded by one EJB deployment are not sharable with other deployments
Must use remote interfaces and pass-by-value serialized data
No pass by reference
EJB classes/resources can be shared with single Web App if loaded in WEB-INF/lib
Local interfaces/resources are local to that WAR
EJB classes cannot be shared across WARs
Must again resort to remote interfaces and pass-by-value serialized data to share implementations
EAR class loader is root to deployed application
EJB class loader is host to all EJBs and root to WAR class loaders
WAR class loaders are separate
Allows two separate WARs to generate the same class from two separate index.jsp files
Disallows WAR from duplicating EAR/EJB-supplied classes
EJBs and WARs-to-EJBs can share information using pass-by-reference
"JSR 345: Enterprise JavaBeansTM 3.2", Marina Vatkina, https://jcp.org/en/jsr/detail?id=345, 2013 Java Community Press.
"EJB Version History", Wikipedia, http://en.wikipedia.org/wiki/Enterprise_JavaBeans#Version_history, August 17, 2014.
Copyright © 2015 jim stafford (jim.stafford@jhu.edu)
Built on: 2015-11-18 02:38 EST
Abstract
This presentation provides information for developing a basic EJB (one without resource or dependency requirements) and deploying it to the server using WAR and EAR-based deployments.
Table of Contents
Show how EJBs build on POJO concepts
Create and deploy a basic EJB (without resource requirements or other dependencies)
Show different deployment options
Show how to access the EJB
Create an integration test for the EJB
At the completion of this topic, the student shall
be able to:
Create an EJB module
Create a simple @Stateless EJB in the EJB module
Add required and key aspects to the EJB
Unit test the EJB as a POJO
Deploy the EJB to the server using an EAR
Deploy the EJB to the server using a WAR
Integration test the EJB deployed to the server
EJBs are based on Plain Old Java Objects (POJOs)
Standard POJO rules apply...
...
public class GreeterEJB implements Greeter {
private static final Logger logger = LoggerFactory.getLogger(GreeterEJB.class);
...
}
Must not write to static fields. Read-only static fields are allowed (suggests use of final)
Must not use thread synchronization primitives to synchronize execution
Must not manage/manipulate threads, class loaders, or security managers
Must not attempt to become a network server and form a network listen and similar actions
...
@Override
public String sayHello(String name) throws BadRequestException {
logger.debug("sayHello({})", name);
if (name == null || name.isEmpty()) {
throw new BadRequestException("you must have a name for me to say hello");
}
return "hello " + name;
}
Normal stateless POJO method at this point
import info.ejava.examples.ejb.basic.dto.Greeting;
import info.ejava.examples.ejb.basic.dto.Name;
public interface Greeter {
String sayHello(String name) throws BadRequestException;
Greeting sayHello(Name name) throws BadRequestException;
}
Provides an abstraction of the public class business methods
Some methods use simple/primitive types. Others use complex/custom classes.
All types conveniently defined using Serializable types at this point to permit use of remote interface in future
public class Name implements Serializable {
private String firstName;
private String lastName;
...
public class Greeting implements Serializable {
private Date date;
private String message;
...
Data abstraction necessary for local and remote interaction
Serializable not necessary until we use this in a remote interface
import static org.junit.Assert.*;
import info.ejava.examples.ejb.basic.dto.Greeting;
import info.ejava.examples.ejb.basic.dto.Name;
import info.ejava.examples.ejb.basic.ejb.BadRequestException;
import info.ejava.examples.ejb.basic.ejb.Greeter;
import info.ejava.examples.ejb.basic.ejb.GreeterEJB;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GreeterTest {
private static final Logger logger = LoggerFactory.getLogger(GreeterTest.class);
private Greeter greeter;
@Before
public void setUp() {
greeter = new GreeterEJB();
}
...
}
Test case used to verify the core functionality of the POJO -- prior to deployment as EJB
Each test case should be independent of another and not depend on a particular order
@Test
public void pojoGreeter() throws BadRequestException {
logger.info("*** pojoGreeter ***");
String name = "cat inhat";
String greeting = greeter.sayHello(name);
assertTrue("greeter did not say my name", greeting.contains(name));
}
@Test(expected = BadRequestException.class)
public void badName() throws BadRequestException {
logger.info("*** badName ***");
greeter.sayHello("");
}
@Test
public void dto() throws BadRequestException {
logger.info("*** dto ***");
Name name = new Name("thing", "one");
Greeting greeting = greeter.sayHello(name);
assertTrue("greeter did not say my name", greeting.getGreeting()
.contains(name.getFirstName()));
}
Each test method focused on testing a specific scenario or state
Contains assert statements to verify expected results
Each test method should be independent of another and not depend on a particular order
Production classes typically deployed in a Java archive (JAR)
$ jar tf target/ejb-basic-ejb-4.0.0-SNAPSHOT.jar ... info/ejava/examples/ejb/basic/ejb/BadRequestException.class info/ejava/examples/ejb/basic/ejb/Greeter.class info/ejava/examples/ejb/basic/ejb/GreeterEJB.class info/ejava/examples/ejb/basic/dto/Name.class info/ejava/examples/ejb/basic/dto/Greeting.class ...
|-- pom.xml `-- src |-- main | `-- java | `-- info | `-- ejava | `-- examples | `-- ejb | `-- basic | |-- dto | | |-- Greeting.java | | `-- Name.java | `-- ejb | |-- BadRequestException.java | |-- GreeterEJB.java | `-- Greeter.java `-- test |-- java | `-- info | `-- ejava | `-- examples | `-- ejb | `-- basic | `-- pojo | `-- GreeterTest.java `-- resources `-- log4j.xml
Module contains separate source trees for production and unit test code
Unit tests should run with every module build
Unit tests should auto-evaluate themselves so that no human analysis is required
$ mvn clean test ... [INFO] Deleting .../ejb-basic-ejb/target [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ ejb-basic-ejb --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] skip non existing resourceDirectory .../ejb-basic-ejb/src/main/resources [INFO] [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ ejb-basic-ejb --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 5 source files to .../ejb-basic-ejb/target/classes [INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ ejb-basic-ejb --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 1 resource [INFO] [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ ejb-basic-ejb --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 1 source file to .../ejb-basic-ejb/target/test-classes [INFO] [INFO] --- maven-surefire-plugin:2.17:test (default-test) @ ejb-basic-ejb --- [INFO] Surefire report directory: .../ejb-basic-ejb/target/surefire-reports ------------------------------------------------------- T E S T S ------------------------------------------------------- Running info.ejava.examples.ejb.basic.pojo.GreeterTest 16:57:12,237 INFO (GreeterTest.java:41) -*** dto *** 16:57:12,243 DEBUG (GreeterEJB.java:41) -sayHello(Name [firstName=thing, lastName=one]) 16:57:12,249 INFO (GreeterTest.java:27) -*** pojoGreeter *** 16:57:12,250 DEBUG (GreeterEJB.java:27) -sayHello(cat inhat) 16:57:12,252 INFO (GreeterTest.java:35) -*** badName *** 16:57:12,255 DEBUG (GreeterEJB.java:27) -sayHello() Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.239 sec - in info.ejava.examples.ejb.basic.pojo.GreeterTest Results : Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.815 s ...
Basic POJO/JAR pom thus far
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>info.ejava.examples.ejb.basicejb</groupId>
<artifactId>ejb-basic-ejb</artifactId>
<packaging>jar</packaging>
<dependencies>
<!-- core implementation dependencies -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- test dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Add essential EJB aspects to POJO classes
Deploy to server and test
import javax.ejb.Stateless;
...
@Stateless
public class GreeterEJB implements Greeter {
Other types include @Stateful, @Singleton, and @MessageDriven
import javax.ejb.Remote;
...
@Remote
public interface Greeter {
Defines business remote interface for EJB
Alternately can define @Remote(Interface.class) within EJB bean class
@Stateless
@Remote(Greeter.class)
public class GreeterEJB implements Greeter {
public interface Greeter {
String sayHello(String name) throws BadRequestException;
Greeting sayHello(Name name) throws BadRequestException;
}
import javax.ejb.Remote;
@Remote
public interface GreeterRemote extends Greeter {
}
Useful when EJB implements legacy interface
EJB must then implement Remote or declare @Remote
@Stateless
public class GreeterEJB implements GreeterRemote {
@Stateless
@Remote(GreeterRemote.class)
public class GreeterEJB implements Greeter {
Optional callbacks to initialize and destroy resources
@PostConstruct
public void init() {
logger.info("*** GreeterEJB:init({}) ***", super.hashCode());
}
Called after all resources have been injected and before first business method called
No args, void return, no declared checked exceptions
Used to ready bean to satisfy business methods
@PreDestroy
public void destroy() {
logger.info("*** GreeterEJB:destroy({}) ***", super.hashCode());
}
If called, will be after last business method
EJB artifact looks very much like a normal POJO archive
Since our EJB is 100% annotation-based, we have no need for META-INF/ejb-jar.xml descriptor
$ jar tf target/ejb-basic-ejb-4.0.0-SNAPSHOT.jar ... info/ejava/examples/ejb/basic/ejb/BadRequestException.class info/ejava/examples/ejb/basic/ejb/Greeter.class info/ejava/examples/ejb/basic/ejb/GreeterEJB.class info/ejava/examples/ejb/basic/ejb/GreeterRemote.class info/ejava/examples/ejb/basic/dto/Name.class info/ejava/examples/ejb/basic/dto/Greeting.class ...
|-- pom.xml `-- src |-- main | `-- java | `-- info | `-- ejava | `-- examples | `-- ejb | `-- basic | |-- dto | | |-- Greeting.java | | `-- Name.java | `-- ejb | |-- BadRequestException.java | |-- GreeterEJB.java | |-- Greeter.java | `-- GreeterRemote.java `-- test |-- java | `-- info | `-- ejava | `-- examples | `-- ejb | `-- basic | `-- pojo | `-- GreeterTest.java `-- resources `-- log4j.xml
Module contains separate source trees for production and unit test code
EJB module deployed either as naked EJB
17:19:52,979 INFO [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-1) JNDI bindings for session bean named GreeterEJB in deployment unit deployment "ejb-basic-ejb.jar" are as follows: java:global/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:app/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:module/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:jboss/exported/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:global/ejb-basic-ejb/GreeterEJB java:app/ejb-basic-ejb/GreeterEJB java:module/GreeterEJB 17:19:53,012 INFO [org.jboss.weld.deployer] (MSC service thread 1-1) JBAS016005: Starting Services for CDI deployment: ejb-basic-ejb.jar 17:19:53,029 INFO [org.jboss.weld.deployer] (MSC service thread 1-2) JBAS016008: Starting weld service for deployment ejb-basic-ejb.jar 17:19:53,485 INFO [org.jboss.as.server] (DeploymentScanner-threads - 1) JBAS018559: Deployed "ejb-basic-ejb.jar" (runtime-name : "ejb-basic-ejb.jar")
JBoss server prints out JNDI names to access EJB through...
@Remote
No interface
Globally within the server (java:global)
Within the deployed application (java:app)
Within the EJB module (java:module)
External to server (java:global/exported)
Derive remote client JNDI name from "exported" name
ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote
Local and No-interface interfaces are not exposed to remote clients
packaging=ejb
Required in order for downstream containers to recognize as EJB component
javax.ejb:javax.ejb-api
Supplies EJB API definitions
maven-ejb-plugin
Builds EJB and optional ejbclient artifact
<project>
...
<packaging>ejb</packaging>
<dependencies>
...
<dependency>
<groupId>javax.ejb</groupId>
<artifactId>javax.ejb-api</artifactId>
<scope>provided</scope>
</dependency>
...
</dependencies>
<build>
<plugins>
<!-- tell EJB plugin to create a client-jar -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ejb-plugin</artifactId>
<configuration>
<generateClient>true</generateClient>
<clientExcludes>
<clientExclude>**/ejb/*Local.class</clientExclude>
<clientExclude>**/ejb/*EJB.class</clientExclude>
</clientExcludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Initialized EJB bean instances can be cached in a pool
Instances from the pool can be re-used rather than destroy and re-intialize a new instance
JBoss pools singleton and stateful session beans by default
# standalone/configuration/standalone.xml
<session-bean>
<stateful default-access-timeout="5000" cache-ref="simple" passivation-disabled-cache-ref="simple"/>
<singleton default-access-timeout="5000"/>
</session-bean>
JBoss defines a stateless session bean pool for use but left unassigned
# standalone/configuration/standalone.xml
<pools>
<bean-instance-pools>
<strict-max-pool name="slsb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
<strict-max-pool name="mdb-strict-max-pool" max-pool-size="20" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
</bean-instance-pools>
</pools>
No pooling can be costly when initialization costs are high
Stateless and MDB EJB pooling can be enabled globally
# standalone/configuration/standalone.xml
<session-bean>
<stateless>
<bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
</stateless>
<stateful default-access-timeout="5000" cache-ref="simple" passivation-disabled-cache-ref="simple"/>
<singleton default-access-timeout="5000"/>
</session-bean>
Pooling can be costly when initialization costs are low
Stateless and MDB EJB pooling can be enabled on a per-application(*)/per-EJB(GreeterEJB) basis
# META-INF/jboss-ejb3.xml
<jboss:ejb-jar xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:s="urn:security"
xmlns:c="urn:clustering:1.0"
xmlns:p="urn:ejb-pool:1.0"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-2_0.xsd
http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"
version="3.1"
impl-version="2.0">
<assembly-descriptor>
<!-- defines EJB pool else not pooled -->
<p:pool>
<ejb-name>*</ejb-name>
<p:bean-instance-pool-ref>slsb-strict-max-pool</p:bean-instance-pool-ref>
</p:pool>
</assembly-descriptor>
</jboss:ejb-jar>
Added necessary aspects to POJO to make it an EJB
Annotated class as @Stateless
Annotated remote interface as @Remote
Deployed to server
Obtained JNDI name of naked EJB-based @Remote
(component-name)/(ejb-name)!(remote-interface-name)
ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote
Useful when need to deploy EJB with dependent artifacts or without WAR
|-- ejb-basic-ejb.jar `-- META-INF `-- application.xml
Must supply a META-INF/application.xml with the EJB listed as an ejb module
May supply utility jars in library-directory (e.g., /lib)
$ more META-INF/application.xml
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/application_7.xsd" version="7">
<application-name>ejb-basic-ear</application-name>
<description>This projects provides an example EAR for deploying
one or more EJB and other type modules.
This EAR is assembled here, but tested as a part of the
function/integration testing in a sibling project.</description>
<display-name>ejb-basic-ear</display-name>
<module>
<ejb>ejb-basic-ejb.jar</ejb>
</module>
<library-directory>lib</library-directory>
</application>
EJB module deployed within EAR
19:50:07,583 INFO [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-1) JNDI bindings for session bean named GreeterEJB in deployment unit subdeployment "ejb-basic-ejb.jar" of deployment "ejb-basic-ear-4.0.0-SNAPSHOT.ear" are as follows: java:global/ejb-basic-ear/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:app/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:module/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:jboss/exported/ejb-basic-ear/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:global/ejb-basic-ear/ejb-basic-ejb/GreeterEJB java:app/ejb-basic-ejb/GreeterEJB java:module/GreeterEJB 19:50:07,588 INFO [org.jboss.weld.deployer] (MSC service thread 1-1) JBAS016005: Starting Services for CDI deployment: ejb-basic-ear-4.0.0-SNAPSHOT.ear 19:50:07,596 INFO [org.jboss.weld.deployer] (MSC service thread 1-2) JBAS016008: Starting weld service for deployment ejb-basic-ear-4.0.0-SNAPSHOT.ear
JBoss server prints out JNDI names to access EJB through...
@Remote
No interface
Globally within the server (java:global)
Within the deployed application (java:app)
Within the EJB module (java:module)
External to server (java:global/exported)
Derive remote client JNDI name from "exported" name
ejb-basic-ear/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote
Local and No-interface interfaces are not exposed to remote clients
packaging=ear
Configures plugin to perform EAR-like packaging of dependencies
ejb scope=compile dependencies
Name upstream Maven modules that should be deployed as EJBs
maven-ear-plugin
Can perform most actions be default/convention. Example shows how to clean up JNDI names so that version# is not part of the JNDI name exposed
<project>
<groupId>info.ejava.examples.ejb.basicejb</groupId>
<artifactId>ejb-basic-ear</artifactId>
<packaging>ear</packaging>
<dependencies>
<!-- The EAR must have a scope=compile dependency on the EJB -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>ejb-basic-ejb</artifactId>
<version>${project.version}</version>
<type>ejb</type>
</dependency>
</dependencies>
<build>
<plugins>
<!-- provide properties here to impact the EAR packaging -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ear-plugin</artifactId>
<configuration>
<defaultLibBundleDir>lib</defaultLibBundleDir>
<!-- eliminates use of version in EAR JNDI name portion -->
<applicationName>${project.artifactId}</applicationName>
<modules>
<!-- eliminates use of the version in the EJB JNDI name -->
<ejbModule>
<groupId>${project.groupId}</groupId>
<artifactId>ejb-basic-ejb</artifactId>
<bundleFileName>ejb-basic-ejb.jar</bundleFileName>
</ejbModule>
</modules>
</configuration>
</plugin>
</plugins>
</build>
Add optional undeploy of EAR to clean lifecycle
Must be done in pre-clean phase before .ear artifact removed
Latch in profile (-Pundeploy) to prevent from running when .ear not exists
<profiles>
<profile>
<id>undeploy</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<executions>
<execution>
<id>undeploy-ear</id>
<phase>pre-clean</phase>
<goals>
<goal>undeploy</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
Add EAR artifact as a scope=compile dependency when deploying from separate (test) module
<dependencies>
...
<!-- packages being deployed must be a dependency -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>ejb-basic-ear</artifactId>
<version>${project.version}</version>
<type>ear</type>
<scope>compile</scope>
</dependency>
...
</dependencies>
Declare EAR artifact to be deployed
<build>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<configuration>
<!-- artifacts to deploy to server -->
<deployables>
<deployable>
<groupId>${project.groupId}</groupId>
<artifactId>ejb-basic-ear</artifactId>
<type>ear</type>
</deployable>
</deployables>
</configuration>
</plugin>
</plugins>
</build>
Details of cargo-maven2-plugin are provided elsewhere
Deployed EJB using an EAR
Traditional JavaEE deployment
Permits deployment of dependency artifacts
Obtained JNDI name of EAR-based @Remote
(application-name)/(component-name)/(ejb-name)!(remote-interface-name)
ejb-basic-ear/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote
Useful in WAR deployments
`-- WEB-INF `-- WEB-INF |-- classes | `-- info | `-- ejava | `-- examples | `-- ejb | `-- basic | `-- webejb | `-- WebGreeterEJB.class |-- jboss-web.xml `-- lib `-- ejb-basic-ejb-4.0.0-SNAPSHOT.jar
WARs may host
imported EJBs - developed in a separate EJB module
embedded EJBs - developed within this module
(WebGreeterEJB is a copy/paste clone of GreeterEJB)
EJB module deployed within WAR
20:47:51,929 INFO [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-2) JNDI bindings for session bean named GreeterEJB in deployment unit deployment "ejb-basic-war.war" are as follows: java:global/ejb-basic-war/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:app/ejb-basic-war/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:module/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:jboss/exported/ejb-basic-war/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:global/ejb-basic-war/GreeterEJB java:app/ejb-basic-war/GreeterEJB java:module/GreeterEJB 20:47:51,970 INFO [org.jboss.weld.deployer] (MSC service thread 1-2) JBAS016005: Starting Services for CDI deployment: ejb-basic-war.war 20:47:51,988 INFO [org.jboss.weld.deployer] (MSC service thread 1-1) JBAS016008: Starting weld service for deployment ejb-basic-war.war 20:47:52,353 INFO [org.wildfly.extension.undertow] (MSC service thread 1-1) JBAS017534: Registered web context: /ejb-basic-war
JBoss server prints out JNDI names to access EJB through...
@Remote
No interface
Globally within the server (java:global)
Within the deployed application (java:app)
Within the EJB module (java:module)
External to server (java:global/exported)
Derive remote client JNDI name from "exported" name
ejb-basic-war/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote
Local and No-interface interfaces are not exposed to remote clients
Embedded EJBs have same naming structure
java:global/ejb-basic-war/WebGreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:app/ejb-basic-war/WebGreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:module/WebGreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:jboss/exported/ejb-basic-war/WebGreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:global/ejb-basic-war/WebGreeterEJB java:app/ejb-basic-war/WebGreeterEJB java:module/WebGreeterEJB
packaging=war
Configures plugin to perform WAR-like packaging of dependencies
scope=compile dependencies
Name upstream Maven modules that should be deployed in WEB-INF/classes
build.finalName
Helps eliminate version# from being in JNDI name
<project>
<groupId>info.ejava.examples.ejb.basicejb</groupId>
<artifactId>ejb-basic-war</artifactId>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>ejb-basic-ejb</artifactId>
<version>${project.version}</version>
<type>ejb</type>
</dependency>
</dependencies>
<build>
<finalName>ejb-basic-war</finalName>
</build>
</project>
Support for embedded EJB development will require standard POJO and EJB dependencies
<!-- for embedded code/EJBs -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.ejb</groupId>
<artifactId>javax.ejb-api</artifactId>
<scope>provided</scope>
</dependency>
Add EAR artifact as a scope=compile dependency when deploying from separate (test) module
<dependencies>
...
<!-- packages being deployed must be a dependency -->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>ejb-basic-war</artifactId>
<version>${project.version}</version>
<type>war</type>
<scope>compile</scope>
</dependency>
...
</dependencies>
Declare EAR artifact to be deployed
<build>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<configuration>
<!-- artifacts to deploy to server -->
<deployables>
<deployable>
<groupId>${project.groupId}</groupId>
<artifactId>ejb-basic-war</artifactId>
<type>war</type>
</deployable>
</deployables>
</configuration>
</plugin>
</plugins>
</build>
Details of cargo-maven2-plugin are provided elsewhere
Unit tests test POJO classes in a single Maven phase
IT tests test components deployed to the server using multiple phases Maven Lifecycle Reference
pre-integration-test
Deploy artifacts
integration-test
Execute tests
post-integration-test
Undeploy artifacts
verify
Evaluate test results (possibly fail build after artifacts undeployed)
IT tests require remote access
Access Type
JBoss Remoting
EJB Client
JNDI Properties
JNDI Name
IT tests test EJB deployed to server
Java client may access @Remote interface of EJB by first going through Java Naming and Directory Interface (JNDI)
JBoss uses two different types of remote interface techniques -- both accessed thru JNDI; JBoss Remoting and EJB Client
Obtain JNDI properties through combination of jndi.properties file and supplied Properties object.
java.util.Properties jndiProperties=...; // varies whether using Remoting or EJBClient
|-- jndi.properties java.naming.factory.initial=... java.naming.factory.url.pkgs=... java.naming.provider.url=...
Get InitialContext to server JNDI tree
Context jndi = new InitialContext(jndiProperties); //jndi.properties augmented
Context jndi = new InitialContext(); //jndi.properties is complete
Obtain JNDI-name using deployment-specific name and access technique
String jndiName; // varies whether accessing EJB, WAR or EAR deployment and access
Lookup EJB's remote interface
GreeterRemote greeter = (GreeterRemote) jndi.lookup(jndiName);
Legacy approach
Follows published standards
Consistent with other resource types (e.g., JMS resource lookups)
Knows nothing of EJB
Stated to be less efficient than EJB Client
java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory java.naming.factory.url.pkgs= java.naming.provider.url=http-remoting://localhost:8080 jboss.naming.client.ejb.context=true
Custom API for accessing EJBs
Can perform certain actions more efficiently with that knowledge and specialization
Uses separate naming context (ejb:)
Uses different and separate properties
Specifics can be shielded from client with minimal effort
java.naming.factory.initial= java.naming.factory.url.pkgs=org.jboss.ejb.client.naming java.naming.provider.url=
#top level property listing the names of the connections remote.connections=default #here we define the properties for the server we have called "default" remote.connection.default.host=localhost remote.connection.default.port=8080 remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false
ejb:/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote
ejb:/ejb-basic-war/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote
ejb:ejb-basic-ear/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote
The EJB Client implementation understands JBoss/Wildfly EJBs and to further help optimize the communication with Stateful Session EJBs the following string ("?stateful")must be appended to the JNDI name
InitialContextFactory looks for custom naming extensions when encountering naming prefix ("ejb:")
java.naming.factory.url.pkgs lists base package names to start looking for extensions
jboss-ejb-client.jar must be in the classpath
$ mvn dependency:tree [INFO] +- info.ejava.examples.common:jboss-rmi-client:pom:4.0.0-SNAPSHOT:test [INFO] | +- org.jboss.spec.javax.transaction:jboss-transaction-api_1.2_spec:jar:1.0.0.Final:test [INFO] | +- org.jboss.spec.javax.ejb:jboss-ejb-api_3.2_spec:jar:1.0.0.Final:test [INFO] | +- org.jboss:jboss-ejb-client:jar:2.0.1.Final:test
Provider looks for "ejb" extension below "org.jboss.ejb.client.naming" package
#jndi.properties java.naming.factory.url.pkgs=org.jboss.ejb.client.naming
$ jar tf ~/.m2/repository/org/jboss/jboss-ejb-client/2.0.1.Final/jboss-ejb-client-2.0.1.Final.jar | grep org.jboss.ejb.client.naming ... org/jboss/ejb/client/naming/ejb/SecurityActions$1.class org/jboss/ejb/client/naming/ejb/EjbNamingContext$2.class org/jboss/ejb/client/naming/ejb/EjbNamingContext.class org/jboss/ejb/client/naming/ejb/EjbNamingContextSetup.class org/jboss/ejb/client/naming/ejb/ejbURLContextFactory.class org/jboss/ejb/client/naming/ejb/SecurityActions.class org/jboss/ejb/client/naming/ejb/EjbJndiIdentifier.class org/jboss/ejb/client/naming/ejb/EjbJndiNameParser.class org/jboss/ejb/client/naming/ejb/SecurityActions$2.class org/jboss/ejb/client/naming/ejb/SecurityActions$3.class org/jboss/ejb/client/naming/ejb/EjbNamingContext$1.class
EJB Client only works for EJBs
InitialContext may need to locate other non-EJB resources (e.g., JMS)
Replace default InitialContextFactory with JBoss Remoting
Extend JBoss Remoting InitialContextFactory with EJB Client
java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory java.naming.factory.url.pkgs=org.jboss.ejb.client.naming java.naming.provider.url=http-remoting://localhost:8080 jboss.naming.client.ejb.context=true
IT tests require...
Server running (possibly started)
EJB deployed
Tests run
EJB undeployed (proper cleanup)
Server stopped (if started as part of the test)
Results evaluated
IT test can also be written with JUnit
@BeforeClass runs at start of test case and before all test methods. This must be public and static
@Before runs before each test method. This must be public and non-static
@After runs after each test method. This must be public and non-static
@AfterClass runs after all test methods are complete and prior to test case shutdown. This must be public and static
...
import static org.junit.Assert.*;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import info.ejava.examples.ejb.basic.dto.Greeting;
import info.ejava.examples.ejb.basic.dto.Name;
import info.ejava.examples.ejb.basic.ejb.BadRequestException;
import info.ejava.examples.ejb.basic.ejb.GreeterRemote;
...
public class GreeterIT {
private static final Logger logger = LoggerFactory.getLogger(GreeterBase.class);
protected Properties jndiProperties; // varies whether using Remoting or EJBClient
protected String jndiName; // varies whether accessing EJB, WAR or EAR deployment and access
protected Context jndi;
protected GreeterRemote greeter;
@Before
public void setUp() throws Exception {
jndi = new InitialContext(jndiProperties);
greeter = (GreeterRemote) jndi.lookup(jndiName);
}
@After
public void tearDown() throws Exception {
if (jndi != null) {
jndi.close(); // produces errors with JBoss Remoting
}
}
@Before method sets up JNDI context and performs lookup for @Remote before each @Test
@After method closes out JNDI Context between @Tests
Same as before except they must use the EJB remote interface rather than instantiate bean
@Test
public void pojoGreeter() throws BadRequestException {
logger.info("*** pojoGreeter ***");
String name = "cat inhat";
String greeting = greeter.sayHello(name);
assertTrue("greeter did not say my name", greeting.contains(name));
}
@Test(expected = BadRequestException.class)
public void badName() throws BadRequestException {
logger.info("*** badName ***");
greeter.sayHello("");
}
@Test
public void dto() throws BadRequestException {
logger.info("*** dto ***");
Name name = new Name("thing", "one");
Greeting greeting = greeter.sayHello(name);
assertTrue("greeter did not say my name", greeting.getGreeting()
.contains(name.getFirstName()));
}
IT tests housed in separate modules (can be part of EJB and WAR modules)
jndi.properties part of IT test tree -- not the production code
. |-- pom.xml `-- src `-- test |-- java | `-- info | `-- ejava | `-- examples | `-- ejb | `-- basicejb | `-- ejbclient | |-- GreeterIT.java `-- resources |-- jboss-ejb-client.properties |-- jndi.properties `-- log4j.xml
surefire/Unit Tests run prior to deployment
failsafe/IT tests run after deployment
$ mvn clean verify ... [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ ejb-basic-test --- [INFO] Deleting .../ejb-basic-example/ejb-basic-test/target ... [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ ejb-basic-test --- [INFO] No sources to compile [INFO] [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ ejb-basic-test --- [INFO] Using 'UTF-8' encoding to copy filtered resources. [INFO] Copying 4 resources [INFO] [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ ejb-basic-test --- [INFO] Changes detected - recompiling the module! [INFO] Compiling 5 source files to .../ejb-basic-example/ejb-basic-test/target/test-classes [INFO] [INFO] --- maven-surefire-plugin:2.17:test (default-test) @ ejb-basic-test --- [INFO] [INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ ejb-basic-test --- [WARNING] JAR will be empty - no content was marked for inclusion! [INFO] Building jar: .../ejb-basic-example/ejb-basic-test/target/ejb-basic-test-4.0.0-SNAPSHOT.jar ... [INFO] --- cargo-maven2-plugin:1.4.3:redeploy (cargo-prep) @ ejb-basic-test --- Sep 28, 2014 11:58:23 PM org.xnio.Xnio <clinit> INFO: XNIO version 3.2.2.Final Sep 28, 2014 11:58:23 PM org.xnio.nio.NioXnio <clinit> INFO: XNIO NIO Implementation Version 3.2.2.Final Sep 28, 2014 11:58:23 PM org.jboss.remoting3.EndpointImpl <clinit> INFO: JBoss Remoting version 4.0.3.Final ... [INFO] --- maven-failsafe-plugin:2.17:integration-test (integration-tests) @ ejb-basic-test --- [INFO] Failsafe report directory: .../ejb-basic-example/ejb-basic-test/target/failsafe-reports ------------------------------------------------------- T E S T S ------------------------------------------------------- Running info.ejava.examples.ejb.basicejb.ejbclient.GreeterRemotingWARIT 23:58:26,441 INFO (GreeterIT.java:43) -using jndiName=ejb-basic-war/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote ... 23:58:27,811 INFO (GreeterIT.java:43) -using jndiName=ejb:ejb-basic-ear/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote ... 23:58:28,167 INFO (GreeterIT.java:43) -using jndiName=ejb-basic-ear/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote ... 23:58:28,289 INFO (GreeterIT.java:43) -using jndiName=ejb:/ejb-basic-war/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote Results : Tests run: 12, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] --- cargo-maven2-plugin:1.4.3:undeploy (cargo-post) @ ejb-basic-test --- [INFO] [INFO] --- maven-failsafe-plugin:2.17:verify (verify) @ ejb-basic-test --- [INFO] Failsafe report directory: .../ejb-basic-example/ejb-basic-test/target/failsafe-reports [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 11.036 s
Copyright © 2015 jim stafford (jim.stafford@jhu.edu)
Built on: 2015-11-18 02:41 EST
Abstract
This presentation provides information for performing use cases that are specific to developing server-side EJBs. Topics range from managing server execution, executing the build, and remote debugging.
Manage server execution
Manage EJB deployment
Running IT tests with server deployments
Perform remote debugging
At the completion of this topic, the student shall
be able to:
Choose whether to run application server standalone or embedded
Start/Stop application server
Select specific Maven commands to execute for the development case
Run an IT test within IDE with EJB deployed to application server
Remote debug EJB deployed to application server
Two primary ways to run the application server
Standalone. Can be local or remote
Embedded within IDE. Shares same JVM.
Deployed applications log to an API (e.g., commons-logging, slf4j)
API bound to server logger mechanism (e.g., log4j)
Figure 91.1. Adjust Verbosity for Application
# standalone/configuration/standalone.xml
<logger category="info.ejava">
<level name="DEBUG"/>
</logger>
<root-logger>
<level name="INFO"/>
<handlers>
<handler name="CONSOLE"/>
<handler name="FILE"/>
</handlers>
</root-logger>
root-logger sets default versbosity to INFO
additional logger sets verbosity for info.ejava.* to DEBUG
Figure 91.2. Application Code
package info.ejava.examples.ejb.basic.ejb;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Stateless
@Remote(GreeterRemote.class)
public class GreeterEJB implements Greeter {
private static final Logger logger = LoggerFactory.getLogger(GreeterEJB.class);
@PostConstruct
public void init() {
logger.info("*** GreeterEJB ***");
}
@Override
public String sayHello(String name) throws BadRequestException {
logger.debug("sayHello({})", name);
...
}
EJB class used to create logger named after the class' fully qualified name
@PostConstruct logging at INFO level
business method logging at DEBUG level
Figure 91.3. Server Console Output
./bin/standalone.sh ... 01:17:01,990 INFO [info.ejava.examples.ejb.basic.ejb.GreeterEJB] (EJB default - 1) *** GreeterEJB ***
Console configured (by default) to only output INFO and above
Figure 91.4. Server log/server.log Output
$ tail -n 999 -f standalone/log/server.log 2014-10-01 01:17:01,990 INFO [info.ejava.examples.ejb.basic.ejb.GreeterEJB] (EJB default - 1) *** GreeterEJB *** 2014-10-01 01:17:02,009 DEBUG [info.ejava.examples.ejb.basic.ejb.GreeterEJB] (EJB default - 1) sayHello(cat inhat)
server.log will print all verbosity levels
Runs as separate process
Started/stopped by shell commands
Decoupled from IDE
Use start script (standalone.sh or standalone.bat)
Figure 91.5. Start Standalone Server at Shell
wildfly-8.1.0.Final]$ ./bin/standalone.sh ========================================================================= JBoss Bootstrap Environment JBOSS_HOME: /opt/wildfly-8.1.0.Final JAVA: /usr/lib/jvm/java-1.7.0/bin/java JAVA_OPTS: -server -Xms64m -Xmx512m -XX:MaxPermSize=256m -Djava.net.preferIPv4Stack=true -Djboss.modules.system.pkgs=org.jboss.byteman -Djava.awt.headless=true -agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=n ========================================================================= Listening for transport dt_socket at address: 8787 01:33:57,008 INFO [org.jboss.modules] (main) JBoss Modules version 1.3.3.Final 01:33:57,508 INFO [org.jboss.msc] (main) JBoss MSC version 1.2.2.Final 01:33:57,970 INFO [org.jboss.as] (MSC service thread 1-1) JBAS015899: WildFly 8.1.0.Final "Kenny" starting ... 01:34:56,423 INFO [org.jboss.as] (Controller Boot Thread) JBAS015874: WildFly 8.1.0.Final "Kenny" started in 60065ms - Started 309 of 362 services (113 services are lazy, passive or on-demand)
Server can be started at the command line
Add -c profile-name.xml to run alternate profile in standalone/configuration
Server can be safely shutdown using the command line shell or Control-C
Figure 91.6. Shutdown Standalone Server using Command
$ /opt/wildfly-8.1.0.Final/bin/jboss-cli.sh --connect command=:shutdown {"outcome" => "success"}
All server actions can be scripted with command-line interface
Figure 91.7. Kill Standalone Server using Control-C
01:39:20,291 INFO [org.jboss.as.messaging] (MSC service thread 1-4) JBAS011601: Bound messaging object to jndi name java:jboss/DefaultJMSConnectionFactory ^C 01:39:32,666 INFO [org.wildfly.extension.undertow] (MSC service thread 1-4) JBAS017532: Host default-host stopping 01:39:32,666 INFO [org.jboss.as.messaging] (ServerService Thread Pool -- 57) JBAS011605: Unbound messaging object to jndi name java:jboss/exported/jms/RemoteConnectionFactory
Server can also be safely shutdown with Control-C in console window
Runs within IDE JVM
Controlled through UI clicks
Output appears in IDE panels
Provides easier access to server-side debugging and profiling
Install JBoss Tools (JBoss AS Tools) -- shown earlier
Locate Server tab in the JavaEE Profile
Defaults are usually good enough
Start (debug and profile) option associated with server
Console output appears in IDE console window
Many/most build commands can be automated through Maven or other scripts
Used in interactive development and automated/headless builds
Uses EJB, EAR and Test Module
Also shows WAR option to EAR
IT tests could be placed inside WAR module
Figure 92.1. Example Source Tree
. |-- ejb-basic-ear | `-- pom.xml |-- ejb-basic-ejb | |-- pom.xml | `-- src | |-- main | | `-- java | | `-- info | | `-- ejava | | `-- examples | | `-- ejb | | `-- basic | | |-- dto | | | |-- Greeting.java | | | `-- Name.java | | `-- ejb | | |-- BadRequestException.java | | |-- GreeterEJB.java | | |-- Greeter.java | | `-- GreeterRemote.java | `-- test | |-- java | | `-- info | | `-- ejava | | `-- examples | | `-- ejb | | `-- basic | | `-- pojo | | `-- GreeterTest.java | `-- resources | `-- log4j.xml |-- ejb-basic-test | |-- pom.xml | `-- src | `-- test | |-- java | | `-- info | | `-- ejava | | `-- examples | | `-- ejb | | `-- basicejb | | `-- ejbclient | | `-- GreeterIT.java | `-- resources | |-- jndi.properties | `-- log4j.xml |-- ejb-basic-war | |-- pom.xml | `-- src | `-- main | `-- webapp | `-- WEB-INF | `-- jboss-web.xml `-- pom.xml
Tests functionality of code within the bounds of a single JVM
All external dependencies stubbed, simulated, or in-memory versions used (e.g., database, JMS server)
Copies resource files from src tree to target tree
Can optionally "filter" variables and replace them with build time values (from environment's settings.xml)
Useful in customizing environment-specific properties
server URLs
server port#s
Figure 92.2. Example Source Resource File: src/test/resources/jndi.properties
$ cat src/test/resources/jndi.properties ###jboss-remoting technique java.naming.factory.initial=${jboss.remoting.java.naming.factory.initial} java.naming.factory.url.pkgs=${jboss.remoting.java.naming.factory.url.pkgs} java.naming.provider.url=${jboss.remoting.java.naming.provider.url} java.naming.security.principal=${jboss.remoting.java.naming.security.principal} java.naming.security.credentials=${jboss.remoting.java.naming.security.credentials} jboss.naming.client.ejb.context=true
Source file uses variables to template source file
Figure 92.3. Example Filtered Resource File: target/test-classes/jndi.properties
$ cat target/test-classes/jndi.properties ###jboss-remoting technique java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory java.naming.factory.url.pkgs= java.naming.provider.url=http-remoting://localhost:8080 java.naming.security.principal=known java.naming.security.credentials=password1! jboss.naming.client.ejb.context=true
Concrete values are put in place for specific environment
Figure 92.4. Example Maven Configuration
<properties>
<jboss.host>localhost</jboss.host>
<jboss.http.port>8080</jboss.http.port>
<jndi.user>known</jndi.user>
<jndi.password>password1!</jndi.password>
<jboss.remoting.java.naming.factory.initial>org.jboss.naming.remote.client.InitialContextFactory</jboss.remoting.java.naming.factory.initial>
<jboss.remoting.java.naming.provider.url>http-remoting://${jboss.host}:${jboss.http.port}</jboss.remoting.java.naming.provider.url>
<jboss.remoting.java.naming.factory.url.pkgs/>
<jboss.remoting.java.naming.security.principal>${jndi.user}</jboss.remoting.java.naming.security.principal>
<jboss.remoting.java.naming.security.credentials>${jndi.password}</jboss.remoting.java.naming.security.credentials>
</properties>
<build>
<!-- filter test/resource files for profile-specific valies -->
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>true</filtering>
</testResource>
</testResources>
Properties defined either within pom.xml, settings.xml, or -Dsystem-properties
includes/exclused aids in filtering wrong files
avoid filtering binary files -- corrupts them (Ant has same issue)
Runs unit tests
Runs after tests compiled and before IT tests
Figure 92.5. Module Declaration
<properties>
<loop-count>3</loop-count>
</properties>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<loop.count>${loop-count}</loop.count>
</systemPropertyVariables>
</configuration>
</plugin>
Plugin always enabled by default. Declaration used only to customize
Configuration defines environment for JVM
Easy way to supply system properties (-Dsystem-property=value)
values can be hard-coded or use properties to allow easier overrides
Figure 92.6. Optional Includes/Excludes
<configuration>
<includes>
<include>**/GreeterTest.java</include>
</includes>
</configuration>
Useful when turning off all unit tests to concentrate on IT tests
Can turn off problem test(s)
Figure 92.7. Parent Definition
<maven-surefire-plugin.version>2.17</maven-surefire-plugin.version>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<argLine>${surefire.argLine}</argLine>
</configuration>
</plugin>
Just defining version here
Figure 92.8. Run Unit Tests
$ mvn clean test ... [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ ejb-basic-ejb --- ... [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ ejb-basic-ejb --- ... [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ ejb-basic-ejb --- ... [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ ejb-basic-ejb --- ... [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ ejb-basic-ejb --- ... [INFO] --- maven-surefire-plugin:2.17:test (default-test) @ ejb-basic-ejb --- ------------------------------------------------------- T E S T S ------------------------------------------------------- Running info.ejava.examples.ejb.basic.pojo.GreeterTest 02:11:38,695 INFO (GreeterTest.java:41) -*** dto *** 02:11:38,701 DEBUG (GreeterEJB.java:45) -sayHello(Name [firstName=thing, lastName=one]) 02:11:38,707 INFO (GreeterTest.java:27) -*** pojoGreeter *** 02:11:38,708 DEBUG (GreeterEJB.java:31) -sayHello(cat inhat) 02:11:38,711 INFO (GreeterTest.java:35) -*** badName *** 02:11:38,713 DEBUG (GreeterEJB.java:31) -sayHello() Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.238 sec - in info.ejava.examples.ejb.basic.pojo.GreeterTest Results : Tests run: 3, Failures: 0, Errors: 0, Skipped: 0 [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.871 s
Just definiing version here
Runs after artifacts packaged and deployed
Required when tests require preparation/shutdown external to JVM
Four (4) phases
pre-integration-test
integration-test
post-integration-test
verify
Can be in same module as deployable artifact (i.e., within EJB or WAR modules)
Manages deployment of components to containers
Not specific to JBoss/Wildfly
Specializations for Maven and JBoss/Wildfly
Can run embedded within each IT module build or use remote instance
Typically configured within module with IT tests
Figure 92.9. IT Module Declaration
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<configuration>
<!-- artifacts to deploy to server -->
<deployables>
<deployable>
<groupId>${project.groupId}</groupId>
<artifactId>ejb-basic-ear</artifactId>
<type>ear</type>
</deployable>
</deployables>
</configuration>
</plugin>
Plugin declaration enacts parent-defined behavior
Module specifies artifacts to deploy
Module specification is not required if deployment is self (i.e., the primary EJB/WAR/EAR produced by this module)
Figure 92.10. WAR/EAR Module Cleanup Declarations
<profile>
<id>undeploy</id>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<executions>
<execution>
<id>undeploy-ear</id>
<phase>pre-clean</phase>
<goals>
<goal>undeploy</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
Used to simply undeploy module from server
Requires artifact to be present
Profile keeps behavior from running under conditions that would fail build
Figure 92.11. Parent Definition
<pluginManagement>
<plugins>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>${cargo-maven2-plugin.version}</version>
<configuration>
<container>
<containerId>${cargo.containerId}</containerId>
<type>remote</type>
<log>target/server.log</log>
<output>target/output.log</output>
</container>
<configuration>
<type>runtime</type>
<properties>
<cargo.hostname>${jboss.mgmt.host}</cargo.hostname>
<cargo.jboss.management.port>${jboss.mgmt.port}</cargo.jboss.management.port>
</properties>
</configuration>
</configuration>
<dependencies>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-controller-client</artifactId>
<version>${wildfly.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>cargo-prep</id>
<phase>pre-integration-test</phase>
<goals>
<goal>redeploy</goal>
</goals>
</execution>
<execution>
<id>cargo-post</id>
<phase>post-integration-test</phase>
<goals>
<goal>undeploy</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
Parent defines boiler-plate portion
Child will provide module-specific information
Figure 92.12. Deploy Application
$ mvn pre-integration-test -DskipTests ... [INFO] [INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ ejb-basic-test --- ... [INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ ejb-basic-test --- ... [INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ ejb-basic-test --- ... [INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ ejb-basic-test --- ... [INFO] --- maven-surefire-plugin:2.17:test (default-test) @ ejb-basic-test --- ... [INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ ejb-basic-test --- ... [INFO] --- cargo-maven2-plugin:1.4.3:redeploy (cargo-prep) @ ejb-basic-test --- Oct 01, 2014 2:20:21 AM org.xnio.Xnio <clinit> INFO: XNIO version 3.2.2.Final Oct 01, 2014 2:20:21 AM org.xnio.nio.NioXnio <clinit> INFO: XNIO NIO Implementation Version 3.2.2.Final Oct 01, 2014 2:20:21 AM org.jboss.remoting3.EndpointImpl <clinit> INFO: JBoss Remoting version 4.0.3.Final [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS
Builds a new packaged, deployable artifact
Deploys and leaves on server
(optional)-DskipTests
bypasses any unit tests
Notice that build completes immediately after deploy with no IT tests or undeploy
Figure 92.13. Undeploy Application
$ mvn clean -Pundeploy ... [INFO] ------------------------------------------------------------------------ [INFO] Building EJB::Basic EJB::EAR 4.0.0-SNAPSHOT [INFO] ------------------------------------------------------------------------ [INFO] [INFO] --- cargo-maven2-plugin:1.4.3:undeploy (undeploy-ear) @ ejb-basic-ear --- Oct 01, 2014 2:23:50 AM org.xnio.Xnio <clinit> INFO: XNIO version 3.2.2.Final Oct 01, 2014 2:23:50 AM org.xnio.nio.NioXnio <clinit> INFO: XNIO NIO Implementation Version 3.2.2.Final Oct 01, 2014 2:23:50 AM org.jboss.remoting3.EndpointImpl <clinit> INFO: JBoss Remoting version 4.0.3.Final [INFO] [INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ ejb-basic-ear --- [INFO] Deleting /home/jcstaff/workspaces/ejava-class/ejava-student/ejb/ejb-intro/ejb-basic-example/ejb-basic-ear/target
Undeploys atifact from server
Useful in automating cleanup
-Pundeploy
activates latched profile
Runs integration (IT) tests
Must be declared, not configured in by default
Runs after deployer completes and prior to undeployer
Figure 92.14. IT Module Declaration
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<cargo.startstop>${cargo-startstop}</cargo.startstop>
</systemPropertyVariables>
</configuration>
</plugin>
Plugin definition causes IT tests to run
Configuration defines environment for JVM
Figure 92.15. Optional Includes/Excludes
<configuration>
<includes>
<include>**/GreeterIT.java</include>
</includes>
</configuration>
Can turn off problem or lengthy test(s)
Figure 92.16. Parent Definitition
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${maven-failsafe-plugin.version}</version>
<configuration>
<argLine>${surefire.argLine}</argLine>
</configuration>
<executions>
<execution> <!-- run the tests here -->
<id>integration-tests</id>
<phase>integration-test</phase>
<goals>
<goal>integration-test</goal>
</goals>
</execution>
<execution> <!-- delay failures to after undeploy -->
<id>verify</id>
<phase>verify</phase>
<goals>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</pluginManagement>
Defines version# and wires plugin into build phases when declared by child module
Figure 92.17. Run IT Tests
$ mvn clean verify
$ mvn clean verify
...
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ ejb-basic-test ---
...
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ ejb-basic-test ---
...
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ ejb-basic-test ---
...
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ ejb-basic-test ---
...
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ ejb-basic-test ---
...
[INFO] --- maven-surefire-plugin:2.17:test (default-test) @ ejb-basic-test ---
...
[INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ ejb-basic-test ---
...
[INFO] --- cargo-maven2-plugin:1.4.3:redeploy (cargo-prep) @ ejb-basic-test ---
Oct 01, 2014 2:28:02 AM org.xnio.Xnio <clinit>
INFO: XNIO version 3.2.2.Final
Oct 01, 2014 2:28:02 AM org.xnio.nio.NioXnio <clinit>
INFO: XNIO NIO Implementation Version 3.2.2.Final
Oct 01, 2014 2:28:02 AM org.jboss.remoting3.EndpointImpl <clinit>
INFO: JBoss Remoting version 4.0.3.Final
...
[INFO] --- maven-failsafe-plugin:2.17:integration-test (integration-tests) @ ejb-basic-test ---
-------------------------------------------------------
T E S T S
-------------------------------------------------------
...
Results :
Tests run: 12, Failures: 0, Errors: 0, Skipped: 0
...
[INFO] --- cargo-maven2-plugin:1.4.3:undeploy (cargo-post) @ ejb-basic-test ---
...
[INFO] --- maven-failsafe-plugin:2.17:verify (verify) @ ejb-basic-test ---
...
-------------------------------------------------------
[INFO] BUILD SUCCESS
Runs all phases and stops just prior to installing into local repository
Cannot bypass unit tests (unless includes/excludes used)
Deploy artifacts to server
Run IT tests in IDE
Connect with Debugger
Figure 93.1. Deploy Artifacts
$mvn clean pre-integration-test ... 22:51:43,460 INFO [org.jboss.as.ejb3.deployment.processors.EjbJndiBindingsDeploymentUnitProcessor] (MSC service thread 1-2) JNDI bindings for session bean named GreeterEJB in deployment unit subdeployment "ejb-basic-ejb.jar" of deployment "ejb-basic-ear-4.0.0-SNAPSHOT.ear" are as follows: java:global/ejb-basic-ear/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:app/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:module/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:jboss/exported/ejb-basic-ear/ejb-basic-ejb/GreeterEJB!info.ejava.examples.ejb.basic.ejb.GreeterRemote java:global/ejb-basic-ear/ejb-basic-ejb/GreeterEJB java:app/ejb-basic-ejb/GreeterEJB java:module/GreeterEJB 22:51:43,563 INFO [org.jboss.weld.deployer] (MSC service thread 1-2) JBAS016005: Starting Services for CDI deployment: ejb-basic-ear-4.0.0-SNAPSHOT.ear
Deploy artifacts using Maven command from either module parent or IT test module
Build from parent will re-build, deploy, and leave deployed the artifacts
Can select a specific test or package of tests
Leverage reasonable hard-coded defaults and filtered resource files to make JUnit tests IDE friendly so that much of heavyweight Maven setup and teardown can be skipped when focusing on specific tests.
The following information is for remote debugging a standalone server. If you are using the IDE embedded mode, just re-start your server using the debug option and add source references as needed.
Figure 93.4. Enable Remote Debugging in Server
# wildfly-8.1.0.Final/bin/standalone.conf # Sample JPDA settings for remote socket debugging JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,address=8787,server=y,suspend=n"
Not required when using embedded server (just use debug-as)
Standalone server must be restarted after making this edit
Figure 93.6. Standalone Server Debugger Listen Port
========================================================================= Listening for transport dt_socket at address: 8787 22:51:31,542 INFO [org.jboss.modules] (main) JBoss Modules version 1.3.3.Final 22:51:32,276 INFO [org.jboss.msc] (main) JBoss MSC version 1.2.2.Final 22:51:32,526 INFO [org.jboss.as] (MSC service thread 1-2) JBAS015899: WildFly 8.1.0.Final "Kenny" starting
Debugger Client port must match what JBoss debugger listen port
You can optionally add modules now to resolve source code references
Execution will stop at server breakpoint
No sourcecode will show up if not yet in search path
Add as many projects as you wish
You may have to stop and re-run your test for the source code path to take effect.
Notice we are seeing variables as well as line of execution
Copyright © 2015 jim stafford (jim.stafford@jhu.edu)
Built on: 2015-11-18 02:48 EST
Abstract
This presentation provides information for configuring EJBs with resources using deployment descriptors and JNDI Enterprise Naming Context (ENC). Thru one or more of these mechanisms you can inject resources -- such as Persistence Contexts, JMS Resources, references to other EJBs, etc -- into your EJB.
Table of Contents
Java Environment Naming Context (ENC)
Reading JNDI and ENC
Populating ENC using XML Deployment Descriptor
Populating ENC using Java Annotations
EJB Resource Injection
Using XML Deployment Descriptor Injection
Using Annotation Injection
Naming - provides a "white pages" lookup of an object based on name
Directory - provides a "yellow pages" lookup of an object based on properties
JavaEE providers required to supply Naming implementation
Naming consists of hierarchical Contexts (directories) and Objects
Reference to JNDI Naming tree through javax.naming.InitialContext
InitialContext jndi = null;
try {
jndi = new InitialContext();
...
Object object = jndi.lookup(jndiName);
} catch (NamingException ex) {
throw new EJBException(ex);
}
Lookups are passed a JNDI name
JNDI name have prefixes that can given them special meaning
(no prefix) - global namespace (available remotely)
java: - namespace available only within JVM
java:comp - an Enterprise Naming Context (ENC) relative to a specific component
ejb: - namespace for use by JBoss EJB Client
JavaEE resources are registered in the JNDI tree
Not all resources are available from remote clients
Resources in the global JNDI Naming Context are available to all clients
jms/RemoteConnectionFactory jms/queue/test jms/topic/test enc-config-example-ejb/XMLConfiguredEJB!ejava.ejb.examples.encconfig.ejb.JNDIReaderRemote
You can locate these global names using the java:/jboss/exported local context
java:jboss/exported/jms/RemoteConnectionFactory java:jboss/exported/jms/queue/test java:jboss/exported/jms/topic/test java:jboss/exported/enc-config-example-ejb/XMLConfiguredEJB!ejava.ejb.examples.encconfig.ejb.JNDIReaderRemote
Resources in the local JNDI Naming Context are available to components within the server
java:jboss/datasources/ExampleDS java:jboss/jaxr/ConnectionFactory java:jboss/mail/Default java:/ConnectionFactory java:/JmsXA java:/queue/test java:/topic/test
JavaEE 6/EJB3.1 added a portable JNDI naming standard for EJB that provides scoping for global, application, and module for the EJB's local and remote interfaces
java:global/enc-config-example-ejb/InjectedEJB!ejava.ejb.examples.encconfig.ejb.InjectedEJB java:app/enc-config-example-ejb/InjectedEJB!ejava.ejb.examples.encconfig.ejb.InjectedEJB java:module/InjectedEJB!ejava.ejb.examples.encconfig.ejb.InjectedEJB java:global/enc-config-example-ejb/InjectedEJB java:app/enc-config-example-ejb/InjectedEJB java:module/InjectedEJB
Component-specific namespace within JNDI tree
Holds references to values and objects in the environment
Theoretical equivalent to Unix soft links
ln -s <some physical file path> $HOME/<my logical path>
Accessible through "java:comp/env/(encname)" when using InitialContext
Context jndi = new InitialContext();
String jndiName = "java:comp/env/" + encname;
Object object = jndi.lookup(jndiName);
Accessible through "(encname)" when using injected SessionContext
private @Resource SessionContext ctx;
...
Object object = ctx.lookup(encname);
Populated into ENC using
Annotations
@Resource(lookup="java:jboss/datasources/ExampleDS", name="jdbc/ds2")
private DataSource ds2;
XML using META-INF/ejb-jar.xml
<resource-ref>
<res-ref-name>jdbc/ds2</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<lookup-name>java:jboss/datasources/ExampleDS</lookup-name>
</resource-ref>
XML using META-INF/jboss-ejb3.xml
<resource-ref>
<res-ref-name>jdbc/ds3</res-ref-name>
<jndi-name>java:jboss/datasources/ExampleDS</jndi-name>
</resource-ref>
Injected into EJB using
Annotations without ENC
@Resource(lookup="java:jboss/datasources/ExampleDS")
private DataSource ds1;
Annotations with populated ENC
@Resource(name="jdbc/ds3")
private DataSource ds3;
XML using META-INF/ejb-jar.xml
<resource-ref>
<res-ref-name>jdbc/ds1</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<injection-target>
<injection-target-class>ejava.ejb.examples.encconfig.ejb.XMLConfiguredEJB</injection-target-class>
<injection-target-name>ds1</injection-target-name>
</injection-target>
<lookup-name>java:jboss/datasources/ExampleDS</lookup-name>
</resource-ref>
Perform actions on injected resources after injection
complete using @PostConstruct
@PostConstruct
public void init() {
...
}
Populate ENC
Lookup resource in ENC
Inject ENC resource
Inject resource without ENC
JavaEE/EJB-standard population/injection using class annotations
@javax.ejb.EJB
@javax.persistence.PersistenceContext
@javax.persistence.PersistenceUnit
@javax.annotation.Resource - general purpose dependency
JavaEE/EJB-standard population/injection using META-INF/ejb-jar.xml
<?xml version="1.0"?>
<ejb-jar
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/ejb-jar_3_2.xsd"
version="3.2">
<enterprise-beans>
<session>
<ejb-name>XMLConfiguredEJB</ejb-name>
...
</session>
...
</enterprise-beans>
JBoss-specific population/injection using META-INF/jboss-ejb3.xml
<?xml version="1.0"?>
<jboss:ejb-jar xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:s="urn:security"
xmlns:c="urn:clustering:1.0"
xmlns:p="urn:ejb-pool:1.0"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-2_0.xsd
http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd"
version="3.1"
impl-version="2.0">
<enterprise-beans>
<session>
<ejb-name>XMLConfiguredEJB</ejb-name>
...
</session>
...
</enterprise-beans>
</jboss:ejb-jar>
Populate ENC with value resource using ejb-jar.xml#env-entry
<env-entry>
<env-entry-name>val/value2</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>from ejb-jar.xml</env-entry-value>
</env-entry>
Inject resource from ENC using @Resource
@Resource(name="val/value2")
private String value2="(class default value)";
Lookup ENC resource using JNDI or SessionContext
value2 = (String)jndi.lookup("java:comp/env/val/value2");
value2 = (String)ctx.lookup("val/value2");
Populate ENC with resource using Annotation. This also injects resource into the bean.
@Resource(lookup="java:jboss/datasources/ExampleDS", name="jdbc/ds2")
private DataSource ds2;
Populate ENC with ejb-jar.xml#resource-ref
<resource-ref>
<res-ref-name>jdbc/ds2</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<lookup-name>java:jboss/datasources/ExampleDS</lookup-name>
</resource-ref>
Populate ENC with jboss-ejb3.xml#resource-ref
<resource-ref>
<res-ref-name>jdbc/ds3</res-ref-name>
<jndi-name>java:jboss/datasources/ExampleDS</jndi-name>
</resource-ref>
Inject resource from ENC using annotations
@Resource(name="jdbc/ds3")
private DataSource ds3;
Lookup ENC resource using JNDI or SessionContext
ds2 = (DataSource)jndi.lookup("java:comp/env/jdbc/ds2");
ds2 = (DataSource)ctx.lookup("jdbc/ds2");
Inject resource without ENC using ejb-jar.xml#injection-target
<resource-ref>
<res-ref-name>jdbc/ds1</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<injection-target>
<injection-target-class>ejava.ejb.examples.encconfig.ejb.XMLConfiguredEJB</injection-target-class>
<injection-target-name>ds1</injection-target-name>
</injection-target>
<lookup-name>java:jboss/datasources/ExampleDS</lookup-name>
</resource-ref>
Inject resource without ENC using annotations
@Resource(lookup="java:jboss/datasources/ExampleDS")
private DataSource ds1;
Populate ENC with resource using Annotation. This also injects resource into the bean.
@PersistenceContext(unitName="enc-config", name="jpa/em2")
private EntityManager em2;
@PersistenceUnit(unitName="enc-config", name="jpa/emf2")
private EntityManagerFactory emf2;
Populate ENC with ejb-jar.xml#persistence-context-ref and persistence-unit-ref
<persistence-context-ref>
<persistence-context-ref-name>jpa/em2</persistence-context-ref-name>
<persistence-unit-name>enc-config</persistence-unit-name>
<injection-target>
<injection-target-class>ejava.ejb.examples.encconfig.ejb.XMLConfiguredEJB</injection-target-class>
<injection-target-name>em2</injection-target-name>
</injection-target>
</persistence-context-ref>
<persistence-unit-ref>
<persistence-unit-ref-name>jpa/emf2</persistence-unit-ref-name>
<persistence-unit-name>enc-config</persistence-unit-name>
<injection-target>
<injection-target-class>ejava.ejb.examples.encconfig.ejb.XMLConfiguredEJB</injection-target-class>
<injection-target-name>emf2</injection-target-name>
</injection-target>
</persistence-unit-ref>
Populate ENC with jboss-ejb3.xml#persistence-context-ref and persistence-unit-ref
<persistence-context-ref>
<persistence-context-ref-name>jpa/em3</persistence-context-ref-name>
<persistence-unit-name>enc-config</persistence-unit-name>
</persistence-context-ref>
<persistence-unit-ref>
<persistence-unit-ref-name>jpa/emf3</persistence-unit-ref-name>
<persistence-unit-name>enc-config</persistence-unit-name>
</persistence-unit-ref>
Inject resource from ENC using annotations
@PersistenceContext(name="jpa/em2")
private EntityManager em3;
@PersistenceUnit(name="jpa/emf3")
private EntityManagerFactory emf3;
Lookup ENC resource using JNDI or SessionContext
em2 = (EntityManager)jndi.lookup("java:comp/env/jpa/em2");
em2 = (EntityManager)ctx.lookup("jpa/em2");
emf2 = (EntityManagerFactory)jndi.lookup("java:comp/env/jpa/emf2");
emf2 = (EntityManagerFactory)ctx.lookup("jpa/emf2");
Inject resource without ENC using ejb-jar.xml#injection-target
<persistence-context-ref>
<persistence-context-ref-name>jpa/em1</persistence-context-ref-name>
<persistence-unit-name>enc-config</persistence-unit-name>
<injection-target>
<injection-target-class>ejava.ejb.examples.encconfig.ejb.XMLConfiguredEJB</injection-target-class>
<injection-target-name>em1</injection-target-name>
</injection-target>
</persistence-context-ref>
<persistence-unit-ref>
<persistence-unit-ref-name>jpa/emf1</persistence-unit-ref-name>
<persistence-unit-name>enc-config</persistence-unit-name>
<injection-target>
<injection-target-class>ejava.ejb.examples.encconfig.ejb.XMLConfiguredEJB</injection-target-class>
<injection-target-name>emf1</injection-target-name>
</injection-target>
</persistence-unit-ref>
Inject resource without ENC using annotations
@PersistenceContext(unitName="enc-config")
private EntityManager em1;
@PersistenceUnit(unitName="enc-config")
private EntityManagerFactory emf1;
Populate ENC with resource using Annotation. This also injects resource into the bean.
@Resource(lookup="java:/queue/test", name="jms/queue2")
private Queue queue2;
@Resource(lookup="java:/topic/test", name="jms/topic2")
private Topic topic2;
@Resource(lookup="java:/JmsXA", name="jms/cf2")
private ConnectionFactory cf2;
Populate ENC with ejb-jar.xml#resource-ref and resource-env-ref
<resource-ref>
<res-ref-name>jms/cf2</res-ref-name>
<res-type>javax.jms.ConnectionFactory</res-type>
<lookup-name>java:/JmsXA</lookup-name>
</resource-ref>
...
<resource-env-ref>
<resource-env-ref-name>jms/queue2</resource-env-ref-name>
<resource-env-ref-type>javax.jms.Destination</resource-env-ref-type>
<lookup-name>java:/queue/test</lookup-name>
</resource-env-ref>
<resource-env-ref>
<resource-env-ref-name>jms/topic2</resource-env-ref-name>
<resource-env-ref-type>javax.jms.Destination</resource-env-ref-type>
<lookup-name>java:/topic/test</lookup-name>
</resource-env-ref>
Populate ENC with jboss-ejb3.xml#resource-ref and resource-env-ref
<resource-ref>
<res-ref-name>jms/cf3</res-ref-name>
<jndi-name>java:/JmsXA</jndi-name>
</resource-ref>
<resource-env-ref>
<resource-env-ref-name>jms/queue3</resource-env-ref-name>
<jndi-name>java:/queue/test</jndi-name>
</resource-env-ref>
<resource-env-ref>
<resource-env-ref-name>jms/topic3</resource-env-ref-name>
<jndi-name>java:/topic/test</jndi-name>
</resource-env-ref>
Inject resource from ENC using annotations
@Resource(name="jms/queue3")
private Queue queue3;
@Resource(name="jms/topic3")
private Topic topic3;
@Resource(name="jms/cf3")
private ConnectionFactory cf3;
Lookup ENC resource using JNDI or SessionContext
queue2 = (Queue)jndi.lookup("java:comp/env/jms/queue2");
queue2 = (Queue)ctx.lookup("jms/queue2");
topic2 = (Topic)jndi.lookup("java:comp/env/jms/topic2");
topic2 = (Topic)ctx.lookup("jms/topic2");
cf2 = (ConnectionFactory)jndi.lookup("java:comp/env/jms/cf2");
cf2 = (ConnectionFactory)ctx.lookup("jms/cf2");
Inject resource without ENC using ejb-jar.xml#injection-target
<resource-ref>
<res-ref-name>jms/cf1</res-ref-name>
<res-type>javax.jms.ConnectionFactory</res-type>
<injection-target>
<injection-target-class>ejava.ejb.examples.encconfig.ejb.XMLConfiguredEJB</injection-target-class>
<injection-target-name>cf1</injection-target-name>
</injection-target>
<lookup-name>java:/JmsXA</lookup-name>
</resource-ref>
...
<resource-env-ref>
<resource-env-ref-name>jms/queue1</resource-env-ref-name>
<resource-env-ref-type>javax.jms.Queue</resource-env-ref-type>
<injection-target>
<injection-target-class>ejava.ejb.examples.encconfig.ejb.XMLConfiguredEJB</injection-target-class>
<injection-target-name>queue1</injection-target-name>
</injection-target>
<lookup-name>java:/queue/test</lookup-name>
</resource-env-ref>
<resource-env-ref>
<resource-env-ref-name>jms/topic1</resource-env-ref-name>
<resource-env-ref-type>javax.jms.Topic</resource-env-ref-type>
<injection-target>
<injection-target-class>ejava.ejb.examples.encconfig.ejb.XMLConfiguredEJB</injection-target-class>
<injection-target-name>topic1</injection-target-name>
</injection-target>
<lookup-name>java:/topic/test</lookup-name>
</resource-env-ref>
Inject resource without ENC using annotations
@Resource(name="jms/queue3")
private Queue queue3;
@Resource(name="jms/topic3")
private Topic topic3;
@Resource(name="jms/cf3")
private ConnectionFactory cf3;
Populate ENC with ejb-jar.xml#ejb-local-ref
<ejb-local-ref>
<ejb-ref-name>ejb/ejb2</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>ejava.ejb.examples.encconfig.ejb.InjectedEJB</local>
<ejb-link>InjectedEJB</ejb-link>
</ejb-local-ref>
Inject resource from ENC using annotations
@EJB(name="ejb/ejb2")
private InjectedEJB ejb2;
Lookup ENC resource using JNDI or SessionContext
ejb2 = (InjectedEJB)jndi.lookup("java:comp/env/ejb/ejb2");
ejb2 = (InjectedEJB)ctx.lookup("ejb/ejb2");
Inject resource without ENC using ejb-jar.xml#injection-target
<ejb-local-ref>
<ejb-ref-name>ejb/ejb1</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<local>ejava.ejb.examples.encconfig.ejb.InjectedEJB</local>
<ejb-link>InjectedEJB</ejb-link>
<injection-target>
<injection-target-class>ejava.ejb.examples.encconfig.ejb.XMLConfiguredEJB</injection-target-class>
<injection-target-name>ejb1</injection-target-name>
</injection-target>
</ejb-local-ref>
Inject resource without ENC using annotations
@EJB
private InjectedEJB ejb1;
Copyright © 2015 jim stafford (jim.stafford@jhu.edu)
Built on: 2015-11-18 02:55 EST
Abstract
This presentation provides coverage of how to package a JPA persistence unit within an EJB or WAR deployed to the server. It covers the resources involved and how they can be injected into the beans of the data tier as well as various issues that can arise and suggested solutions.
Table of Contents
Server-side Resources
PersistenceUnit/PersistenceContext injection
Managed Entities and Remote Interfaces
Persistence Context Propagation
Physical connections to database are placed into a pool
Logical connections are borrowed and returned from the pool
Figure 96.1. Server SQL DataSource (standalone.xml)
<subsystem xmlns="urn:jboss:domain:datasources:1.0">
<datasources>
<datasource jndi-name="java:jboss/datasources/ExampleDS" pool-name="ExampleDS" enabled="true" use-java-context="true">
<connection-url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</connection-url>
<driver>h2</driver>
<security>
<user-name>sa</user-name>
<password>sa</password>
</security>
</datasource>
<drivers>
<driver name="h2" module="com.h2database.h2">
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
</driver>
</drivers>
</datasources>
</subsystem>
No connection information - use jta-data-source
Properties included define how to work with DB -- not how to connect
Figure 96.2. Server-side 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_2_0.xsd" version="2.0">
<persistence-unit name="ejbjpa-hotel">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
...
<properties>
<property name="hibernate.dialect" value="${hibernate.dialect}"/>
...
</properties>
</persistence-unit>
</persistence>
Figure 96.3. EJB persistence.xml Placement
enc-config-example-ejb |-- ejava | `-- ejb | `-- examples | `-- encconfig | |-- dto | | `-- NCPair.class | `-- ejb | |-- AnnotatedEJB.class | |-- InjectedEJB.class | |-- JNDIReader.class | |-- JNDIReaderRemote.class | `-- XMLConfiguredEJB.class `-- META-INF `-- persistence.xml
META-INF/persistence.xml
Same location as normal JAR
Figure 96.4. WAR persistence.xml Placement
ejb-jpa-example-war |-- META-INF `-- WEB-INF |-- classes | |-- info | | `-- ejava | | `-- examples | | `-- ejb | | `-- ejbjpa | | |-- dto | | | |-- FloorDTO.class | | | `-- RoomDTO.class | | `-- ejb | | |-- HotelInitEJB.class | | |-- HotelInitRemote.class | | |-- HotelMgmtEJB.class | | |-- HotelMgmtLocal.class | | |-- HotelMgmtRemote.class | | |-- ReservationEJB.class | | `-- ReservationRemote.class | `-- META-INF | |-- ejb-jar.xml | |-- jboss-ejb3.xml | `-- persistence.xml `-- lib `-- ejb-jpa-example-blimpl-4.0.0-SNAPSHOT.jar
WEB-INF/classes/META-INF/persistence.xml
Inside JARs within WEB-INF/lib also allowed
to have persistence.xml reference @Entity class(es) outside of local archive
Figure 96.5. Reference External @Entities: EAR Deploy
ejbsessionBankEAR-4.0.0-SNAPSHOT |-- ejbsessionBankEJB.jar |-- ejbsessionBankWAR-4.0.0-SNAPSHOT.war |-- lib | |-- ejava-util-4.0.0-SNAPSHOT.jar | `-- ejbsessionBankImpl-4.0.0-SNAPSHOT.jar `-- META-INF `-- application.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_2_0.xsd" version="2.0">
<persistence-unit name="ejbsessionbank">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<jar-file>lib/ejbsessionBankImpl-4.0.0-SNAPSHOT.jar</jar-file>
<properties>
<property name="hibernate.dialect"
value="org.hibernate.dialect.H2Dialect"/>
...
</properties>
</persistence-unit>
</persistence>
jar-file element able to reference external JAR using a deterministic, portable path in EAR
jar-file element not usable in WARs
Figure 96.6. Reference External @Entities: WAR Deploy
<?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="ejbjpa-hotel">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<class>info.ejava.examples.ejb.ejbjpa.bo.Guest</class>
<class>info.ejava.examples.ejb.ejbjpa.bo.Room</class>
<class>info.ejava.examples.ejb.ejbjpa.bo.Floor</class>
<properties>
<property name="hibernate.dialect"
value="org.hibernate.dialect.H2Dialect"/>
...
</properties>
</persistence-unit>
</persistence>
There is no need to declare "class" entity references into a properly configured "jar-file" that is located in the classpath of a deployed EAR. Use "class" entity references when using a WEB-INF/classes/META-INF deployment of a WAR -- or if you really need to only include specific entities in the archive.
@PersistenceContext
Injected with EntityManager
Transaction Scoped
persistence context only sees a single Tx
container injects EntityManager with Tx active
Extended Scope
persistence context may see multiple Tx
only relevant for Stateful EJBs
@PersistenceUnit
Injected with EntityManagerFactory
May be used to implement BEAN-managed transactions
Figure 97.1. @PersistenceContext Injection
@Stateless
public class HotelMgmtEJB implements HotelMgmtRemote, HotelMgmtLocal {
@PersistenceContext(unitName="ejbjpa-hotel")
private EntityManager em;
private HotelDAO dao;
private HotelMgmt hotelMgmt;
@PostConstruct
public void init() {
dao = new JPAHotelDAO();
((JPAHotelDAO)dao).setEntityManager(em);
hotelMgmt = new HotelMgmtImpl();
((HotelMgmtImpl)hotelMgmt).setHotelDao(dao);
}
@PersistenceContext.unitName is the name used within the persistence.xml
@PersistenceContext.name would be the ENC name normally defined in ejb-jar.xml
Figure 97.2. @PersistenceUnit Injection
@Singleton
@Startup
@TransactionManagement(TransactionManagementType.BEAN)
public class HotelInitEJB implements HotelInitRemote {
@PersistenceUnit(unitName="ejbjpa-hotel")
private EntityManagerFactory emf;
@Resource
private UserTransaction tx;
@Override
public void businessMethod() {
EntityManager em=emf.createEntityManager();
HotelDAO dao = new JPAHotelDAO();
((JPAHotelDAO)dao).setEntityManager(em);
tx.begin();
em.joinTransaction(); //tells the EM to join the JTA Tx
...
tx.commit();
em.close();
}
}
BEAN-managed transactions means JTA transaction controlled thru injected UserTransaction
Persistence unit (EntityManagerFactory) being injected from a JTA-managed source
i.e., the transaction must be managed at JTA level
Method programmatically controlling scope of JTA transaction
em.joinTransaction() called on EntityManager created outside scope of JTA transaction
Newer technique -- JavaEE's answer to Spring Configuration
Define qualifier annotation
package ejava.examples.jndidemo;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.*;
import static java.lang.annotation.ElementType.*;
import javax.inject.Qualifier;
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface JndiDemo {
}
Define producer of Persistence Context
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
public class SchedulerResources {
@PersistenceContext(name="jndidemo") //using ENC-name
@Produces
@JndiDemo
public EntityManager em;
Define injection point
@Stateless
public class TrainSchedulerEJB
extends SchedulerBase implements TrainSchedulerRemote {
@Inject @JndiDemo
private EntityManager em;
Occurs when managed @Entity classes used as DTOs
Provider places proxy classes on managed entities to watch for changes
Provider classes can get marshaled back to client
Client must have hibernate-core in classpath to avoid ClassNotFoundException
Figure 98.1. Remote Client De-serialization Error with Missing Provider Class
javax.ejb.EJBException: java.lang.ClassNotFoundException: org.hibernate.proxy.pojo.javassist.SerializableProxy
Figure 98.2. Hibernate dependency allowing @Entity classes to be DTOs
<!-- used if hibernate entities re-used as DTOs -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<scope>test</scope>
</dependency>
Figure 98.3. Example: @Entity Class Returned to Client
@Entity
public class Room implements Serializable {
@Id
@Column(name="ROOM_NUMBER")
private int number;
@ManyToOne(optional=false, fetch=FetchType.LAZY)
@JoinColumn(name="FLOOR_ID")
private Floor floor;
@OneToOne(optional=true, fetch=FetchType.LAZY)
@JoinColumn(name="OCCUPANT_ID")
private Guest occupant;
Room has mandatory reference to Floor and option reference to Guest
fetch=LAZY references most likely will be proxies implemented by JPA provider classes
Figure 98.4. Example: EJB @Remote Method Returns Query Result Directly to Client
@Override
public List<Room> getAvailableRooms(Integer level, int offset, int limit) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Room> qdef = cb.createQuery(Room.class);
Root<Room> r = qdef.from(Room.class);
...
em.createQuery(qdef)
.setFirstResult(offset);
.setMaxResults(limit);
.getResultList();
}
Room entities returned from query are passed to client
Figure 98.5. Example: Provider Proxy Classes Marshaled to Client
72 List<Room> rooms = hotelMgmt.getAvailableRooms(null, 0, 1);
75 Room room = rooms.get(0);
77 logger.info("what's floor is this??? {}", room.getFloor().getClass());
78 assertFalse("floor was not proxy", room.getFloor().getClass().equals(Floor.class));
80 Guest guest = new Guest("Cosmo Kramer");
81 guest = hotelMgmt.checkIn(guest, room);
82 logger.info("final guest: {}:{}", guest.getClass(), guest);
HotelMgmtEJBIT:77 - what's floor is this??? class info.ejava.examples.ejb.ejbjpa.bo.Floor_$$_jvst9a8_0 HotelMgmtEJBIT:82 - final guest: class info.ejava.examples.ejb.ejbjpa.bo.Guest:Guest [id=11, name=Cosmo Kramer]
Room returned with proxy class (Floor_$$_jvst9a8_0
)between Room and Floor
Requires client to have hibernate-core in classpath
Allows @Entity classes to be used as POJOs except never managed
Contains no provider proxy classes
Figure 98.6. Example: EJB Copies Managed @Entity to new Instance
@Override
public List<Room> getCleanAvailableRooms(Integer level, int offset, int limit) {
List<Room> rooms = getAvailableRooms(level, offset, limit);
return toClean(rooms);
}
/**
* This helper method will instantiate new entity classes to re-use as DTOs.
* This is done to remove hibernate-proxy classes that are part of the managed
* entity.
*/
private List<Room> toClean(List<Room> rooms) {
if (rooms==null) { return null; }
List<Room> cleanRooms = new ArrayList<Room>(rooms.size());
for (Room room : rooms) {
Floor floor = room.getFloor();
Floor cleanFloor = new Floor(floor.getLevel());
Room cleanRoom = new Room(cleanFloor, room.getNumber());
cleanFloor.withRoom(cleanRoom);
Guest occupant = room.getOccupant();
if (occupant!=null) {
Guest cleanOccupant = new Guest(occupant.getId());
cleanOccupant.setName(occupant.getName());
cleanRoom.setOccupant(cleanOccupant);
}
cleanRooms.add(cleanRoom);
}
return cleanRooms;
}
EJB Remote facade creating new instances of @Entity classes
Figure 98.7. Example: Client Receives Pure POJOs without Provider Classes
this looks like a good floor: class info.ejava.examples.ejb.ejbjpa.bo.Floor final guest: class info.ejava.examples.ejb.ejbjpa.bo.Guest:Guest [id=17, name=Cosmo Kramer]
Client now gets Room.floor without provider proxy class in between
DB session closed before necessary references resolved
Common when using @Entities across transaction boundaries
Not unique to RMI
Figure 98.8. Floor @Entity
@Entity
public class Floor implements Serializable {
@Id
@Column(name="LEVEL", nullable=false)
int level;
@OneToMany(mappedBy="floor",
fetch=FetchType.LAZY,
cascade={CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH},
orphanRemoval=true)
@OrderBy("number")
List<Room> rooms;
Rooms fetch=LAZY
Rooms must be fetched within same DB session when using that collection
Figure 98.9. Standard @Entity Access
@Stateless
public class HotelMgmtEJB implements HotelMgmtRemote, HotelMgmtLocal {
...
@Override
public Floor getFloor(int level) {
return em.find(Floor.class, level);
}
Floor being marshaled directly back to client without addressing LAZY fetches
Figure 98.10. Remote Client Causing LAZY-load Exception
112 Floor floor = hotelMgmt.getFloor(0);
113 assertNotNull("floor not found", floor);
114 try {
115 logger.info("foor has {} rooms", floor.getRooms().size());
116 fail("did not get lazy-load exception");
117 } catch (LazyInitializationException expected) {
118 logger.info("got expected exception:{}", expected.toString());
119 }
HotelMgmtEJBIT:118 - got expected exception:org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: info.ejava.examples.ejb.ejbjpa.bo.Floor.rooms, could not initialize proxy - no Session
Floor can be accessed
Floor.room access causes Lazy-load exception of collection
Simple/brute force technique to stimulate resolution of references
Where do you stop???
Repetitive round trips to DB can be expensive
Figure 98.11. Server-side Stimulates References To Be Loaded
@Override
public Floor getTouchedFloor(int level) {
Floor floor = getFloor(level);
if (floor!=null) {
//touch the managed-floor to cause lazy-loads to be resolved
floor.getRooms().isEmpty();
for (Room room: floor.getRooms()) {
Guest guest = room.getOccupant();
if (guest!=null) {
guest.getName(); //touch all occupants to cause lazy-loads to be resolved
}
}
}
return floor;
}
Server-side code, within the DB session boundary stimulates references to be loaded prior to marshaling back to client
Figure 98.12. Initial getFloor() queries parent FLOOR table
Floor floor = getFloor(level);
select floor0_.LEVEL as LEVEL1_0_0_ from EJBJPA_FLOOR floor0_ where floor0_.LEVEL=?
Figure 98.13. Accessing Rooms Collection Causes Child Table Load
if (floor!=null) {
floor.getRooms().isEmpty();
select rooms0_.FLOOR_ID as FLOOR_ID2_0_0_, rooms0_.ROOM_NUMBER as ROOM_NUM1_2_0_, rooms0_.ROOM_NUMBER as ROOM_NUM1_2_1_, rooms0_.FLOOR_ID as FLOOR_ID2_2_1_, rooms0_.occupant_GUEST_ID as occupant3_2_1_ from EJBJPA_ROOM rooms0_ where rooms0_.FLOOR_ID=? order by rooms0_.ROOM_NUMBER
Figure 98.14. Additional Child Table Load Occurring One-at-a-Time
for (Room room: floor.getRooms()) { Guest guest = room.getOccupant(); if (guest!=null) { guest.getName(); //touch all occupants to cause lazy-loads to be resolved
select guest0_.GUEST_ID as GUEST_ID1_1_0_, guest0_.name as name2_1_0_ from EJBJPA_GUEST guest0_ where guest0_.GUEST_ID=? select guest0_.GUEST_ID as GUEST_ID1_1_0_, guest0_.name as name2_1_0_ from EJBJPA_GUEST guest0_ where guest0_.GUEST_ID=?
DAO queries crafted to support remote access patterns
LAZY fetches resolved through "join fetch" queries
Figure 98.15. DAO Query Fetches Child Tables
@Override
public Floor getFetchedFloor(int level) {
List<Floor> floors = em.createNamedQuery("Floor.fetchFloor",
Floor.class)
.setParameter("level", level)
.getResultList();
return floors.isEmpty() ? null : floors.get(0);
}
@Entity
@NamedQueries({
@NamedQuery(name="Floor.fetchFloor",
query="select f from Floor f "
+ "join fetch f.rooms r "
+ "join fetch r.occupant "
+ "where f.level=:level")
})
public class Floor implements Serializable {
Join fetch used to EAGER-ly load child rows
Less trips to DB for fatch=LAZY mappings
Figure 98.16. Returned Object Tree Accessed in fewer Queries
select floor0_.LEVEL as LEVEL1_0_0_, rooms1_.ROOM_NUMBER as ROOM_NUM1_2_1_, guest2_.GUEST_ID as GUEST_ID1_1_2_, rooms1_.FLOOR_ID as FLOOR_ID2_2_1_, rooms1_.occupant_GUEST_ID as occupant3_2_1_, rooms1_.FLOOR_ID as FLOOR_ID2_0_0__, rooms1_.ROOM_NUMBER as ROOM_NUM1_2_0__, guest2_.name as name2_1_2_ from EJBJPA_FLOOR floor0_ inner join EJBJPA_ROOM rooms1_ on floor0_.LEVEL=rooms1_.FLOOR_ID inner join EJBJPA_GUEST guest2_ on rooms1_.occupant_GUEST_ID=guest2_.GUEST_ID where floor0_.LEVEL=? order by rooms1_.ROOM_NUMBER
Still have to know "when is enough -- enough"
Server-side has job to implement overall capability
Client may have an "outsider" role
Figure 98.17. Room @Entity references Sensitive Occupant Information
@Entity
public class Room implements Serializable {
@Id
@Column(name="ROOM_NUMBER")
private int number;
...
@OneToOne(optional=true, fetch=FetchType.LAZY)
@JoinColumn(name="OCCUPANT_ID")
private Guest occupant;
More information than the client wants/needs
More information than what client should have
But that is how out server-side model is designed...
Figure 98.18. @Entity Model mis-used by Client
//lets see if we can manually find a vacant room.....
Floor floor = hotelMgmt.getFetchedFloor(0);
//all floors have at least one occupant
for (Room room: floor.getRooms()) {
Guest occupant = room.getOccupant();
if (occupant!=null) {
logger.info("hey {}, are you done with room {} yet?",
occupant.getName(), room.getNumber());
//that is just rude
}
}
hey guest 1, are you done with room 0 yet? hey guest 3, are you done with room 2 yet?
Client only needed to know if room was occupied -- not by who
Figure 98.19. DTO Represents what Clients Can/Should Know
public class RoomDTO implements Serializable {
private int number;
private boolean occupied;
Room DTO class is only expressing that room is occupied
Figure 98.20. Server-side constructs DTOs
@Override
public FloorDTO getFetchedFloorDTO(int level) {
Floor floor = getFetchedFloor(level);
return toDTO(floor);
}
private FloorDTO toDTO(Floor floor) {
if (floor==null) { return null; }
FloorDTO floorDTO = new FloorDTO(floor.getLevel());
if (floor.getRooms()!=null) { for (Room room: floor.getRooms()) {
floorDTO.withRoom(toDTO(room));
}}
return floorDTO;
}
private RoomDTO toDTO(Room room) {
if (room==null) { return null; }
RoomDTO roomDTO = new RoomDTO(room.getNumber());
//remote client shouldn't care who is in the room -- just if busy
roomDTO.setOccupied(room.getOccupant()!=null);
return roomDTO;
}
Similar to @Entity cleansing since DTO classes aren't managed
Can indirectly solve LAZY-load issue because @Entity is walked on server-side
Must still pay attention to DB access for performance reasons
Figure 98.21. Client receives more Appropriate Abstraction
//lets see if we can manually find a vacant room.....
FloorDTO floor = hotelMgmt.getFetchedFloorDTO(0);
//all floors have at least one occupant
for (RoomDTO room: floor.getRooms()) {
if (room.isOccupied()) {
logger.info("hey whoever, are you done with room {} yet?", room.getNumber());
//still rude, but a bit more private
}
}
hey whoever, are you done with room 0 yet? hey whoever, are you done with room 2 yet?
Client no longer has access to who is in each room -- but server-side does
EJB will instantiate persistence context if does not yet exist
Stateless EJBs may only have transaction-scope persistence contexts
Stateful EJBs may have transaction-scope or extended persistence contexts
EJBs can share persistence contexts
Stateless EJB can propagate its tx-scope persistence context to a called EJB
Stateful EJB can propagate its tx-scoped or extended persistence context to a called EJB
Stateless EJB can work with extended persistence context provided by upstream Stateful client
Stateful EJB cannot transform propagated tx-scope persistence context into an extended
EJB Facade can act as sharing point for common persistence context
Each interaction is independent of the next except for what is stored in DB and at client
Figure 99.2. Stateless EJB Example Check-in
@Override
public Guest checkIn(Guest guest, Room room) throws RoomUnavailableExcepton {
Room hotelRoom = dao.getRoom(room.getNumber());
dao.addGuest(guest);
hotelRoom.setOccupant(guest);
return guest;
}
(Error checking removed)
Locate specific room
Add guest to DB
Associate room with guest
Figure 99.3. Stateless EJB Example Client Check-in
List<Room> availableRooms = hotelMgmt.getAvailableRooms(null, 0, 0);
logger.debug("we have {} available rooms", availableRooms.size());
List<Guest> members = new ArrayList<Guest>(availableRooms.size());
int i=0;
for (Room room: availableRooms) {
Guest member = new Guest("member " + i++);
member = hotelMgmt.checkIn(member, room);
members.add(member);
}
Client performs actions one at a time
Figure 99.4. EJB Gets Available Rooms from DB
client: hotelMgmt.getAvailableRooms(null, 0, 0);
[HotelMgmtEJB] *** HotelMgmtEJB(1543684701):init ***
[stdout] Hibernate:
[stdout] select
[stdout] room0_.ROOM_NUMBER as ROOM_NUM1_2_,
[stdout] room0_.FLOOR_ID as FLOOR_ID2_2_,
[stdout] room0_.OCCUPANT_ID as OCCUPANT3_2_
[stdout] from
[stdout] EJBJPA_ROOM room0_
[stdout] where
[stdout] room0_.OCCUPANT_ID is null
[stdout] order by
[stdout] room0_.ROOM_NUMBER asc
[HotelMgmtEJB] *** HotelMgmtEJB(1543684701):destroy ***
Persistence context created
Available rooms loaded into persistence context
Result is returned
Persistence context destroyed
Figure 99.5. EJB Gets Specific Room
Room hotelRoom = dao.getRoom(room.getNumber());
[HotelMgmtEJB] *** HotelMgmtEJB(613346935):init ***
[HotelMgmtEJB] checkin(guest=Guest [id=0, name=member 0], room=Room [number=1, occupant=null])
[stdout] Hibernate:
[stdout] select
[stdout] room0_.ROOM_NUMBER as ROOM_NUM1_2_0_,
[stdout] room0_.FLOOR_ID as FLOOR_ID2_2_0_,
[stdout] room0_.OCCUPANT_ID as OCCUPANT3_2_0_
[stdout] from
[stdout] EJBJPA_ROOM room0_
[stdout] where
[stdout] room0_.ROOM_NUMBER=?
Persistence context created
Specific room loaded into persistence context
Guest inserted into DB
Figure 99.6. EJB Adds Guest
dao.addGuest(guest);
[stdout] Hibernate:
[stdout] call next value for hibernate_sequence
[HotelMgmtEJB] *** HotelMgmtEJB(613346935):destroy ***
[stdout] Hibernate:
[stdout] insert
[stdout] into
[stdout] EJBJPA_GUEST
[stdout] (name, GUEST_ID)
[stdout] values
[stdout] (?, ?)
Guest inserted into DB
The transaction does not end until existing the Stateless EJB method. That means any DB constraint violations will not occur within the context of the EJB call that caused it unless you call em.flush() (as a form of debug) prior to exiting the business method.
Figure 99.7. EJB associates Guest with Room
hotelRoom.setOccupant(guest);
[stdout] Hibernate:
[stdout] update
[stdout] EJBJPA_ROOM
[stdout] set
[stdout] FLOOR_ID=?,
[stdout] OCCUPANT_ID=?
[stdout] where
[stdout] ROOM_NUMBER=?
Room.occupant foreign key updated
Guest returned to client
Persistence context destroyed
State can be cached in-memory on server-side
Facade EJB propagates persistence context to called EJBs
Figure 99.8. Example Stateful Reservation EJB Caches Guest Requests for Client
@Stateful
public class ReservationEJB implements ReservationRemote {
@PersistenceContext(unitName="ejbjpa-hotel", type=PersistenceContextType.EXTENDED)
private EntityManager em;
List<Guest> guests = new LinkedList<Guest>();
@Override
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public int addGuest(Guest guest) {
logger.debug("addGuest={}", guest);
if (guest!=null) {
guests.add(guest);
em.persist(guest); //<== no transaction active yet
}
return guests.size();
}
Stateful EJB injects @PersistenceContext -- propagated in downstream EJB calls
Extended persistence context -- may be associated with zero or more transactions
Performing in-memory actions outside of transaction boundary
Figure 99.9. Example Stateful Reservation EJB Acting on Cached State
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Remove
public List<Guest> reserveRooms() throws RoomUnavailableExcepton {
List<Room> rooms = hotelMgmt.getAvailableRooms(null, 0, guests.size());
//assign each one of them a room
em.flush(); //<== flush guests persisted outside of a TX for demonstration
List<Guest> completed = new ArrayList<Guest>(guests.size());
Iterator<Room> roomItr = rooms.iterator();
for (Guest guest: guests) {
Room room = roomItr.next();
try {
guest = hotelMgmt.checkIn(guest, room); //<== will attempt to also persist guest
completed.add(guest);
} catch (RoomUnavailableExcepton ex) {
//rollback any previous reservations
ctx.setRollbackOnly();
throw ex;
}
}
return completed;
}
Method executed within active JTA transaction with persistence context associated with transaction
Guests already managed but will be fully persisted in this method
Calls to Stateless HotelMgmtEJB executed on this EJB's persistence context
@Remove indicates stateful instance to be discarded after method called
Figure 99.10. Stateful EJB Example Client Check-in
int availableRooms = hotelMgmt.getAvailableRooms(null, 0, 0).size();
logger.debug("we have {} available rooms", availableRooms);
ReservationRemote checkin = (ReservationRemote) jndi.lookup(reservationJNDI);
for (int i=0; i<availableRooms; i++) {
Guest member = new Guest("member " + i);
int count=checkin.addGuest(member);
logger.debug("we have {} in our group so far", count);
}
List<Guest> guests = checkin.reserveRooms();
Multiple requests are issued to Stateful EJB
Specific method(s) act on that state
Figure 99.11. Stateful EJB Persists Guests Prior to Active JTA Transaction
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
...
em.persist(guest); //<== no transaction active yet
[ReservationEJB] *** ReservationEJB(12230192):init ***
[ReservationEJB] addGuest=Guest [id=0, name=member 0]
[stdout] Hibernate:
[stdout] call next value for hibernate_sequence
[ReservationEJB] addGuest=Guest [id=0, name=member 1]
[stdout] Hibernate:
[stdout] call next value for hibernate_sequence
[ReservationEJB] addGuest=Guest [id=0, name=member 2]
[stdout] Hibernate:
[stdout] call next value for hibernate_sequence
[ReservationEJB] addGuest=Guest [id=0, name=member 3]
[stdout] Hibernate:
[stdout] call next value for hibernate_sequence
em.persist() outside of transaction causing causing sequence call but no table inserts
Figure 99.12. Stateless EJB Populates Propagated Persistence Context with Rooms
List<Room> rooms = hotelMgmt.getAvailableRooms(null, 0, guests.size());
[HotelMgmtEJB] *** HotelMgmtEJB(1162892707):init ***
[stdout] Hibernate:
[stdout] select
[stdout] room0_.ROOM_NUMBER as ROOM_NUM1_2_,
[stdout] room0_.FLOOR_ID as FLOOR_ID2_2_,
[stdout] room0_.OCCUPANT_ID as OCCUPANT3_2_
[stdout] from
[stdout] EJBJPA_ROOM room0_
[stdout] where
[stdout] room0_.OCCUPANT_ID is null
[stdout] order by
[stdout] room0_.ROOM_NUMBER asc limit ?
[HotelMgmtEJB] *** HotelMgmtEJB(1162892707):destroy ***
Downstream Stateless HotelMgmtEJB queried for rooms
Rooms loaded into persistence context
Figure 99.13. Stateful EJB Method Activates Transaction and flush()es Guests in EntityManager Cache
@TransactionAttribute(TransactionAttributeType.REQUIRED)
...
logger.debug("reserving {} rooms for {} guests", rooms.size(), guests.size());
em.flush(); //<== flush guests persisted outside of a TX for demonstration
[ReservationEJB] reserving 4 rooms for 4 guests
[stdout] Hibernate:
[stdout] insert
[stdout] into
[stdout] EJBJPA_GUEST
[stdout] (name, GUEST_ID)
[stdout] values
[stdout] (?, ?)
SQL inserts issued to DB during flush (requires transaction active)
Figure 99.14. Stateful EJB uses pre-loaded Rooms and Guests without accessing DB (until association)
guest = hotelMgmt.checkIn(guest, room); //<== will attempt to also persist guest
[HotelMgmtEJB] *** HotelMgmtEJB(1086999670):init ***
[HotelMgmtEJB] checkin(guest=Guest [id=65, name=member 0], room=Room [number=1, occupant=null])
[HotelMgmtEJB] *** HotelMgmtEJB(1086999670):destroy ***
[HotelMgmtEJB] *** HotelMgmtEJB(99312186):init ***
[HotelMgmtEJB] checkin(guest=Guest [id=66, name=member 1], room=Room [number=100, occupant=null])
[HotelMgmtEJB] *** HotelMgmtEJB(99312186):destroy ***
[HotelMgmtEJB] *** HotelMgmtEJB(545116383):init ***
[HotelMgmtEJB] checkin(guest=Guest [id=67, name=member 2], room=Room [number=102, occupant=null])
[HotelMgmtEJB] *** HotelMgmtEJB(545116383):destroy ***
[HotelMgmtEJB] *** HotelMgmtEJB(605810979):init ***
[HotelMgmtEJB] checkin(guest=Guest [id=68, name=member 3], room=Room [number=201, occupant=null])
[HotelMgmtEJB] *** HotelMgmtEJB(605810979):destroy ***
[stdout] Hibernate:
[stdout] update
[stdout] EJBJPA_ROOM
[stdout] set
[stdout] FLOOR_ID=?,
[stdout] OCCUPANT_ID=?
[stdout] where
[stdout] ROOM_NUMBER=?
[stdout] Hibernate:
...
[ReservationEJB] *** ReservationEJB(12230192):destroy ***
Stateless HotelMgmtEJB accesses Rooms from EntityManager first-level cache -- no additional DB access
Stateless HotelMgmtEJB updates Room.occupant FK to reference Guest -- who is also already managed and in first-level cache
JTA Transaction committed by container after Stateful EJB method exits
Stateful EJB and persistence context also destroyed after method exits
Part of transaction ACID properties
Atomic
All or nothing
Consistent
Valid state
Isolation
Visibility of incomplete changes
Durable
Not forgotten once committed
Figure 99.15. Stateless EJB will Reject New Check-in when Occupied
@Override
public Guest checkIn(Guest guest, Room room) throws RoomUnavailableExcepton {
Room hotelRoom = dao.getRoom(room.getNumber());
if (hotelRoom==null) {
throw new RoomUnavailableExcepton(String.format("room [%d] does not exist", room.getNumber()));
}
if (hotelRoom.getOccupant()!=null) {
throw new RoomUnavailableExcepton(String.format("room is occupied by %s", hotelRoom.getOccupant()));
}
dao.addGuest(guest);
hotelRoom.setOccupant(guest);
return guest;
}
Rejects checkin when invalid
Completes check-in when valid
Transaction per call, no rolling back previous calls
Figure 99.16. Client uses up all Available Rooms
List<Guest> members = new ArrayList<Guest>(availableRooms.size());
int i=0;
for (Room room: availableRooms) {
Guest member = new Guest("member " + i++);
member = hotelMgmt.checkIn(member, room);
members.add(member);
}
Stateless EJB commits each of these check-ins
Figure 99.17. Client Attempts an Invalid Check-in
//try doing it again
Room room = availableRooms.get(0);
Guest member = new Guest("member " + i++);
try {
member = hotelMgmt.checkIn(member, room);
members.add(member);
fail("fail to detect bad checkin");
} catch (RoomUnavailableExcepton ex) {
logger.debug("expected exception making too many reservations:{}", ex.toString());
}
expected exception making too many reservations:info.ejava.examples.ejb.ejbjpa.bl.RoomUnavailableExcepton: room is occupied by Guest [id=103, name=member 0]
Additional check-in rejected -- nothing committed
Figure 99.18. Server still has Initial Committed Check-ins
logger.info("completed reservations for {} guests", members.size());
int availableRooms2 = hotelMgmt.getAvailableRooms(null, 0, 0).size();
logger.info("hotel has {} rooms available", availableRooms2);
assertEquals("", availableRooms.size()-members.size(), availableRooms2);
completed reservations for 4 guests hotel has 0 rooms available
Each check-in occured in own transaction
Later error did not impact previous completed transactions
Multiple resource actions part of same transaction
All-or-none performed
Figure 99.19. Client Issues Guests Up Front
ReservationRemote checkin = (ReservationRemote) jndi.lookup(reservationJNDI);
for (int i=0; i<availableRooms+1; i++) {
Guest member = new Guest("member " + i);
int count=checkin.addGuest(member);
logger.debug("we have {} in our group so far", count);
}
00:47:20 DEBUG HotelMgmtEJBIT:326 - we have 4 available rooms 00:47:20 DEBUG HotelMgmtEJBIT:332 - we have 1 in our group so far 00:47:20 DEBUG HotelMgmtEJBIT:332 - we have 2 in our group so far 00:47:20 DEBUG HotelMgmtEJBIT:332 - we have 3 in our group so far 00:47:20 DEBUG HotelMgmtEJBIT:332 - we have 4 in our group so far 00:47:20 DEBUG HotelMgmtEJBIT:332 - we have 5 in our group so far
Same stateful process as before -- but with one additional Guest (one too many)
Figure 99.20. Client Attempts to Check-in all Guests at Once
try {
checkin.reserveRooms();
fail("too many check-ins not detected");
} catch (RoomUnavailableExcepton ex) {
logger.debug("expected exception making too many reservations:{}", ex.toString());
}
Client attempts to check-in to all Rooms
Checked exception will be thrown
Figure 99.21. Stateful EJB Attempts to flush() Guests and Reserve all Rooms
@TransactionAttribute(TransactionAttributeType.REQUIRED)
...
em.flush(); //<== flush guests persisted outside of a TX for demonstration
[ReservationEJB] reserving 4 rooms for 5 guests
[stdout] Hibernate:
[stdout] insert
[stdout] into
[stdout] EJBJPA_GUEST
[stdout] (name, GUEST_ID)
[stdout] values
[stdout] (?, ?)
...
Five (5) Guests are flushed to database prior to the rollback
Figure 99.22. Stateful EJB rolls back Transaction on Stateless HotelMgmtEJB Exception
@Resource
private SessionContext ctx;
} catch (RoomUnavailableExcepton ex) {
//rollback any previous reservations
ctx.setRollbackOnly();
throw ex;
}
[ReservationEJB] *** ReservationEJB(271512057):destroy ***
expected exception making too many reservations:info.ejava.examples.ejb.ejbjpa.bl.RoomUnavailableExcepton: found on 4 out of 5 required
Persisted Guests removed from database as a part of transaction rollback
Early check-ins never flushed to DB -- discarded as part of rollback
Figure 99.23. Client Checks and finds Rooms Still Available -- all Check-ins Rolled Back
int availableRooms2 = hotelMgmt.getAvailableRooms(null, 0, 0).size();
logger.info("hotel has {} rooms available", availableRooms2);
assertEquals("unexpected room count", availableRooms, availableRooms2);
hotel has 4 rooms available
All check-ins associated with Rooms removed as a part of rollback
One concurrency strategy
Create/wait-for a lock on data up-front, prior to getting the data
Figure 99.24. Stateful Scenario Ontaining Pessamistic Lock with Rooms
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Remove
public List<Guest> reserveRoomsPessimistic() throws RoomUnavailableExcepton {
List<Room> rooms = em.createQuery(...)
.setFirstResult(0)
.setMaxResults(guests.size())
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.setHint("javax.persistence.lock.timeout", 5000) //can also be set globally as persistence.xml property
.getResultList();
return reserveRooms(rooms);
}
[stdout] select [stdout] room0_.ROOM_NUMBER as ROOM_NUM1_2_, [stdout] room0_.FLOOR_ID as FLOOR_ID2_2_, [stdout] room0_.OCCUPANT_ID as OCCUPANT3_2_ [stdout] from [stdout] EJBJPA_ROOM room0_ [stdout] where [stdout] room0_.OCCUPANT_ID is null [stdout] order by [stdout] room0_.ROOM_NUMBER asc limit ? for update
setLockMode(LockModeType.PESSIMISTIC_WRITE) - locks row (or table) for remainder of transaction
select ... FOR UPDATE issued for query
Table of Contents
Understand the purpose of a transaction
Local Transactions
JTA Transactions
XA Transactions
Manage transaction scope
Manage transaction state
Monitor transactions
Unit of work that accesses one more more resources (usually databases)
set of one or more activities related to each other
must be completed together or not at all
ATM
Withdraw from one source
Deposit to another
Order System
Locate item
Charge account
Schedule shipment
Medical System
Identify medical state
dispense prescription
Atomic
All parts of the transaction must complete or nothing at all
Consistent
Resource (e.g., data in database) is in consistent state upon completion
State "makes sense"
Database constraints (primary keys, foreign keys, etc.) are satisfied
Isolated
Transaction executes without interference from outside the scope of the transaction
Can the transaction read state not yet committed by another transaction?
Can the transaction read state that did not exist prior to it starting?
Durable
Committed changes are not lost if system crashes after the commit completes
Rather than autocommit each command, a set of commands are executed within larger transaction
A failure of any of these commands or failure to commit -- will rollback all commands
Container will coordinate transaction with resource manager for database
Same as before except commands spread across multiple databases
Container will coordinate transaction with resource managers for all databases involved
Requires a distributed transaction -- transparent to commands executed
Even though JTA transactions allow for the forming of a distributed transaction across multiple resources -- that does not mean you should design for that goal. Local transactions (single resource) are much faster than distributed transactions (multiple resources) and should be the primary goal in transaction design.
Same as before except commands issued to different types of resources
Message taken off queue only if overall transaction succeeds
Changes occur to database only if overall transaction succeeds
Message added to topic only if overall transaction succeeds
Many possibilities...
Design JMS interfaces to handle duplicate messages if work has to be repeated
Design database interaction to detect repeated work
Form a local transaction with the JMS Server limited to receipt and send of message
Form a new/separate local transaction with database commands
Leverage the completion status of the database to commit the JMS transaction
Resource
The actual (e.g., database) target of a command with the transaction
Local Transaction
A transaction involving only a single resource
Distributed Transaction
A transaction involving two or more resources
Two-Phase Commit
A protocol used to implement a distributed transaction
Long-lived Transactions
Transaction-like construct that spans long lengths of time and usually uses compensating transactions for rollback
Compensating Transaction
A set of follow-up actions performed outside of the resource to return to a prior state when transaction rollback not available
Transaction Context Propagation
Sharing of the same transaction context across components
Deadlock
When two or more threads own locks within a resource that the other thread requires to complete their transaction
EJB requires support of the JTA APIs
Interface specification between participants in a transaction
applications
resource managers
application server
EJB is primarily a transaction framework/platform for application code
Two styles of transaction demarcation
Container-Managed Transactions (declarative)
Bean Managed Transactions (programmatic)
Container and server handle all interaction with resources -- regardless of demarcation style
EJB transactions are flat -- not nested
Declarative interface to transactions
Declarative attributes tell container to execute a method
within a transaction (MANDATORY, REQUIRED, REQUIRES_NEW, SUPPORTS)
without a transaction (SUPPORTS, UNSUPPORTED, NEVER)
Figure 100.4. Container-Managed Transactions
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class HotelMgmtEJB implements HotelMgmtRemote, HotelMgmtLocal {
...
@PersistenceContext(unitName="ejbjpa-hotel")
private EntityManager em;
private HotelDAO dao;
private HotelMgmt hotelMgmt;
@PostConstruct
public void init() {
dao = new JPAHotelDAO();
((JPAHotelDAO)dao).setEntityManager(em);
hotelMgmt = new HotelMgmtImpl();
((HotelMgmtImpl)hotelMgmt).setHotelDao(dao);
}
@Override
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public Room getRoom(int number) {
return hotelMgmt.getRoom(number);
}
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Guest checkIn(Guest guest, Room room) throws RoomUnavailableExcepton {
return hotelMgmt.checkIn(guest, room);
}
Programmatic interface to transactions
Interactions through javax.transaction.UserTransaction
interface
Everything between utx.begin() and utx.commit() is within a single transaction
Figure 100.5.
@Singleton
@Startup
@TransactionManagement(TransactionManagementType.BEAN)
public class HotelInitEJB implements HotelInitRemote {
@PersistenceUnit(unitName="ejbjpa-hotel")
private EntityManagerFactory emf;
@Resource
private UserTransaction tx;
@Override
public void clearAll() {
EntityManager em=emf.createEntityManager();
try {
JPAHotelDAO dao = new JPAHotelDAO();
dao.setEntityManager(em);
tx.begin();
em.joinTransaction(); //tells the EM to join the JTA UTx we are managing
dao.clearAll();
tx.commit();
} catch (Exception ex) {
try {
tx.rollback();
} catch (Exception ex2) {
throw new EJBException(ex2);
}
} finally {
if (em!=null) { em.close(); }
}
}
Declarative interface to transactions
Declarative attributes tell container to execute a method
within a transaction (MADATORY, REQUIRED, REQUIRES_NEW, SUPPORTS)
without a transaction (SUPPORTS, UNSUPPORTED, NEVER)
Container completes transaction protocol prior to marshaling return objects
Container must suspend any active transaction
Figure 101.1. Transaction Not Supported
@Override
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public Product createProductAndShipments_NotSupported(Product product, List<Shipment> shipments)
throws UnexpectedState {
When called with transaction active:
Caller transaction suspended during call
Caller transaction resumed after call
Caller transaction is not impacted by resource managers used during call
Caller transaction not passed to anything this method calls
Container must invoke method with transaction active
Figure 101.2. Transaction Required
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Product createProduct(Product product) {
When called with transaction active:
Caller transaction context passed to method (unless @Asynchronous)
Caller transaction context passed to anything this method calls
Resource manager(s) called by method are enlisted in caller's transaction context
When called without transaction active:
New transaction started
Transaction committed after method returns
Container can invoke method with transaction either active or inactive
Figure 101.3. Transaction Supports
@TransactionAttribute(TransactionAttributeType.SUPPORTS)
public int getShipmentCount(int productId) {
When called with transaction active:
Caller transaction context passed to method (unless @Asynchronous)
When called without transaction active:
No transaction started
Container must invoke method with new transaction active
Figure 101.4.
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public Shipment createShipment_RequiresNew(Shipment shipment) {
New transaction started
Transaction committed after method returns and prior to returning result to client
When called with transaction active:
Caller transaction suspended during call
Caller transaction resumed after call
Caller transaction is not impacted by resource managers used during call
Caller transaction not passed to anything this method calls
Caller must already have transaction active
When called with transaction active:
Caller transaction context passed to method
When called without transaction active:
javax.ejb.EJBTransactionRequiredException
Caller must never invoke method with transaction active
When called with transaction active:
javax.ejb.EJBException
When called without transaction active:
Called method continues without transaction
Figure 101.8. @Asynchronous Method with REQUIRED
@Asynchronous
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void addQuantity(int productId, int quantity) {
EJB transactions cannot propagate across @Asynchronous methods
Use of REQUIRED in @Asynchronous method will act like REQUIRES_NEW
Methods
@PostConstruct
@PreDestroy
@PrePassivate (Stateful)
@PostActivate (Stateful)
Operate by default in an undefined transaction context
May be defined with REQUIRES_NEW or NOT_SUPPORTED
Any transactions created during a lifecycle method will not be a part of the client's transaction
Figure 101.9. EJB Lifecycle Methods with Container-Managed Transactions
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class WarehouseTxEJB {
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@PostConstruct
public void init() {
logger.debug("*** {}:init({}) ***", getClass().getSimpleName(), super.hashCode());
updateBeanCount(1);
}
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@PreDestroy
public void destroy() {
logger.debug("*** {}:destroy({}) ***", getClass().getSimpleName(), super.hashCode());
updateBeanCount(-1);
}
private void updateBeanCount(int value) {
String beanName = getClass().getSimpleName();
BeanCount count=em.find(BeanCount.class,
beanName,
LockModeType.PESSIMISTIC_WRITE);
if (count!=null) {
count.setCount(count.getCount()+value);
} else {
count = new BeanCount(beanName);
count.setCount(value);
em.persist(count);
}
logger.debug("updatedBeanCount({}) to {}", count.getName(), count.getCount());
}
Must be either
REQUIRED - message receipt integrated with JTA transaction
NOT_SUPPORTED - message receipt not part of JTA transaction
EJB cannot access transaction
EJB has no means to commit transaction -- commit managed by container
EJB can only mark the transaction for rollback -- rollback managed by container
Unchecked exceptions automatically mark the transaction for rollback
Figure 101.10. EJBContext
public interface javax.ejb.EJBContext {
...
javax.transaction.UserTransaction getUserTransaction() throws java.lang.IllegalStateException;
void setRollbackOnly() throws java.lang.IllegalStateException;
boolean getRollbackOnly() throws java.lang.IllegalStateException;
...
}
@Resource
SessionContext ctx;
...
ctx.setRollbackOnly();
...
getUserTransaction()
Returned instance can use used to demarcate transactions
Restricted to EJBs using bean-managed transactions
getRollbackOnly()
Tests whether current transaction is going to be rolled back at the end
Restricted to EJBs using container-managed transactions
setRollbackOnly()
Allows EJBs to trigger the current transaction to be rolled back at the end
Restricted to EJBs using container-managed transactions
Figure 101.11. Example: Explicit Rollback thru EJB/SessionContext
@Resource
private SessionContext ctx;
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Product createProductAndRollback(Product product) {
//create a product in the database
Product p=beanA.createProduct(product);
beanA.flush(); //here just to highlight the impact of the rollback
ctx.setRollbackOnly();
return p; //return the product even thow we are going to roll it back
}
Resources created/modified within the transaction -- including resources operated on by called EJBs -- will be rolled back.
Figure 101.12. Example: IT Client Triggers Rollback
Product product = warehouse.createProductAndRollback(new Product("thing", 6));
//since we did not throw an exception -- we get a normal return object
assertEquals("unexpected quantity for product", 6, product.getQuantity());
//since we setRollbackOnly() prior to the return -- our product will not be there
Product p2 = warehouse.getProduct(product.getId());
assertNull("unexpected/rolled back product found", p2);
Even though the application did not throw an exception and gracefully returned a result -- the IT test shows the entity was not stored during the callback
Two types
Checked/Application
Unchecked/Runtime
Checked/Application exceptions, by default, do not rollback the transaction when throw
Unchecked/Runtime exceptions do rollback the transaction when thrown
No automatic rollback by default
Figure 101.13. Example: Checked/Business Exception
public class MyCheckedProductException extends Exception {
private Product product;
public MyCheckedProductException(Product product, String message) {
super(message);
this.product = product;
}
Checked exceptions, but default, do not trigger rollbacks
Figure 101.14. Example: EJB Method Commits Data, Throws Checked Exception
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Product createProductAndThrowChecked(Product product)
throws MyCheckedProductException {
//create a product in the database
Product p=beanA.createProduct(product);
//throw a checked exception -- the product will still be committed
throw new MyCheckedProductException(p, "planned checked exception");
}
An error in processing can be reported with an exception without rollback
Figure 101.15. Example: Client Triggers Exception and Verifies Data Committed
Product product = null;
try {
warehouse.createProductAndThrowChecked(new Product("thing", 6));
fail("planned exception not thrown");
} catch (MyCheckedProductException ex) {
//the *checked* exception contains the product in the exception
product = ex.getProduct();
assertEquals("unexpected quantity for product", 6, product.getQuantity());
}
//our product should exist
Product p2 = warehouse.getProduct(product.getId());
assertEquals("product not updated async", 6, p2.getQuantity());
IT test verifies entity thrown with the exception was persisted
Checked/Application exceptions can be configured to trigger a rollback
Figure 101.16. Example: Checked/Business Exception
import javax.ejb.ApplicationException;
@ApplicationException(rollback=true)
public class MyCheckedRollbackProductException extends Exception {
Annotation tells container to automatically rollback transaction if thrown
Figure 101.17. Example: EJB Method Throws Checked Exception to Automatically Rollback
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Product createProductAndThrowCheckedRollback(Product product) throws MyCheckedRollbackProductException {
//create a product in the database
Product p=beanA.createProduct(product);
beanA.flush(); //here just to highlight the impact of the rollback
//throw a checked exception marked for rollback -- the product will *NOT* be committed
throw new MyCheckedRollbackProductException(p, "planned checked exception");
}
An error in processing can be reported with an exception without rollback
Figure 101.18. Example: Client Triggers Exception and Verifies Data Committed
Product product = null;
try {
warehouse.createProductAndThrowCheckedRollback(new Product("thing", 6));
fail("planned exception not thrown");
} catch (MyCheckedRollbackProductException ex) {
//the *checked* exception contains the product in the exception
product = ex.getProduct();
assertEquals("unexpected quantity for product", 6, product.getQuantity());
}
//since we used a rollback=true checked exception -- our product will not be there
Product p2 = warehouse.getProduct(product.getId());
assertNull("unexpected/rolled back product found", p2);
IT test verifies entity thrown with the exception was persisted
Persistence contexts can also get propagated
Stateless EJB can propagate its tx-scope persistence context to a called EJB
Stateful EJB can propagate its tx-scoped or extended persistence context to a called EJB
Stateless EJB can work with extended persistence context provided by upstream Stateful client
Stateful EJB cannot transform propagated tx-scope persistence context into an extended
EJB Facade can act as sharing point for common persistence context
Programmatic interface to transactions
Interactions through javax.transaction.UserTransaction
interface
Everything between utx.begin() and utx.commit() is within a single transaction
Bean is in control
Figure 102.1. Example: EJB with Bean-Managed Transactions
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class BmtCreateEJB {
@PersistenceContext(unitName="ejbtx-warehouse")
private EntityManager em;
@Resource
private UserTransaction utx;
public Product createProduct(Product product) {
try {
txWatcher.watchTransaction(getClass(), super.hashCode());
utx.begin(); //<=== manually start Tx
em.joinTransaction();
em.persist(product);
utx.commit(); //<=== manually end Tx
logger.debug("createProduct()={}", product);
return product;
} catch (Exception ex) {
try { utx.rollback(); }
catch (Exception ex2) { ... }
throw new EJBException("error managing transaction", ex);
}
}
Can inject @PersistenceContext/EntityManager or @PersistenceUnit/EntityManagerFactory
Figure 102.2. UserTransaction
public interface javax.transaction.UserTransaction {
void begin() throws javax.transaction.NotSupportedException,
javax.transaction.SystemException;
void commit() throws javax.transaction.RollbackException,
javax.transaction.HeuristicMixedException,
javax.transaction.HeuristicRollbackException,
java.lang.SecurityException,
java.lang.IllegalStateException,
javax.transaction.SystemException;
void rollback() throws java.lang.IllegalStateException,
java.lang.SecurityException,
javax.transaction.SystemException;
void setRollbackOnly() throws java.lang.IllegalStateException,
javax.transaction.SystemException;
int getStatus() throws javax.transaction.SystemException;
void setTransactionTimeout(int) throws javax.transaction.SystemException;
}
Current message being processed cannot be made to join current UserTransaction
Must use Container-Managed REQUIRED scope
Stateful session EJBs using container-managed transactions can receive transaction events
EJBs using bean-managed transactions are in control of their transaction and do not need events
Figure 103.1. Example: Stateful EJB with Transaction Callbacks
@Stateful public class TxWatcherEJB { ... /** * By calling this (or any other business method, the sessionEJB will be created * and enlisted in the current transaction */ public void watchTransaction(Class<?> clazz, int hashCode) { String txName = clazz.getSimpleName() + ":" + hashCode; if (this.txName==null || !this.txName.equals(txName)) { logger = LoggerFactory.getLogger(clazz); logger.debug("watcher EJB {} enlisted in transaction for {}", beanName, txName); this.txName = txName; } else { logger.debug("transaction continued for {}", this.txName); } } @AfterBegin public void afterBegin() { logger.debug("transaction for {} has started", beanName); } @BeforeCompletion public void beforeCompletion() { logger.debug("transaction for {} is committing", beanName); } @AfterCompletion public void afterCompletion(boolean committed) { logger.debug("transaction committed for {}, commited={}", beanName, committed); }
Implement a javax.ejb.SessionSynchronization
callback interface or use annotations
Annotate methods -- no more than once per annotation
afterBegin()/@AfterBegin
Executed within the transaction, prior to first business method
beforeCompletion()/@BeforeCompletion
Executed after last business method complete -- chance to rollback
afterCompletions(boolean)/@AfterCompletion
Executed after transaction complete with a boolean complete
providing status
Cannot be final or static
A stateful session EJB may participate in only a single transaction at a time
A stateful session EJB will not be passivated while in an active transaction
A stateful session EJB's conversational state is not transactional
i.e., it is not automatically rolled back
Table of Contents
Provides a rich Model View Controller (MVC) framework for developing web-based UIs
Integrates well with many CDI concepts
Provides access to model state and actions
Referred by assigned name in page #{sellerController.xxx}
Caches state for page
Scope of state depends on declaration
@RequestScoped - stateless
@ConversationScoped - state maintained across requests within a conversation boundary
@SessionScoped - state maintained for lifetime of user session
@ApplicationScoped - state maintained for life of application
Figure 104.2. Example JSF Controller Bean
@Named("sellerController") //named used by the JSF page to access properties and actions
@ConversationScoped //controls lifespan of instance
public class SellerController implements Serializable { //#{sellerController.xxx}
CDI @Named used to specify name for page to use
Class required to implement Serializable unless RequestScoped
Figure 104.3. Controller Bean Example Business Data State
private Product product;
public Product getProduct() { return product; } //value="#{sellerController.product.name}
public void setProduct(Product product) { //value="#{sellerController.product.name}
this.product = product;
}
<h:inputText value="#{sellerController.product.name}" required="true"/>
Page refers to @Name.property and accesses using associated methods (SellerController.getProperty() and Product.setName() in this case)
Controller holds this state until action called
Figure 104.4. Controller Bean Example Action
public String addNew() { //action="#{sellerController.addNew}"
this.product = new Product(); //create a new Product instance
return null; //for navigation
}
public String add() { //action="#{sellerController.add}"
products.add(product);
return "/seller/seller-products"; //for navigation
}
public String save() { //action="#{sellerController.save}"
product.setSeller(user.getMember());
try {
product = catalog.addProduct(product);
Collections.sort(products, new Product.ProductASC());
return null; //for navigation
} catch (InvalidProduct ex) {
error.setError("error saving product:" + product);
error.setException(ex);
return "error"; //for navigation
}
}
<h:commandButton ...
action="#{sellerController.addNew}"
value="Sell New Product"/>
...
<h:commandButton value="Add Product" action="#{sellerController.add}"/>
...
<h:commandButton value="save" action="#{sellerController.save}">
...
</h:commandButton>
Page refers to action using @Name.methodName
Action methods take no arguments. They act on state supplied by setters.
Action methods return a String result used for navigation
Action methods may interact with business methods/resources to perform task
Figure 104.6. Controller Bean Back-end Business Resources
public class SellerController implements Serializable {
...
@Inject @Tx //back-end business logic that can manage products
private ProductCatalog catalog;
@Inject //place to stash errors to be displayed
private ErrorController error;
Useful in interacting with back-end resources and other page controllers
ProductCatalog is a Java business interface that is implemented by a DAO and EJB.
@Inject tells the provider to inject an implementation that matches the specified type
@Tx is defined within the application to further qualify which provider of the ProductCatalog interface is used
ErrorController is a sibling PageController for crudely displaying error messages and stack traces
Figure 104.7. Example Error Controller Bean
@Named("errorController") // #{errorController.xxx}
@SessionScoped
public class ErrorController implements Serializable {
private String error;
private Exception exception;
public String getError() { return error; } // #{errorController.error}
public void setError(String error) {
this.error = error;
}
public String getStackTrace() { // #{errorController.stackTrace}
if (exception != null) {
StringWriter sw = new StringWriter();
exception.printStackTrace(new PrintWriter(sw));
return sw.toString();
} else {
return null;
}
}
public void setException(Exception exception) {
this.exception = exception;
}
}
Simple example contains business data state and data access methods
Many mark-up languages can be used (e.g., JSP, XHTML, etc.)
Figure 104.8. Example XHTML Error Page
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html">
<h:head>
<title>Error Page</title>
</h:head>
<h:body>
<h2>Error: #{errorController.error}</h2>
<p>
#{errorController.stackTrace}
</p>
<h:link value="Home" outcome="/index" />
</h:body>
</html>
Page uses XHTML markup -- must be a legacy XML document
Page uses JSF types xmlns:h="http://xmlns.jcp.org/jsf/html"
to interact with framework and controller bean
Figure 104.10. Outer Page Structure
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>Sell Products (#{sellerController.seller.name})</title>
</h:head>
<h:body>
<h3>Sell Products (#{sellerController.seller.name})</h3>
</h:body>
</html>
Standard html begin/end tags and basic HTML document formatting
JSF provides replacement h:head and h:body tags
Figure 104.11. Example Form
<h:form binding="#{sellerController.form}">
<h:outputText value="Category"/>
<h:selectOneMenu value="#{sellerController.product.category}" required="true">
<f:selectItems value="#{sellerController.categories}"/>
</h:selectOneMenu>
<h:outputText value="Name"/>
<h:inputText value="#{sellerController.product.name}" required="true"/>
<h:outputText value="Year"/>
<h:inputText value="#{sellerController.product.year}"/>
<h:outputText value="Price"/>
<h:inputText value="#{sellerController.product.price}"/>
<p></p>
<h:commandButton value="Add Product" action="#{sellerController.add}"/>
</h:form>
public class SellerController implements Serializable {
private UIForm form;
public UIForm getForm() { return form; } //binding="#{sellerController.form}"
public void setForm(UIForm form) { //binding="#{sellerController.form}"
this.form = form;
}
Form bound to UIForm
instance supplied by provider. Permits controller bean to manage page form.
Form will display the starting state of Product
Form will update the properties of Product
commandButton registered to action method in controller
Figure 104.12. Example Choice List
<h:selectOneMenu value="#{sellerController.product.category}" required="true">
<f:selectItems value="#{sellerController.categories}"/>
</h:selectOneMenu>
public List<SelectItem> getCategories() {
List<SelectItem> list = new ArrayList<SelectItem>(ProductCategory.values().length);
for (ProductCategory pc: ProductCategory.values()) {
list.add(new SelectItem(pc, pc.getPrettyName()));
}
return list;
}
selectMenu calls getCategories()
to obtain choice list
selectMenu will store that value in SellerController.getProduct().setCatagory()
Figure 104.13. Example commandButton
<h:commandButton value="Add Product" action="#{sellerController.add}"/>
private UIForm form;
private UICommand addCommand;
public String add() {
products.add(product);
Collections.sort(products, new Product.ProductASC());
form.setRendered(false);
addCommand.setRendered(true);
return "/seller/seller-products";
}
commandButton calls SellerController.add()
action
SellerController.add() action acts on the state and provides a navigation result
Figure 104.14. Example Table with Row Actions
<h:form>
<h:dataTable value="#{sellerController.products}" var="p">
...
<h:column>
<f:facet name="header">
<h:column>
<h:outputText value="Name"></h:outputText>
</h:column>
</f:facet>
<h:outputText value="#{p.name}"/>
</h:column>
...
<h:column>
<f:facet name="header">
<h:column>
<h:outputText value="Actions"></h:outputText>
</h:column>
</f:facet>
<h:panelGrid columns="2">
<h:commandButton value="save" action="#{sellerController.save}">
<f:setPropertyActionListener
target="#{sellerController.product}"
value="#{p}"/>
</h:commandButton>
</h:panelGrid>
</h:column>
</h:dataTable>
</h:form>
public String save() {
product.setSeller(user.getMember());
product = catalog.addProduct(product); //EJB call
Collections.sort(products, new Product.ProductASC());
return null;
}
Table displays column and row data
Example shows columns for each property of the product
Example adds commandButton to each row to control actions with back-end
Figure 104.15.
target/cdisales-war |-- index.xhtml |-- seller | `-- seller-products.xhtml `-- WEB-INF |-- beans.xml |-- classes |-- content | `-- error.xhtml |-- faces-config.xml |-- lib `-- web.xml
index.xhtml and seller-products.xhtml meant to be bookmarked and called at any time -- please in public web space
error.xhtml meant to be navigated to from other controllers. Placed in non-public WEB-INF directory
faces-config.xml can be used for navigation flow and pre-dates @Annotations
beans.xml enables CDI processing
Figure 104.16. Example JSF web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<context-param>
<param-name>javax.faces.PROJECT_STAGE</param-name>
<param-value>Development</param-value>
</context-param>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.xhtml</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.xhtml</welcome-file>
</welcome-file-list>
</web-app>
FacesServlet
declared to process all JSF pages
PROJECT_STAGE=Development enables extra debug in output
Figure 104.17. Example welcome-file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="en"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:f="http://java.sun.com/jsf/core">
<h:head>
<title>CDI Sales</title>
</h:head>
<h:body>
<h2>CDI Sales Main Menu</h2>
<ul>
<li><h:link value="Sell Products" outcome="seller/seller-products"/></li>
</ul>
</h:body>
</html>
Provides a top level entry page
Example provides link(s) to more detailed pages
This and follow-on pages may or may not reference a controller
Figure 104.18. Example faces-config.xml
<?xml version="1.0"?>
<faces-config
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
version="2.2">
<navigation-rule>
<from-view-id>/seller/seller-products.xhtml</from-view-id>
<navigation-case>
<from-outcome>error</from-outcome>
<to-view-id>/WEB-INF/content/error.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
</faces-config>
Any "error"
output from seller-products will navigate to error page
Figure 104.19. Example (empty) beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans bean-discovery-mode="all" version="1.1"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd">
</beans>
CDI activated when file exists (blank, empty, or populated)
JSR-299 (CDI 1.0), JSR-346 (CDI 1.1)
Started as "Web Beans"
Derived from "Seam"
Original draft conflicted with Google and Spring JSR submission
JSR-330 (Java Injection)
Approved version refactored to account for JSR-330
Figure 105.1. Beans
@Named
public class JPASchedulerDAOImpl extends JPADAOBase<Task> implements SchedulerDAO {
@Stateless
public class CookEJB extends SchedulerBase implements CookLocal {
Beans can be simple POJOs, EJBs, Servlets, etc.
CDI Beans generalize behavior provided by larger frameworks
Type-safe, Java-based container injection
Container will match injection point with managed bean of requested type
Error if container finds multiple classes satisfying requested type
Figure 105.3. Qualifiers and Qualified Injection
@Inject @Cook
protected Scheduler cook;
@Stateless
@Cook
public class CookEJB extends SchedulerBase implements CookLocal {
@Annotation qualifier specializes injection type to remove ambiguity
Figure 105.4. Qualifier Annotations
package ejava.examples.jndidemo;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.*;
import static java.lang.annotation.ElementType.*;
import javax.inject.Qualifier;
@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface Cook {
}
java.lang.annoation
@Retention
how long the class is retained (default=CLASS)
RententionPolict
CLASS - annotation recorded in class file but not available at runtime
RUNTIME - annotation recorded in class and available at runtime
SOURCE - annotation discarded by compiler
@Target
kind of Java construct the annotation applies to
ElementType
TYPE
FIELD
METHOD
PARAMETER
(more)
javax.inject (JSR-330)
@Inject
an injection target
@Named
string qualifier (for JSPs, etc. to reference bean using text)
@Qualifier
identifies qualifier annotations
javax.enterprise.inject (JSR-299)
@Produces
an injection source
@Any
no qualifiers
Instance<Bean>
runtime source of type Bean
Activated when beans.xml is located in appropriate location
META-INF/beans.xml for EJBs and regular JARs
WEB-INF/beans.xml for WARs
Figure 105.5. beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans bean-discovery-mode="all" version="1.1"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd">
<scan>
<exclude name="info.ejava.examples.ejb.cdisales.dao.UserMgmtDAO"/>
</scan>
</beans>
Can be blank or empty
Example shows XML alternative to using @Veto - which makes a bean unavailable for injection
public class SchedulerResources {
@PersistenceContext(unitName="jndidemo")-->-+
@Produces |
@JndiDemo |
+-<----public EntityManager em; <------------------+
|
| @Stateless
| public class TrainSchedulerEJB
| extends SchedulerBase implements TrainSchedulerRemote {
|
| @Inject @JndiDemo
`----->private EntityManager em;
Public field used as injection source
Useful when no behavior is required
public class DAOFactory {
@PersistenceContext(unitName="ejbcdi-sales")
@Produces
public EntityManager em;
@Produces
@Dependent
+-<----public UserMgmtDAO getUserMgmtDAO(EntityManager em, Validator validator) {
| UserMgmtDAO dao = new UserMgmtDAO();
| dao.setEntityManager(em);
| dao.setValidator(validator);
| return dao;
| }
|
| @Stateless
| public class UserMgmtEJB implements UserMgmtLocal {
| @Inject
`----->private UserMgmtDAO dao;
Injection type was not ambiguous - no @Qualifier required
Method called by container
Method declares parameters for what it needs to build product
Method parameters automatically injected
@Dependent allows injection point to determine scope for the bean (e.g., request, session, etc.)
Figure 106.1. JNDI Resource Lookup
@Resource(lookup="java:jboss/datasources/ExampleDS")
@Produces
@JndiDemo
public DataSource ds;
Injection source populated from a JNDI lookup
Injection source populated from hard coded value. Could be changed to method.
@EJB(lookup="java:app/jndiDemoEJB/CookEJB!ejava.examples.jndidemo.ejb.CookLocal")
@Produces |
@Cook |
+---<--public Scheduler cook; <-----------------------------+
|
| @Stateless
| public class TrainSchedulerEJB
| @Inject @Any
`----> protected Instance<Scheduler> anyCook;
protected Scheduler cook2; |
+-------------------+
v
cook2 = anyCook.select(new CookQualifier()).get();
public class CookQualifier extends javax.enterprise.util.AnnotationLiteral<Cook>{}
Allows bean to hold onto all implementations of type
Bean can select from different implementations at runtime
@Named
public class JPASchedulerDAOImpl extends JPADAOBase<Task> implements SchedulerDAO {
Assignable to four (4) different types
JPASchedulerDAOImpl
JPADAOBase<Task>
SchedulerDAO
java.lang.Object
@Typed({SchedulerDAO.class, JPASchedulerDAOImpl.class})
public class JPASchedulerDAOImpl extends JPADAOBase<Task> implements SchedulerDAO {
@Type reduces number of types allowed
JPASchedulerDAOImpl
SchedulerDAO
@Named("sellerController")
@ConversationScoped
public class SellerController implements Serializable {
<h3>Sell Products (#{sellerController.seller.name})</h3>
Used for document expression languages to reference intended bean
Table of Contents
Introduce key technology for exposing resources to the web (JAX-RS and JAXB)
Introduce flexible EJB deployment (WAR-based)
At the completion of this topic, the student shall
have more understanding of:
flexibility of EJB deployment
exposing resources using web-friendly HTTP facades
marshaling DTOs using web-friendly representations (XML)
be able to:
Deploy an EJB within a WAR
Expose an asset or functionality through a (web) resource and URI using JAX-RS
Use HTTP methods to interact with resources using JAX-RS
Implement representations with XML, JAXB, and JAX-RS
Prior to JavaEE 6
EJB beans could only be deployed in EJB modules
EJB modules integrated with WARs using common EAR
Starting with JavaEE 6
EJB beans can be embedded within WAR
EJB modules can be deployed within WAR
|-- index.html `-- WEB-INF ... |-- jboss-web.xml |-- lib `-- web.xml
index.html - example static web resource
WEB-INF/jboss-web.xml - container-specific configuration. Used here to control name of WAR
WEB-INF/lib - directory to place jars for classpath
WEB-INF/web.xml - standard WAR deployment descriptor
EJB beans developed external to WAR
Integrated using WEB-INF/lib
WEB-INF/lib/ `-- webejbCustomerEJB-3.0.2012.2-SNAPSHOT.jar
example EJB module contents
$ jar tf WEB-INF/lib/webejbCustomerEJB-3.0.2012.2-SNAPSHOT.jar | sort ejava/examples/ejbwar/customer/bo/Customer.class ejava/examples/ejbwar/customer/bo/CustomerRepresentation.class ejava/examples/ejbwar/customer/bo/Customers.class ejava/examples/ejbwar/customer/client/ ejava/examples/ejbwar/customer/client/CustomerClient.class ejava/examples/ejbwar/customer/client/CustomerClientImpl.class ejava/examples/ejbwar/customer/CustomerResources.class ejava/examples/ejbwar/customer/Customers.class ejava/examples/ejbwar/customer/dao/ ejava/examples/ejbwar/customer/dao/CustomerDAO.class ejava/examples/ejbwar/customer/dao/CustomerDAOImpl.class ejava/examples/ejbwar/customer/ejb/ ejava/examples/ejbwar/customer/ejb/CustomerMgmt.class ejava/examples/ejbwar/customer/ejb/CustomerMgmtEJB.class ejava/examples/ejbwar/customer/ejb/CustomerMgmtLocal.class ejava/examples/ejbwar/customer/ejb/CustomerMgmtRemote.class ejava/examples/ejbwar/customer/rs/ ejava/examples/ejbwar/customer/rs/CustomersResource.class META-INF/beans.xml META-INF/MANIFEST.MF META-INF/persistence.xml
JNDI name uses WAR name (minus.war)
java:global/jaxrsInventoryWAR/CustomerMgmtEJB!ejava.examples.ejbwar.customer.ejb.CustomerMgmtRemote java:app/jaxrsInventoryWAR/CustomerMgmtEJB!ejava.examples.ejbwar.customer.ejb.CustomerMgmtRemote java:module/CustomerMgmtEJB!ejava.examples.ejbwar.customer.ejb.CustomerMgmtRemote java:jboss/exported/jaxrsInventoryWAR/CustomerMgmtEJB!ejava.examples.ejbwar.customer.ejb.CustomerMgmtRemote java:global/jaxrsInventoryWAR/CustomerMgmtEJB!ejava.examples.ejbwar.customer.ejb.CustomerMgmtLocal java:app/jaxrsInventoryWAR/CustomerMgmtEJB!ejava.examples.ejbwar.customer.ejb.CustomerMgmtLocal java:module/CustomerMgmtEJB!ejava.examples.ejbwar.customer.ejb.CustomerMgmtLocal
jboss-web.xml used to express WAR name without version#
$ cat WEB-INF/jboss-web.xml <jboss-web> <!-- needed to always assure that version# does not get into JNDI name --> <context-root>jaxrsInventoryWAR</context-root> </jboss-web>
EJB module contains persistence.xml and CDI beans.xml
META-INF/beans.xml META-INF/persistence.xml
EJB beans developed as part of WAR
No need to declare new EJB module just to get EJB bean functionality
WEB-INF |-- beans.xml |-- classes | |-- ejava | | `-- examples | | `-- ejbwar | | `-- inventory | | |-- bo | | | |-- Categories.class | | | |-- Category.class | | | |-- InventoryRepresentation.class | | | |-- Product.class | | | `-- Products.class | | |-- client | | | |-- InventoryClient.class | | | `-- InventoryClientImpl.class | | |-- dao | | | |-- InventoryDAO.class | | | `-- InventoryDAOImpl.class | | |-- ejb | | | |-- InventoryMgmtEJB.class | | | `-- InventoryResources.class | | |-- rmi | | | |-- InventoryMgmtRemote.class | | | `-- InventoryMgmtRMIEJB.class | | `-- rs | | |-- Application.class | | |-- CategoriesResource.class | | |-- PrettyPrinter.class | | |-- ProductsResource.class | | |-- ResourceHelper.class | | `-- TxFilter.class | `-- META-INF | `-- persistence.xml ...
CDI descriptor hosted in WEB-INF/beans.xml
persistence unit hosted in WEB-INF/classes/META-INF/persistence.xml
JNDI name uses WAR name (minus.war)
java:global/jaxrsInventoryWAR/InventoryMgmtRMIEJB!ejava.examples.ejbwar.inventory.rmi.InventoryMgmtRemote java:app/jaxrsInventoryWAR/InventoryMgmtRMIEJB!ejava.examples.ejbwar.inventory.rmi.InventoryMgmtRemote java:module/InventoryMgmtRMIEJB!ejava.examples.ejbwar.inventory.rmi.InventoryMgmtRemote java:jboss/exported/jaxrsInventoryWAR/InventoryMgmtRMIEJB!ejava.examples.ejbwar.inventory.rmi.InventoryMgmtRemote java:global/jaxrsInventoryWAR/InventoryMgmtRMIEJB java:app/jaxrsInventoryWAR/InventoryMgmtRMIEJB java:module/InventoryMgmtRMIEJB
Alternative: Use traditional RMI interface (specific to Java)
Alternative: Use Web-oriented HTTP interface (open to all platforms)
JAX-RS 1.x only addresses server side
Many vendor-specific client libraries (e.g., RESTEasy, Jersey, HttpClient)
HTTP/REST Client API will be part of JAX-RS 2.0
Class examples implements with Apache HttpClient
Notional example accessing /index.html
HttpClient client = new DefaultHttpClient();
URI appURI = "http://127.0.0.1:8080/jaxrsInventoryWAR";
URI uri = UriBuilder.fromUri(appURI).path("index.html").build();
//build the overall request
HttpGet get = new HttpGet(uri);
get.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_XML);
HttpResponse response = client.execute(get);
if (Response.Status.OK.getStatusCode() == response.getStatusLine().getStatusCode()) {
return ...
}
An asset to be exposed to the web
Document, set of properties, method, nearly anything
Web has limited methods but an unlimited number of resources
Example
Products
Categories
Customers
An address to access a particular resource
Can be expressed relative
index.html /rest/products /rest/categories /rest/customers
Can be expressed fully qualified
http://127.0.0.1:8080/jaxrsInventoryWAR/index.html http://127.0.0.1:8080/jaxrsInventoryWAR/rest/products http://127.0.0.1:8080/jaxrsInventoryWAR/rest/categories http://127.0.0.1:8080/jaxrsInventoryWAR/rest/customers
May have query parameters
http://127.0.0.1:8080/jaxrsInventoryWAR/rest/categories?name=&offset=0&limit=0
May have nested path parameters
http://127.0.0.1:8080/jaxrsInventoryWAR/rest/products/1
Building URIs with JAX-RS URIBuilder
//start with the URI for the WAR deployed to the server //that ends with the context-root return UriBuilder.fromUri(appURI) //add path info from the //javax.ws.rs.core.Application @ApplicationPath .path("rest") //add in @Path added by resource class .path(resourceClass) //add in @Path added by resource class' method .path(resourceClass,method); //marshall @PathParm into the URI .build(id);
Bounded ("uniform interface")
Primary Set
GET - non-destructive read
POST - create and other methods
PUT - create or update
DELETE - delete
Secondary Set
HEAD - a GET without the data - metadata only
OPTIONS - lists which methods supported
Example: Get Product ID=1
GET http://127.0.0.1:8080/jaxrsInventoryWAR/rest/products/1
Standardized
2xx - success
3xx - success/redirect
4xx - client error
5xx - server error
Common Set
200 - OK
201 - CREATED
400 - BAD_REQUEST
404 - NOT_FOUND
500 - INTERNAL_ERROR
Annotated with @Path
Injected with implementation details and call context
@Path("/products")
public class ProductsResource {
private static final Log log = LogFactory.getLog(ProductsResource.class);
@Inject
private InventoryMgmtEJB ejb;
@Context
private Request request;
@Context
private UriInfo uriInfo;
...
Create new resource or tunnel service
Returns CREATED and URO of created resource
@POST @Path("")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Response addCustomer(Customer customer) {
log.debug(String.format("%s %s", request.getMethod(), uriInfo.getAbsolutePath()));
try {
Customer c = ejb.addCustomer(customer);
URI uri = UriBuilder.fromUri(uriInfo.getAbsolutePath())
.path(CustomersResource.class, "getCustomer")
.build(c.getId());
return Response.created(uri)
.entity(c)
.build();
} catch (Exception ex) {
return serverError(log, "creating person", ex).build();
}
}
Non-destructive read
Returns OK with payload
@GET @Path("{id}")
@Produces(MediaType.APPLICATION_XML)
public Response getProduct(@PathParam("id")int id) {
log.debug(String.format("%s %s", request.getMethod(), uriInfo.getAbsolutePath()));
try {
Product product = ejb.getProduct(id);
if (product != null) {
return Response.ok(product)
.build();
}
else {
return Response.status(Response.Status.NOT_FOUND)
.entity(String.format("unable to locate product %d", id))
.type(MediaType.TEXT_PLAIN)
.build();
}
} catch (Exception ex) {
return ResourceHelper.serverError(log, "getting product", ex).build();
}
}
Create new or update existing
Optional results
@PUT @Path("{id}")
@Consumes(MediaType.APPLICATION_XML)
@Produces(MediaType.APPLICATION_XML)
public Response updateProduct(@PathParam("id") int id, Product product) {
log.debug(String.format("%s %s", request.getMethod(), uriInfo.getAbsolutePath()));
try {
Product p = ejb.updateProduct(product);
return Response.ok(p)
.build();
} catch (Exception ex) {
return ResourceHelper.serverError(log, "update product", ex).build();
}
}
Deletes specified resource
Optional results
@DELETE @Path("{id}")
@Produces(MediaType.APPLICATION_XML)
public Response deleteCustomer(@PathParam("id") int id) {
log.debug(String.format("%s %s", request.getMethod(), uriInfo.getAbsolutePath()));
try {
ejb.deleteCustomer(id);
return Response.ok()
.build();
} catch (Exception ex) {
return serverError(log, "deleting person", ex).build();
}
}
Request supplies method, URI, optional payload, and other properties
GET /jaxrsInventoryWAR/rest/categories?name=snacks&offset=0&limit=0 HTTP/1.1 Accept: application/xml Host: 127.0.0.1:7080 Connection: Keep-Alive User-Agent: Apache-HttpClient/4.1.3 (java 1.5)
Response provides status code, optional entity, and other properties]
HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Content-Type: application/xml Transfer-Encoding: chunked Date: Tue, 06 Nov 2012 07:32:18 GMT <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:catageories xmlns:ns2="http://webejb.ejava.info/inventory" count="1" limit="0" offset="0" version="0"> <categories id="4" name="snacks" version="1"> <productCount>1</productCount> </categories> </ns2:catageories>
Elements
Attributes
Namespaces
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ns2:catageory name="snacks" id="1" version="1" xmlns:ns2="http://webejb.ejava.info/inventory">
<productCount>1</productCount>
<products>
<product id="1" name="chips" version="0"/>
</products>
</ns2:catageory>
@XmlRootElement - needed to be able to manipulate object independently of graph
@XmlType - defines/names structure of XML construct
@XmlAccessorType - directs JAXB to invoke property or field accessors
@XmlRootElement(name="catageory", namespace=InventoryRepresentation.NAMESPACE)
@XmlType(name="Category", namespace=InventoryRepresentation.NAMESPACE)
@XmlAccessorType(XmlAccessType.PROPERTY)
public class Category extends InventoryRepresentation {
private int id;
private String name;
private Integer productCount;
private List<Product> products=new ArrayList<Product>();
public Category() {}
public Category(String name) {
this.name=name;
}
...
}
Typically used for identifying properties
Single value, non-repeating
@XmlAttribute(required=true)
public int getId() { return id; }
public void setId(int id) {
this.id = id;
}
@XmlAttribute(required=true)
public String getName() { return name; }
public void setName(String name) {
this.name = name;
}
Typically used for descriptive properties
@XmlElement(required=true)
public int getProductCount() { return productCount!=null ? productCount :products.size();}
public void setProductCount(int productCount) {
this.productCount = productCount;
}
JAXBContext
Marshaller
JAXB_FORMATTED_OUTPUT
JAXBContext jbx = JAXBContext.newInstance(object.getClass());
Marshaller marshaller = jbx.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter writer = new StringWriter();
marshaller.marshal(object, writer);
return writer.toString();
JAX-RS requires built-in provider support for JAXB
JAX-RS defines marshalling framework to add extensions
Client request payload demarshalled into input JAXB class
@POST @Path("")
@Consumes(MediaType.APPLICATION_XML)
public Response addCustomer(Customer customer) {
Table of Contents
At the completion of this topic, the student shall
have more understanding of:
the basic concepts behind JavaEE Security
how to runAs another user within JavaEE
be able to:
define access control policy for EJB and Web applications
authenticate and obtain proper access to the application
implement login using JNDI InitialContext using JBoss Remoting
implement login using EJB Client and other JBoss proprietary APIs
EJB access restrictions
Declarative
Programmatic
EJB assignment to Security Domain
Server definition of Security Domain
Server Security Domain authentication and authorization
Figure 111.1. EJB Access Control: Annotations
@Stateless(name="SecurePingEJB")
public class SecurePingEJB implements SecurePingRemote, SecurePingLocal {
...
@PermitAll
public String pingAll() {
return getInfo("pingAll");
}
@RolesAllowed({"user"})
public String pingUser() {
return getInfo("pingUser");
}
@RolesAllowed({"admin"})
public String pingAdmin() {
return getInfo("pingAdmin");
}
@DenyAll
public String pingExcluded() {
return getInfo("pingExcluded");
}
}
javax.annotation.security
annotations define access requirements to EJB methods
Figure 111.2. EJB Access Control: ejb-jar.xml
<assembly-descriptor>
...
<method-permission>
<unchecked/>
<method>
<ejb-name>SecurePingEJB</ejb-name>
<method-name>pingAllmethod-name>
</method>
</method-permission>
<method-permission>
<role-name>admin</role-name>
<method>
<ejb-name>SecurePingEJB</ejb-name>
<method-name>pingAdmin</method-name>
</method>
</method-permission>
<method-permission>
<role-name>user</role-name>
<method>
<ejb-name>SecurePingEJB</ejb-name>
<method-name>pingUser</method-name>
</method>
</method-permission>
<method-permission>
<excluded/>
<method>
<ejb-name>SecurePingEJB</ejb-name>
<method-name>pingExcluded</method-name>
</method>
</method-permission>
</assembly-descriptor>
Access restrictions can also be defined in the ejb-jar.xml deployment descriptor
Figure 111.3. Programmatic Security Check
@Resource
SessionContext ctx;
if (ctx.isCallerInRole(role)) {
...
}
Permits more fine-grain access control down to the object level
role can be literal or a logical mapping (see below)
Figure 111.4. Optional Role Mapping: ejb-jar.xml
<enterprise-beans>
<session>
<ejb-name>SecurePingEJB</ejb-name>
<security-role-ref>
<description>role-name checked within EJB</description>
<role-name>internalRole</role-name>
<role-link>admin</role-link>
</security-role-ref>
</session>
</enterprise-beans>
<assembly-descriptor>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
</assembly-descriptor>
Permits role-name within Java code to be mapped to security role
Figure 111.5. EJB Security Setup: META-INF/jboss-ejb3.xml
<?xml version="1.0"?>
<jboss:ejb-jar
xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
xmlns:sec="urn:security"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-2_0.xsd
http://java.sun.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-spec-2_0.xsd
urn:security urn:security"
version="3.1"
impl-version="2.0">
<assembly-descriptor>
<sec:security>
<ejb-name>*</ejb-name>
<sec:security-domain>other</sec:security-domain>
</sec:security>
</assembly-descriptor>
</jboss:ejb-jar>
Assigns one or more EJBs to a security-domain defined on server
Security is ignored until this is place
JBoss default security-domain is "other"
Realm
User Credentials (username and password)
Figure 111.6. Example Security Domain: "other"
<security-domain name="other" cache-type="default">
<authentication>
<login-module code="Remoting" flag="optional">
<module-option name="password-stacking" value="useFirstPass"/>
</login-module>
<login-module code="RealmDirect" flag="required">
<module-option name="password-stacking" value="useFirstPass"/>
</login-module>
</authentication>
</security-domain>
Watches over interactions
Defines policy on what can take place
Figure 111.7. Example Application Realm: "ApplicationRealm"
<security-realm name="ApplicationRealm">
<authentication>
<local default-user="$local" allowed-users="*" skip-group-loading="true"/>
<properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
</authentication>
<authorization>
<properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
</authorization>
</security-realm>
Defines authentication and authorization sources
Optionally defines a default user for anonymous
JBoss can operate in a mode to trust a user connecting from the same machine and running under the same operating system identity that launched the server. This allows for development scenarios to bypass login credentials and Command Line Interface (CLI) to operate without credential prompts.
<local default-user="$local" allowed-users="*" skip-group-loading="true"/>
JBoss installs with a set of static files to implement user authentication and authorization. This can be augmented or replaced by more dynamic sources such as a database or LDAP.
Figure 111.8. Example Authentication Source: application-users.properties
$ cat standalone/configuration/application-users.properties
known=0aea67d2fdd25e4207f0bc2a5c84a1a6
user1=b802b482f67706cb453ab9294b88a9ea
admin1=7357a05355829fb2855412b69297a55b
publisher1=a15cfe086e59fea3fe4c787b834f55b8
subscriber1=838a4d7dceb6655e0297947d9677c6cb
requestor1=21a138aa0eeec44ee9cf08ebd9a1dcb5
worker1=41c30fe6af7d12f7b7fd9aa60060f13d
admin2=f064c7710b767a156f7e07469f8ad944
Authenticates identity of user
Username and hashed password stored in static file
Figure 111.9. Example Access Control Source: application-roles.properties
$ cat standalone/configuration/application-roles.properties
user1=user,esales-user
admin1=user,admin,dmv-admin,mayberry-admin,eleague-admin,eclub-admin
publisher1=publisher
subscriber1=subscriber
requestor1=requestor
worker1=worker
admin2=admin
Assigns roles to authenticated user
Username and roles stored in static file
JBoss Remoting and JNDI InitialContext
EJBClient and CallbackHandler
Changing Users
Access Violations
Access Granted
Figure 112.1. jndi.properties
java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory java.naming.factory.url.pkgs=org.jboss.ejb.client.naming java.naming.provider.url=http-remoting://localhost:8080 jboss.naming.client.ejb.context=true
factory.initial set to JBoss Remoting implementation
provider.url set to address of JBoss server
ejb.context set to "true" to use this library to establish EJB contexts
url.pkgs option and used for alternate namespaces (e.g., "ejb:")
Figure 112.2. JBoss Remoting JNDI Name
securePingEAR/securePingEJB/SecurePingEJB!info.ejava.examples.secureping.ejb.SecurePingRemote
Using generic JBoss Remoting JNDI name
Figure 112.3. Client Provides Credentials to JNDI InitialContext
private Context runAs(String username, String password) throws NamingException {
Properties env = new Properties();
if (username != null) {
env.put(Context.SECURITY_PRINCIPAL, username);
env.put(Context.SECURITY_CREDENTIALS, password);
}
env.put("jboss.naming.client.ejb.context", true); //override anything we put there for EJBClient
return new InitialContext(env);
}
Client provides credentials in JNDI prior to obtaining InitialContext
Must use current JNDI Context to lookup @Remote
Figure 112.4. Example Changing Users with JBoss Remoting
jndi=runAs(userUser, userPassword);
ejb=(SecurePing)jndi.lookup(jndiName);
assertFalse("user in admin role", ejb.isCallerInRole("admin"));
jndi=runAs(adminUser, adminPassword);
ejb=(SecurePing)jndi.lookup(jndiName);
assertTrue("admin not in admin role", ejb.isCallerInRole("admin"));
Client can switch credentials with a change in InitialContexts and @Remote reference
Figure 112.5. Fixed Credentials
java.naming.security.principal=known java.naming.security.credentials=password1!
Fixed credentials can be placed in jndi.properties when known and not changing
Figure 112.6. jndi.properties
java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory java.naming.factory.url.pkgs=org.jboss.ejb.client.naming java.naming.provider.url=http-remoting://localhost:8080 jboss.naming.client.ejb.context=false
factory.initial not important, will delegate to naming extension
url.pkgs defines Java package with EJBClient extensions
provider.url ignored in this case
ejb.context not used and set to false in this case
Figure 112.7. jboss-ejb-client.properties
remote.connectionprovider.create.options.org.xnio.Options.SSL_ENABLED=false remote.connections=default remote.connection.default.host=localhost remote.connection.default.port=8080 remote.connection.default.connect.options.org.xnio.Options.SASL_DISALLOWED_MECHANISMS=JBOSS-LOCAL-USER remote.connection.default.connect.options.org.xnio.Options.SASL_POLICY_NOANONYMOUS=false remote.connection.default.callback.handler.class=info.ejava.examples.secureping.ejbclient.BasicCallbackHandler
SSL_ENABLED=false - example setup does not yet cover SSL and would require trustStore
SASL_DISALLOWED_MECHANISMS=JBOSS-LOCAL-USER - disallowing default user to be set to $local
callback.handler.class=info.ejava...BasicCallbackHandler - provider credentials via callback
Figure 112.8. CallbackHandler used to Provide Credentials
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.RealmCallback;
public class BasicCallbackHandler implements CallbackHandler {
private String name;
private char[] password;
private String realm="ApplicationRealm";
public BasicCallbackHandler(String name, String password) {
this.name = name;
setPassword(password);
}
public void handle(Callback[] callbacks)
throws UnsupportedCallbackException, IOException {
for (Callback cb : callbacks) {
if (cb instanceof NameCallback) {
((NameCallback)cb).setName(name);
}
else if (cb instanceof PasswordCallback) {
((PasswordCallback)cb).setPassword(password);
}
else if (cb instanceof RealmCallback) {
((RealmCallback)cb).setText(realm);
}
else {
throw new UnsupportedCallbackException(cb);
}
}
}
}
Class was registered in jboss-ejb-client.properties
Class responds to handle() callbacks
Figure 112.9. CallbackHandler Accessed at Class Scope
public class BasicCallbackHandler implements CallbackHandler {
...
private static CallbackHandler login;
public void handle(Callback[] callbacks)
throws UnsupportedCallbackException, IOException {
if (login!=null && login!=this) {
login.handle(callbacks);
return;
}
for (Callback cb : callbacks) {
...
public static void setLogin(CallbackHandler login) {
BasicCallbackHandler.login=login;
}
public static CallbackHandler getLogin() {
return login;
}
}
CallbackHandler accessed at class-scope
Must register credentials at class level
Credentials tied to connection. Cannot change identities without breaking and re-establishing connection (with proprietary API)
Figure 112.10. Proprietary API used to Change Identity
private void runAs(CallbackHandler login) throws NamingException, IOException {
InputStream is = getClass().getResourceAsStream("/jboss-ejb-client.properties");
try {
Properties props = new Properties();
props.load(is);
BasicCallbackHandler.setLogin(login);
EJBClientConfiguration cfg = new PropertiesBasedEJBClientConfiguration(props);
ContextSelector<EJBClientContext> contextSelector = new ConfigBasedEJBClientContextSelector(cfg);
EJBClientContext.setSelector(contextSelector);
} finally {
is.close();
}
}
EJBClient stores credentials with connection
Must establish new connection to have CallbackHandler called again
Figure 112.11. Example Changing Users with EJBClient
runAs(userLogin);
assertFalse("user in admin role", securePing.isCallerInRole("admin"));
runAs(adminLogin);
assertTrue("admin not in admin role", securePing.isCallerInRole("admin"));
No need to get new InitialContext or new lookup of @Remote
Figure 112.12. Fixed Credentials
remote.connection.default.username=known remote.connection.default.password=password1!
Fixed credentials can be placed in jboss-ejb-client.properties when known and not changing
Useful to implement sanity check to assure authentication and authorizations in place
Figure 112.13. EJB Security Query Methods
@Stateless
public class SecurePingEJB implements SecurePingRemote, SecurePingLocal {
@Resource
SessionContext ctx;
@PermitAll
public boolean isCallerInRole(String role) {
boolean result = ctx.isCallerInRole(role);
logger.debug("user=" + ctx.getCallerPrincipal().getName() +
", isCallerInRole(" + role + ")=" + result);
return result;
}
@PermitAll
public String getPrincipal() {
String name= ctx.getCallerPrincipal().getName();
logger.debug("getPrincipal(), name=" + name);
return name;
}
Implement security query methods in a EJB
Figure 112.14. Client Issues Security Query Calls
runAs(userLogin);
assertEquals("unexpected user", userUser, securePing.getPrincipal());
assertFalse("user in internalRole role", securePing.isCallerInRole("internalRole"));
runAs(adminLogin);
assertEquals("unexpected user", adminUser, securePing.getPrincipal());
assertTrue("admin not in internalRole role", securePing.isCallerInRole("internalRole"));
Client asserts security query results to verify setup correctly
Figure 112.15. Example Access Violation
Context jndi = new InitialContext();
logger.debug("looking up jndi.name={}", jndiName);
securePing = (SecurePingRemote)jndi.lookup(jndiName);
try {
runAs(userLogin);
logger.info(securePing.pingAdmin());
fail("didn't detect non-admin user");
}
catch (EJBAccessException ex) {
logger.info("expected exception thrown:" + ex);
}
-looking up jndi.name=ejb:securePingEAR/securePingEJB/SecurePingEJB!info.ejava.examples.secureping.ejb.SecurePingRemote -realm callback:ApplicationRealm -name callback:user1 -password callback:password1! -expected exception thrown:javax.ejb.EJBAccessException: JBAS014502: Invocation on method: public abstract java.lang.String info.ejava.examples.secureping.ejb.SecurePing.pingAdmin() of bean: SecurePingEJB is not allowed
EJBAccessException thrown when accessing method not allowed
Figure 112.16. Example Access Granted
runAs(adminLogin);
logger.info(securePing.pingAdmin());
-realm callback:ApplicationRealm -name callback:admin1 -password callback:password1! -called pingAdmin, principal=admin1, isUser=true, isAdmin=true, isInternalRole=true
Access to method protected by declarative security granted
Results of programmatic security checks returned in formatted text string
No caller context
Elevate access
Run-as role-name and identity
Figure 113.1. Default security-identity: use-caller-identity
<enterprise-beans>
<session>
<ejb-name>SecurePingEJB</ejb-name>
<security-identity>
<use-caller-identity/>
</security-identity>
</session>
</enterprise-beans>
security-identity of bean defaults to caller identity
Figure 113.2. Run-as security-identity: role-name
<enterprise-beans>
<session>
<ejb-name>SecurePingClientEJB</ejb-name>
<security-identity>
<run-as>
<role-name>admin</role-name>
</run-as>
</security-identity>
</session>
</enterprise-beans>
<assembly-descriptor>
<security-role>
<role-name>admin</role-name>
</security-role>
</assembly-descriptor>
import javax.annotation.security.RunAs;
@Stateless
@PermitAll
@RunAs("admin")
public class SecurePingClientEJB
implements SecurePingClientRemote, SecurePingClientLocal {
@EJB(lookup="ejb:securePingEAR/securePingEJB/SecurePingEJB!info.ejava.examples.secureping.ejb.SecurePingRemote")
SecurePingRemote securePingServer;
EJB can discard caller's identity/roles and run-as a specific role
Requires specification of an identity (provider-specific annotation)
Figure 113.3. Run-as principal: identity
<assembly-descriptor>
<sec:security>
<ejb-name>*</ejb-name>
<sec:security-domain>other</sec:security-domain>
<sec:run-as-principal>admin1</sec:run-as-principal>
</sec:security>
</assembly-descriptor>
Specifies specific user-identity to run-as
Vendor-specific
Figure 113.4. Invoking Protected EJB thru Run-as Proxy
runAs(userLogin);
logger.info(securePing.pingAdmin());
@RunAs("admin")
public String pingAdmin() {
return securePingServer.pingAdmin();
}
-realm callback:ApplicationRealm -name callback:user1 -password callback:password1! -securePingClient called pingAdmin, principal=user1, isUser=true, isAdmin=false, isInternalRole=false: securePing=called pingAdmin, principal=admin1, isUser=false, isAdmin=true, isInternalRole=true
user1 is allowed to call method restricted to admin when proxied by run-as EJB
proxy EJB sees caller as user1 and having all assigned roles
proxied EJB sees caller as admin1 and only having assigned admin role
Lock down access to web pages and commands
Identify user prior to EJB interaction
HTTP Basic Authentication
supported by HTTP protocol
username/password-based
browser collects information from client
authenticates user in a realm
not secure; passwords sent simple base64 encoding
short-comings overcome by layering over SSL (HTTPS)
Form-based Authentication
permits JSP/HTML forms to gather user info
HTTPS
encrypts communication channel
based on public/private key
hides all client/server exchange, including username/password info
Figure 114.1. Example Security Constraints
<security-constraint>
<web-resource-collection>
<web-resource-name>admin-only</web-resource-name>
<url-pattern>/model/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
Anything accessed via specified url-pattern must have admin role-name
Communication must be encrypted (i.e., switch to HTTPS)
Figure 114.2. Example FORM-based Login
<login-config>
<!--
<auth-method>BASIC</auth-method>
-->
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/WEB-INF/content/Login.jsp</form-login-page>
<form-error-page>/WEB-INF/content/Login.jsp</form-error-page>
</form-login-config>
</login-config>
Obtain missing user credentials using FORM when navigating to protected urls
Figure 114.3. Example Protected Servlet Mapping
<servlet>
<servlet-name>Handler</servlet-name>
<servlet-class>info.ejava.examples.secureping.web.SecurePingHandlerServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Handler</servlet-name>
<url-pattern>/model/admin/handler</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Handler</servlet-name>
<url-pattern>/model/user/handler</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Handler</servlet-name>
<url-pattern>/model/known/handler</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Handler</servlet-name>
<url-pattern>/model/handler</url-pattern>
</servlet-mapping>
Servlet accessible via multiple URLs
The example creates a security hole on purpose to be able to demonstrate EJB security backs the WEB security. The servlet mapped above is accessible through multiple URLs -- each restricted differently but attempting to provide the same functionality. If you access the servlet through the anonymous URL you will encounter many access failures communicating with the EJB. If you access the servlet using the admin URL you will be able to access all functionality.
Figure 114.4. WEB-INF/jboss-web.xml: security-domain
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC
"-//JBoss//DTD Web Application 2.4//EN"
"http://www.jboss.org/j2ee/dtd/jboss-web_4_0.dtd">
<jboss-web>
<security-domain>other</security-domain>
</jboss-web>
Assigning web-tier to same security-domain as EJB tier
Figure 114.5.
<html>
<body>
<h1>Login Required</h1>
<form action="j_security_check" method="POST">
User Name:
<input type="text" size="20" name="j_username">
Password:
<input type="password" size="10" name="j_password">
<input type="submit" value="Login">
</table>
</form>
</body>
</html>
form action j_security_check
is a standard container action for login forms
j_username
input field name standard for username
j_password
input field name standard for password
uri-path constrained to use only HTTPS
EJB returning formatted string with caller role information
Caller authenticated at web-tier and passed to EJB
EJB and WAR using same security-domain
BASIC authentication requested for any missing credentials
URL selected also not enforcing confidential (i.e., HTTPS)
Authenticate user passed to EJB
EJB rejects unauthorized caller
Table of Contents
At the completion of this topic, the student shall
have more understanding of:
Decouple implementation concerns using interceptors
Implement method interpose thru interceptors
be able to:
Implement an interceptor
Register/activate interceptor using EJB techniques
Register/activate interceptor using CDI techniques
Interpose on contructor, EJB business and lifecycle methods
Interceptor lifecycle is same as the bean it interposes on (i.e., Stateless/Stateful)
EJB Interceptors may define AroundInvoke
callbacks for
business methods
MDB onMessage()
May throw runtime and application exceptions declared by business method
Interceptors may rollback the current transaction by
throwing a RuntimeException
calling setRollbackOnly()
on the EJBContext
Figure 115.1. Sample Business Method being Intercepted
@Stateless
public class ContactsEJB implements ContactsRemote {
@PersistenceContext(unitName="ejbinterceptor-contacts")
private EntityManager em;
@Override
public Contact createContact(Contact contact) throws InvalidParam {
logger.debug("createContact({})", contact);
em.persist(contact);
return contact;
}
Figure 115.2. Sample Business Method Interceptor
import javax.interceptor.AroundConstruct;
public class LifecycleInterceptor {
@Resource
private EJBContext ejbCtx;
@AroundInvoke
public Object invoke(InvocationContext ctx) throws Exception {
logger.debug("*** Business Method: {}, caller={}", ctx.getMethod(), ejbCtx.getCallerPrincipal().getName());
Object response = ctx.proceed();
logger.debug("*** Response Object: {}", response);
return response;
}
Figure 115.3. Sample Output
LifecycleInterceptor] (EJB default - 4) *** Business Method: public info.ejava.examples.ejb.interceptor.bo.Contact info.ejava.examples.ejb.interceptor.ejb.ContactsEJB.createContact(info.ejava.examples.ejb.interceptor.bo.Contact) throws info.ejava.examples.ejb.interceptor.ejb.InvalidParam, caller=anonymous ContactsEJB] (EJB default - 4) createContact(Contact [id=0, name=John Doe, normalizedName=null]) LifecycleInterceptor] (EJB default - 4) *** Response Object: Contact [id=1, name=John Doe, normalizedName=john doe]
Information about the call intercepted
Methods to control behavior of invocation chain
Figure 115.4. InvocationContext Interface
public interface javax.interceptor.InvocationContext {
public abstract java.lang.Object getTarget();
public abstract java.lang.Object getTimer();
public abstract java.lang.reflect.Method getMethod();
public abstract java.lang.reflect.Constructor<?> getConstructor();
public abstract java.lang.Object[] getParameters();
public abstract void setParameters(java.lang.Object[]);
public abstract java.util.Map<java.lang.String, java.lang.Object> getContextData();
public abstract java.lang.Object proceed() throws java.lang.Exception;
}
All EJB Interceptors may define standard lifecycle callbacks
AroundConstruct
PostConstruct
PreDestory
Stateful EJB Interceptors
may define state callbacks
PrePassivate
PostActivate
may use an extended persistence context
Figure 115.5. Lifecycle Callbacks
public class LifecycleInterceptor {
@AroundConstruct
public void ctor(InvocationContext ctx) {
...
}
@PostConstruct
public void init(InvocationContext ctx) {
...
}
@PreDestroy
public void destory(InvocationContext ctx) {
...
}
EJB Interceptors may define AroundTimeout
for timer methods
May call InvocationContext.getTimer()
to get timed out timer
May not throw application exceptions
Figure 115.6.
public class LifecycleInterceptor {
@AroundTimeout
public Object timeout(InvocationContext ctx) throws Exception {
logger.debug("*** Timeout: {}", ctx.getTimer());
Object response = ctx.proceed();
return response;
}
EJB Deployment descriptor (ejb-jar.xml)
@Interceptors annotation
CDI Deployment descriptor (beans.xml)
Figure 115.7. EJB Descriptor (ejb-jar.xml) Activation
<?xml version="1.0"?>
<ejb-jar ...
<assembly-descriptor>
<interceptor-binding>
<ejb-name>ContactsEJB</ejb-name>
<interceptor-class>info.ejava.examples.ejb.interceptor.interceptors.LifecycleInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar>
Decouples EJB and Interceptor classes
Assign to specific EJBs or globally
Assign to specific EJB methods or global to class
Can control order applied
Figure 115.8. @Interceptor(s) Annotation Activation
//way 1 - class level
@Stateless
@Interceptors({ //global to EJB class
PreNormizedInterceptor.class,
ContactsNormalizerInterceptor.class,
PostNormizedInterceptor.class,
})
public class ContactsEJB implements ContactsRemote {
//way 2 - method level
@Stateless
public class ContactsEJB implements ContactsRemote {
@Override
@Interceptors({ //specific to individual EJB method
PreNormizedInterceptor.class,
ContactsNormalizerInterceptor.class,
PostNormizedInterceptor.class,
})
public Contact createContact(Contact contact) throws InvalidParam {
@Annotation near-equivalent to EJB deployment descriptor
Couples EJB class to Interceptor class
Cannot assign globally to all EJBs
Can assign to specific EJB methods or global to class
Can control order applied
Extends Java Interceptors specified in EJB Spec
Not specific to EJBs -- any POJO
Annotation to identify type of interceptor required (e.g., @Transactional)
Figure 116.1. @InterceptorBinding Annotation
package info.ejava.examples.ejb.interceptor.interceptors;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;
@Inherited
@InterceptorBinding
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Validation {
}
The above @InterceptorBinding annotation will be used to designate the type of interceptor required/offered by/to a bean
Same as with EJB
Annotated with @javax.interceptor.Interceptor
Annotated with @InterceptorBinding annotation
Not activate by default
Figure 116.2. Interceptor Class
import javax.interceptor.Interceptor;
@Validation
@Interceptor
public class ContactsNormalizerInterceptor {
@AroundInvoke
public Object invoke(InvocationContext ctx) throws Exception {
Can be an EJB
Annotated with @InterceptorBinding annotation
Figure 116.3. CDI Bean Class
@Stateless
@Validation
public class ContactsEJB implements ContactsRemote {
@Override
public Contact createContact(Contact contact) throws InvalidParam {
Figure 116.4. CDI Descriptor (beans.xml) Activation
<?xml version="1.0" encoding="UTF-8"?>
<beans ...
<interceptors>
<class>info.ejava.examples.ejb.interceptor.interceptors.PreNormizedInterceptor</class>
<class>info.ejava.examples.ejb.interceptor.interceptors.ContactsNormalizerInterceptor</class>
<class>info.ejava.examples.ejb.interceptor.interceptors.PostNormizedInterceptor</class>
</interceptors>
</beans>
Activates Interceptor classes
Controls order applied
EJB Bean Class
Business method(s)
Normalization and validation concerns
Decouple data manipulation/validation from business/DAO logic
Figure 117.1. EJB Bean with Business Method
@Stateless
@Validation
public class ContactsEJB implements ContactsRemote {
@Override
public Contact createContact(Contact contact) throws InvalidParam {
logger.debug("createContact({})", contact);
em.persist(contact);
return contact;
}
Must correct prior to processing
Callers can provide names using different normalization
Name could be missing entirely
Declarative API to define validation rules
JPA validates Default.class
group by default on @PrePersist and @PreUpdate
Can define other custom lifecycle groups
Figure 117.3. Validation API
@Entity
public class Contact implements Serializable {
@Id @GeneratedValue
@Column(name="CONTACT_ID")
private int id;
@NotNull(groups={PreNormalizedCheck.class, PrePersistCheck.class})
@Size(min=1, max=32, groups={PostNormalizedCheck.class, PrePersistCheck.class})
@Pattern.List({
@Pattern(regexp="^[A-Za-z0-9-\\ ]+$", groups=PreNormalizedCheck.class),
@Pattern(regexp="^([A-Z][a-z0-9-]+\\ *)+$", groups=PostNormalizedCheck.class)
})
@Column(name="CONTACT_NAME", length=32, nullable=false)
private String name;
@Valid
@OneToMany(fetch=FetchType.LAZY, mappedBy="contact", orphanRemoval=true,
cascade={CascadeType.PERSIST, CascadeType.REMOVE})
private List<ContactInfo> contactInfo;
Figure 117.4. Custom Validation Groups
public interface PreNormalizedCheck {}
public interface PrePersistCheck {}
public interface PostNormalizedCheck {}
JPA will validate Default
group for pre-persist and pre-update by default when activated
Can override/specify pre-persist, pre-update, and pre-remove validation
Figure 117.5. Activate JPA Validation with Custom Group
@NotNull(groups={PrePersistCheck.class})
@Size(min=1, max=32, groups={PrePersistCheck.class})
@Column(name="CONTACT_NAME", length=32, nullable=false)
private String name;
<?xml version="1.0" encoding="UTF-8"?>
<persistence ...
<persistence-unit name="ejbinterceptor-contacts">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<validation-mode>CALLBACK</validation-mode>
<properties>
<property name="javax.persistence.validation.group.pre-persist"
value="info.ejava.examples.ejb.interceptor.bo.PrePersistCheck"/>
<property name="javax.persistence.validation.group.pre-update"
value="info.ejava.examples.ejb.interceptor.bo.PrePersistCheck"/>
</properties>
</persistence-unit>
</persistence>
Activating validation within JPA lifecycle
Overriding/specifying pre-persist and pre-update to use custom PrePersistCheck
Custom group to defining validation after normalization and prior to JPA
Figure 117.6. PostNormalizedCheck
@Size(min=1, max=32, groups={PostNormalizedCheck.class, PrePersistCheck.class})
@Pattern(regexp="^([A-Z][a-z0-9-]+\\ *)+$", groups=PostNormalizedCheck.class)
private String name;
We apply detailed validation checks during PostNormalizationCheck
Custom group to defining validation prior to normalization
Figure 117.7. PreNormalizedCheck
@NotNull(groups={PreNormalizedCheck.class, PrePersistCheck.class})
@Size(min=1, max=32, groups={PostNormalizedCheck.class, PrePersistCheck.class})
@Pattern.List({
@Pattern(regexp="^[A-Za-z0-9-\\ ]+$", groups=PreNormalizedCheck.class),
@Pattern(regexp="^([A-Z][a-z0-9-]+\\ *)+$", groups=PostNormalizedCheck.class)
})
@Column(name="CONTACT_NAME", length=32, nullable=false)
private String name;
We apply brief sanity check validation prior to normalization
Handle validating Validation API-marked POJOs as Interceptor
Figure 117.8. Validator Base Class
public class ValidatorInterceptor {
@Inject
private Validator validator;
private Class<?>[] groups;
protected ValidatorInterceptor() {}
public ValidatorInterceptor(Class<?>[] groups) { this.groups = groups; }
@AroundInvoke
public Object invoke(InvocationContext ctx) throws Exception {
logger.debug("validating method: {}, groups: {}", ctx.getMethod(), Arrays.toString(groups));
//validate each parameter
for (Object param: ctx.getParameters()) {
logger.debug("validating param: {}, groups: {}", param, Arrays.toString(groups));
Set<ConstraintViolation<Object>> violations = validator.validate(param, groups);
if (!violations.isEmpty()) {
Exception ex = new InvalidParam(param.toString(), getErrors(violations));
logger.debug("aborting call, found error: {}", ex.getMessage());
throw ex;
}
}
return ctx.proceed();
}
private List<String> getErrors(Set<ConstraintViolation<Object>> violations) {
List<String> errors = new ArrayList<String>(violations.size());
for (ConstraintViolation<Object> v: violations) {
errors.add(v.toString());
}
return errors;
}
}
Grabs parameters from InvocationContext
Validates each of them against assigned validation group
Figure 117.9. Pre/PostNormalized Concrete Interceptors
@Validation
@Interceptor
public class PreNormizedInterceptor extends ValidatorInterceptor {
public PreNormizedInterceptor() {super(new Class<?>[]{PreNormalizedCheck.class});}
}
@Validation
@Interceptor
public class PostNormizedInterceptor extends ValidatorInterceptor {
public PostNormizedInterceptor() { super(new Class<?>[]{PostNormalizedCheck.class}); }
}
Define @InterceptorBinding (@Validation) and @Interceptor role for CDI
Define validation group(s) for base class
Intercepts parameters going into business methods
Applies normalization rules to parameter values
Figure 117.10. ContactsNormalizerInterceptor
@Validation
@Interceptor
public class ContactsNormalizerInterceptor {
@AroundInvoke
public Object invoke(InvocationContext ctx) throws Exception {
logger.debug("intercepting: {}", ctx.getMethod());
for (Object param: ctx.getParameters()) {
if (param instanceof Contact) {
logger.debug("normalizing: {}", param);
contactNormalizer.normalize((Contact) param);
logger.debug("normalized: {}", param);
} else if (param instanceof PostalInfo) {
logger.debug("normalizing: {}", param);
contactNormalizer.normalize((PostalInfo) param);
logger.debug("normalized: {}", param);
} else if (param instanceof PhoneInfo) {
logger.debug("normalizing: {}", param);
contactNormalizer.normalize((PhoneInfo) param);
logger.debug("normalized: {}", param);
}
}
return ctx.proceed();
}
Defines @InterceptorBinding (@Validator) and @Interceptor role
Defines @AroundInvoke for business method
Subjects each parameter to normalization rules
Details of normalization omitted (initial caps for each name/word)
Figure 117.11. Bean Class
@Stateless
@Validation
public class ContactsEJB implements ContactsRemote {
@Override
public Contact createContact(Contact contact) throws InvalidParam {
logger.debug("createContact({})", contact);
em.persist(contact);
return contact;
}
Applies @interceptorBinding annotation (@Validation)
Supplies business method
One of several techniques possible
Figure 117.12. CDI Activation (bean.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans ...
<interceptors>
<class>info.ejava.examples.ejb.interceptor.interceptors.PreNormizedInterceptor</class>
<class>info.ejava.examples.ejb.interceptor.interceptors.ContactsNormalizerInterceptor</class>
<class>info.ejava.examples.ejb.interceptor.interceptors.PostNormizedInterceptor</class>
</interceptors>
</beans>
Control ordering of interceptors
Figure 117.13. Example Output
ValidatorInterceptor] validating param: Contact [id=0, name=john Doe], groups: [interface info.ejava.examples.ejb.interceptor.bo.PreNormalizedCheck] ContactsNormalizerInterceptor] normalizing: Contact [id=0, name=john Doe] ContactsNormalizerInterceptor] normalized: Contact [id=0, name=John Doe] ValidatorInterceptor] validating param: Contact [id=0, name=John Doe], groups: [interface info.ejava.examples.ejb.interceptor.bo.PostNormalizedCheck] ContactsEJB] createContact(Contact [id=0, name=John Doe]) call next value for hibernate_sequence insert into EJBINTERCEPTOR_CONTACT (CONTACT_NAME, NORMALIZED_NAME, CONTACT_ID) values (?, ?, ?)
Input hand a correctable, but invalid name
Name passed initial pre-normalization validation
Name corrected by normalization
Name passed post-normalization validation
Name passed pre-persist validation
Contac persisted