Table of Contents
Javaee tutorial 1: An Auction Service
Building The Persistence Layer with JPA2 & Bean Validation
These are the packages
.controller
contains the backing beans for #{newMember} and #{memberRegistration} in the JSF page index.xhtml
Manager: a CDI Bean which acts as a glue between the JSF view and the EJBs
.data
contains a class which uses @Produces and @Named to return the list of members for index.xhtml
Producer: a CDI Producer Bean used as a factory of SimpleProperty for the JSF view
RepositoryManager: a CDI Bean used to perform queries and populate SimpleProperty objects
.model
contains the JPA entity class, a POJO annotated with @Entity, annotated with Bean Validation (JSR 303) constraints
Entity Bean: an Entity Bean for storing our Properties
.rest
contains the JAX-RS endpoints, POJOs annotated with @Path
.service
handles the registration transaction for new members
ServiceBean: a Stateless EJB which will carry on transactional jobs (Inserts, Deletes)
.util
contains Resources.java which sets up an alias for @PersistenceContext to be injectable via @Inject
GenericProducer: a CDI Producer Bean used to instantiate container resources (The Entity Manager)
Entity
Auction Item ItemAttribute Member (User) UserOauth
Database design & relationships
Design Description
A User chooses Auction to place his Item, which has multiple ItemAttribute. He become the Owner of the Item. An Item represents a resource which is located at an url (attribute). The Auction is published on social network depending on User's Oauth providers. An Auction last for a certain time period and has certain form expressed by AuctionAlgorithm. Other users place Bids for the Item and become Bidder.
Extensions ideas:
An Auction may be made for bundle of Items?.
ERD entity relationship diagram
TBD
Media Item
Storing large binary objects, such as images or videos in the database isn’t advisable (as it can lead to performance issues), and playback of videos can also be tricky, as it depends on browser capabilities. For TicketMonster, we decided to make use of existing services to host images and videos, such as YouTube or Flickr. All we store in the database is the URL the application should use to access the media item, and the type of the media item (note that the URL forms a media items natural identifier). We need to know the type of the media item in order to render the media correctly in the view layer.
In order for a view layer to correctly render the media item (e.g. display an image, embed a media player), it’s likely that special code has had to have been added. For this reason we represent the types of media that TicketMonster understands as a closed set, unmodifiable at runtime. An enum is perfect for this!
Luckily, JPA has native support for enums, all we need to do is add the @Enumerated annotation:
public enum ResourceType { /** * The types of media the application can currently handle. Right now, it can only handle images. We plan to add support for * streamed videos in the next development round. */ IMAGE("Image", true); /** * A human readable description of the media type. */ private final String description; /** * A boolean flag indicating whether the media type can be cached. */ private final boolean cacheable; /* Boilerplate constructor and getters */ private ResourceType(String description, boolean cacheable) { this.description = description; this.cacheable = cacheable; } public String getDescription() { return description; } public boolean isCacheable() { return cacheable; } }
Enum field.
@Entity public class MediaItem implements Serializable { /* Declaration of fields */ /** * The synthetic id of the object. */ @Id @GeneratedValue(strategy = IDENTITY) private Long id; /** * <p> * The type of the media, required to render the media item correctly. * </p> * * <p> * The media type is a <em>closed set</em> - as each different type of media requires support coded into the view layers, it * cannot be expanded upon without rebuilding the application. It is therefore represented by an enumeration. We instruct * JPA to store the enum value using it's String representation, so that we can later reorder the enum members, without * changing the data. Of course, this does mean we can't change the names of media items once the app is put into * production. * </p> */ @Enumerated(STRING) private MediaType mediaType;
Auction
An Item can be auctioned many times in many Auctions. We’ll model the relationship as many-to-one - many Auctions can reference the same media item. Creating a many-to-one relationship is very easy in JPA. Just add the ManyToOne annotation to the field. JPA will take care of the rest. Here’s the property for Auction:
/**
* <p>
* A media item, such as an image, which can be used to entice a browser to book a ticket.
* </p>
*
* <p>
* Media items can be shared between auctions, so this is modeled as a \<code\>@ManyToOne\</code\> relationship.
* </p>
*
* <p>
* Adding a media item is optional, and the view layer will adapt if none is provided.
* </p>
*
*/
ManyToOne
private Item auctionItem;
There is no need for a media item to know who references it (in fact, this would be a poor design, as it would reduce the reusability of MediaItem), so we can leave this as a uni-directional relationship.
An Auction will also have an AuctionAlgorithm. Once again, many auctions can use the same Auction Algorithm , and there is no need for an algorithm to know what auctions are using it. To add this relationship, we add the auctionAlgorithm property, and annotate it with ManyToOne, just as we did for auctionItem.
Item to ItemAttribute ManyToOne
/**
* <p>
* The set of item attributes of this item.
* </p>
*
* <p>
* The <code>@OneToMany<code> JPA mapping establishes this relationship. Collection members
* are fetched eagerly, so that they can be accessed even after the entity has become detached.
* This relationship is bi-directional (an attribute knows which item it is part of), and the code>mappedBy/code>
* attribute establishes this.
* </p>
*
* <p>
* The set of tickets is eagerly loaded because FIXME . All operations are cascaded to each ticket, so for example if a
* booking is removed, then all associated tickets will be removed.
* </p>
*
* <p>
* This relationship is uni-directional, so we need to inform JPA to create a foreign key mapping. The foreign key mapping
* is not visible in the {@link ItemAttribute} entity despite being present in the database.
* </p>
*
*/
@OneToMany(fetch = EAGER, cascade = ALL)
@JoinColumn @NotEmpty
@Valid
private Set<ItemAttribute> attributes = new HashSet<ItemAttribute>();
As the relationship is bi-directional, we specify the mappedBy attribute on the @OneToMany annotation, which informs JPA to create a bi-directional relationship. The value of the attribute is name of property which forms the other side of the relationship - in this case, not unsuprisingly item!
As Show is the owner of ItemAttribute (and without an item, an attribute cannot exist), we add the cascade = ALL attribute to the @OneToMany annotation. As a result, any persistence operation that occurs on an item, will be propagated to it’s attributes. For example, if an item is removed, any associated attributes will be removed as well.
When retrieving a show, we will also retrieve its associated performances by adding the fetch = EAGER attribute to the @OneToMany annotation. This is a design decision which required careful consideration. In general, you should favour the default lazy initialization of collections: their content should be accessible on demand. However, in this case we intend to marshal the contents of the collection and pass it across the wire in the JAX-RS layer, after the entity has become detached, and cannot initialize its members on demand.
We add the @JoinColumn annotation, which sets up a foreign key in Ticket, but doesn’t expose the booking on Ticket. This prevents the use of messy mapping tables, whilst preserving the integrity of the entity model.
A ticket embeds the seat allocated, and contains a reference to the category under which it was sold. It also contains the price at which it was sold.
Addresses: Embeddable
In creating this entity, we’ve followed all the design and implementation decisions previously discussed, with one new concept. Rather than add the properties for street, city, postal code etc. to this object, we’ve extracted them into the Address object, and included it in the Venue object using composition. This would allow us to reuse the Address object in other places (such as a customer’s address).
A RDBMS doesn’t have a similar concept to composition, so we need to choose whether to represent the address as a separate entity, and create a relationship between the venue and the address, or whether to map the properties from Address to the table for the owning entity, in this case Venue. It doesn’t make much sense for an address to be a full entity - we’re not going to want to run queries against the address in isolation, nor do we want to be able to delete or update an address in isolation - in essence, the address doesn’t have a standalone identity outside of the object into which it is composed.
To embed the Address into Venue we add the @Embeddable annotation to the Address class. However, unlike a full entity, there is no need to add an identifier. Here’s the source for Address:
src/main/java/org/jboss/jdf/example/ticketmonster/model/Address.java
/** * <p> * A reusable representation of an address. * </p> * * <p> * Addresses are used in many places in an application, so to observe the DRY principle, we model Address as an embeddable * entity. An embeddable entity appears as a child in the object model, but no relationship is established in the RDBMS.. * </p> */ @Embeddable public class Address { /* Declaration of fields */ private String street; private String city; private String country; /* Declaration of boilerplate getters and setters */ public String getStreet() { return street; } public void setStreet(String street) { this.street = street; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } /* toString(), equals() and hashCode() for Address, using the natural identity of the object */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Address address = (Address) o; if (city != null ? !city.equals(address.city) : address.city != null) return false; if (country != null ? !country.equals(address.country) : address.country != null) return false; if (street != null ? !street.equals(address.street) : address.street != null) return false; return true; } @Override public int hashCode() { int result = street != null ? street.hashCode() : 0; result = 31 * result + (city != null ? city.hashCode() : 0); result = 31 * result + (country != null ? country.hashCode() : 0); return result; } @Override public String toString() { return street + ", " + city + ", " + country; } }
User to Oauth: OneToMany bi-directional
User
/** * <p> * A set of user's open authentications * </p> * * <p> * The code>@OneToManycode> JPA mapping establishes this relationship. * Collection members are fetched eagerly, so that they can be accessed even after the * entity has become detached. This relationship is bi-directional (an oauth knows which * user it belongs to), and the code>mappedBy/code> attribute establishes this. We * cascade all persistence operations to the set of oauths, so, for example if a user * is removed, then all of it's oauth will also be removed. * </p> */ @OneToMany(cascade = ALL, fetch = EAGER, mappedBy = "user") private Set<UserOauth> oauths = new HashSet<UserOauth>();
Oauth
/** * <p> * The user of which this is an oauth. The code>@ManyToOnecode> JPA mapping establishes this relationship. * </p> * * <p> * The user to which this a oauth belongs, and the Bean Validation constraint code>@NotNull/code> enforces * this. * </p> */ @ManyToOne @NotNull private User user;
ManyToMany Bi-directional
Defines a many-valued association with many-to-many multiplicity.
Every many-to-many association has two sides, the owning side and the non-owning, or inverse, side. The join table is specified on the owning side. If the association is bidirectional, either side may be designated as the owning side. If the relationship is bidirectional, the non-owning side must use the mappedBy element of the ManyToMany annotation to specify the relationship field or property of the owning side.
The join table for the relationship, if not defaulted, is specified on the owning side.
The ManyToMany annotation may be used within an embeddable class contained within an entity class to specify a relationship to a collection of entities. If the relationship is bidirectional and the entity containing the embeddable class is the owner of the relationship, the non-owning side must use the mappedBy element of the ManyToMany annotation to specify the relationship field or property of the embeddable class. The dot (“.”) notation syntax must be used in the mappedBy element to indicate the relationship attribute within the embedded attribute. The value of each identifier used with the dot notation is the name of the respective embedded field or property.
Example 1: // In Customer class: @ManyToMany @JoinTable(name="CUST_PHONES") public Set<PhoneNumber> getPhones() { return phones; } // In PhoneNumber class: @ManyToMany(mappedBy="phones") public Set<Customer> getCustomers() { return customers; } Example 2: // In Customer class: @ManyToMany(targetEntity=com.example.PhoneNumber.class) public Set getPhones() { return phones; } // In PhoneNumber class: @ManyToMany(targetEntity=com.example.Customer.class, mappedBy="phones") public Set getCustomers() { return customers; } Example 3: // In Customer class: @ManyToMany @JoinTable(name="CUST_PHONE", joinColumns= @JoinColumn(name="CUST_ID", referencedColumnName="ID"), inverseJoinColumns= @JoinColumn(name="PHONE_ID", referencedColumnName="ID") ) public Set<PhoneNumber> getPhones() { return phones; } // In PhoneNumberClass: @ManyToMany(mappedBy="phones") public Set<Customer> getCustomers() { return customers; }
http://www.cs.hs-rm.de/~knauf/JavaEE6/kuchenzutatnm/index.html
Auction.java - referee
@ManyToMany( mappedBy="auctions", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY) private Set<Item> items = new HashSet<Item>();
Item.java - Relationship owner
/** * <p> * The @JoinTable annotation is used to specify a database table that will associate auction IDs with item IDs. * The entity that specifies the @JoinTable is the owner of the relationship, so the Item entity is the owner of * the relationship with the Auction entity. Because we uses automatic table creation at deployment time FIXME, * the container will create a join table named AUCTIONS_ITEMS. * Auction is the inverse, or nonowning, side of the relationship with Team. As one-to-one and many-to-one * relationships, the nonowning side is marked by the mappedBy element in the relationship annotation. Because * the relationship between Item and Auction is bidirectional, the choice of which entity is the owner of the * relationship is arbitrary. * </p> * * <p> * In Auction.java, the @ManyToMany annotation decorates the items attribute * </p> * * <p> * cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH} * Ein kaskadierendes Löschen soll verboten sein, denn eine Zutat kann auch ohne Kuchen existieren. Deshalb kaskadieren wir alle Operationen außer dem Löschen weiter (Merge, Persist und Refresh) * </p> * * <p> * FetchType.LAZY * Den FetchType habe ich auf "Lazy" gesetzt, denn wenn wir den auf beiden Seite der Relation auf "Eager" setzen würden, dann würde beim Abrufen eines Kuchens die Liste seiner Zutaten geholt, pro Zutat wiederum die Liste der Kuchen für die die Zutat verwendet wird, und für jeden dieser Kuchen wiederum die Zutaten. Dadurch würde im schlimmsten Fall die gesamte Datenbank bei einem Zugriff eingelesen werden. * </p> */ // targetEntity=de.dailab.socialcloud.auctionservice.webapp.AuctionServiceWebapp.model.Auction.class, @ManyToMany( cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.LAZY) @JoinTable (name="AUCTIONS_ITEMS", joinColumns={@JoinColumn(name="ITEM_ID", referencedColumnName="ID") }, inverseJoinColumns={@JoinColumn(name="AUCTION_ID", referencedColumnName="ID") }) private Set<Auction> auctions = new HashSet<Auction>();
ManyToMany Uni-directional
http://uaihebert.com/jpa-manytomany-unidirectional-and-bidirectional/
Remove the annotation with mappedBy from the other Entity.
Jboss Forge
- Quickstart: http://forge.jboss.org/docs/using/samples.html
Create a new JPA @Entity class
new-project —named example —topLevelPackage com.example persistence setup —provider HIBERNATE —container JBOSS_AS7 validation setup --provider HIBERNATE_VALIDATOR entity —named SampleEntity field string —named sampleField field temporal —named createdOn —type TIMESTAMP ls
Add an @N-To-N relationship between two @Entity classes
new-project —named example —topLevelPackage com.example persistence setup —provider HIBERNATE —container JBOSS_AS7 entity —named Hurricane entity —named Continent field manyToMany —named hurricanes —fieldType com.example.domain.Hurricane —inverseFieldName continents ls
Create a REST endpoint from @Entity
new-project —named example —topLevelPackage com.example persistence setup —provider HIBERNATE —container JBOSS_AS7 rest setup entity —named SampleEntity field string —named sampleField rest endpoint-from-entity
Entity
entity --named Book field string --named title field temporal --type DATE --named publicationDate field int --named pages
entity --named Author field string --named lastName field string --named firstName
OneToMany
field oneToMany --named books --fieldType blog.thoughts.on.java.forge.model.Book.java --inverseFieldName author
Connecting to the database
In this example, we are using the in-memory H2 database, which is very easy to set up on JBoss AS. JBoss AS allows you deploy a datasource inside your application’s WEB-INF directory. You can locate the source in src/main/webapp/WEB-INF/ticket-monster-ds.xml: src/main/webapp/WEB-INF/ticket-monster-ds.xml
<datasources xmlns="http://www.jboss.org/ironjacamar/schema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.jboss.org/ironjacamar/schema http://docs.jboss.org/ironjacamar/schema/datasources_1_0.xsd"> <!-- The datasource is bound into JNDI at this location. We reference this in META-INF/persistence.xml --> <datasource jndi-name="java:jboss/datasources/ticket-monsterDS" pool-name="ticket-monster" enabled="true" use-java-context="true"> <connection-url> jdbc:h2:mem:ticket-monster;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1 </connection-url> <driver>h2</driver> <security> <user-name>sa</user-name> <password>sa</password> </security> </datasource> </datasources>
The datasource configures an H2 in-memory database, called ticket-monster, and registers a datasource in JNDI at the address:
java:jboss/datasources/ticket-monsterDS
Now we need to configure JPA to use the datasource. This is done in src/main/resources/META-INF/persistence.xml: src/main/resources/persistence.xml
<persistence version="2.0" 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"> <persistence-unit name="primary"> <!-- If you are running in a production environment, add a managed data source, this example data source is just for development and testing! --> <!-- The datasource is deployed as WEB-INF/ticket-monster-ds.xml, you can find it in the source at src/main/webapp/WEB-INF/ticket-monster-ds.xml --> <jta-data-source>java:jboss/datasources/ticket-monsterDS</jta-data-source> <properties> <!-- Properties for Hibernate --> <property name="hibernate.hbm2ddl.auto" value="create-drop" /> <property name="hibernate.show_sql" value="false" /> </properties> </persistence-unit> </persistence>
As our application has only one datasource, and hence one persistence unit, the name given to the persistence unit doesn’t really matter. We call ours primary, but you can change this as you like. We tell JPA about the datasource bound in JNDI.
Hibernate includes the ability to generate tables from entities, which here we have configured. We don’t recommend using this outside of development. Updates to databases in production should be done manually.
Populating test data
Just add a file called import.sql onto the classpath of your application (we keep it in src/main/resources/import.sql). In it, we just write standard sql statements suitable for the database we are using. To do this, you need to know the generated column and table names for your entities. The best way to work these out is to look at the h2console.
The h2console is included in the JBoss AS quickstarts, along with instructions on how to use it. For more information, see http://jboss.org/jdf/quickstarts/jboss-as-quickstart/h2-console/