Vaadin JPAContainer offers a highly flexible API that makes things easy in
simple cases while allowing extensive flexibility in demanding cases. To begin
with, it is a Container
, as described in Section 9.4, “Collecting Items in Containers”.
In this section, we look how to create and use
JPAContainer
instances. We assume that you have defined
a domain model with JPA annotations, as described in the previous section.
The JPAContainerFactory
is the easy way to create
JPAContainer
s. It provides a set of
make...() factory methods for most cases that you
will likely meet. Each factory method uses a different type of entity
provider, which are described in Section 18.5, “Entity Providers”.
The factory methods take the class type of the entity class as the first
parameter. The second parameter is either a persistence unit name
(persistence context) or an EntityManager
instance.
// Create a persistent person container JPAContainer<Person> persons = JPAContainerFactory.make(Person.class, "book-examples"); // You can add entities to the container as well persons.addEntity(new Person("Marie-Louise Meilleur", 117)); // Set up sorting if the natural order is not appropriate persons.sort(new String[]{"age", "name"}, new boolean[]{false, false}); // Bind it to a component Table personTable = new Table("The Persistent People", persons); personTable.setVisibleColumns(new String[]{"id","name","age"}); layout.addComponent(personTable);
It's that easy. In fact, if you run the above code multiple times, you'll
be annoyed by getting a new set of persons for each run - that's how
persistent the container is. The basic make()
uses a CachedMutableLocalEntityProvider
, which
allows modifying the container and its entities, as we do above by adding
new entities.
When using just the persistence unit name, the factory creates an instance
of EntityManagerFactory
for the persistence unit
and uses it to build entity managers. You can also create the entity
managers yourself, as described later.
The entity providers associated with the different factory methods are as follows:
Table 18.1. JPAContainerFactory
Methods
make() | CachingMutableLocalEntityProvider |
makeReadOnly() | CachingLocalEntityProvider |
makeBatchable() | BatchableLocalEntityProvider |
makeNonCached() | MutableLocalEntityProvider |
makeNonCachedReadOnly() | LocalEntityProvider |
JPAContainerFactory
holds a cache of entity manager
factories for the different persistence units, making sure that any entity
manager factory is created only once, as it is a heavy operation. You can
access the cache to get a new entity manager with the
createEntityManagerForPersistenceUnit()
method.
// Get an entity manager EntityManager em = JPAContainerFactory. createEntityManagerForPersistenceUnit("book-examples"); // Do a query em.getTransaction().begin(); em.createQuery("DELETE FROM Person p").executeUpdate(); em.persist(new Person("Jeanne Calment", 122)); em.persist(new Person("Sarah Knauss", 119)); em.persist(new Person("Lucy Hannah", 117)); em.getTransaction().commit(); ...
Notice that if you use update the persistent data with an entity manager
outside a JPAContainer
bound to the data, you need
to refresh the container as described in Section 18.4.2, “Creating and Accessing Entities”.
While it is normally easiest to use a
JPAContainerFactory
to create
JPAContainer
instances, you may need to create
them manually. It is necessary, for example, when you need to use a
custom entity provider or extend JPAContainer
.
First, we need to create an entity manager and then the entity
provider, which we bind to a JPAContainer
.
// We need a factory to create entity manager EntityManagerFactory emf = Persistence.createEntityManagerFactory("book-examples"); // We need an entity manager to create entity provider EntityManager em = emf.createEntityManager(); // We need an entity provider to create a container CachingMutableLocalEntityProvider<Person> entityProvider = new CachingMutableLocalEntityProvider<Person>(Person.class, em); // And there we have it JPAContainer<Person> persons = new JPAContainer<Person> (Person.class); persons.setEntityProvider(entityProvider);
You could save the first step by asking the entity manager from the
JPAContainerFactory
.
JPAContainer integrates with the JPA entity manager, which you would
normally use to create and access entities with JPA. You can use the
entity manager for any purposes you may have, and then
JPAContainer
to bind entities to user interface
components such as Table
,
Tree
, any selection components, or a
Form
.
You can add new entities to a JPAContainer
with the
addEntity()
method. It returns the item ID of the
new entity.
Country france = new Country("France"); Object itemId = countries.addEntity(france);
The item ID used by JPAContainer
is the value of
the ID property (column) defined with the @Id
annotation. In our Country
entity, it would have
Long
type. It is generated by the entity manager
when the entity is persisted and set with the setter for the ID proeprty.
Notice that the addEntity()
method does
not attach the entity instance given as the
parameter. Instead, it creates a new instance. If you need to use the
entity for some purpose, you need to get the actual managed entity from
the container. You can get it with the item ID returned by
addEntity()
.
// Create a new entity and add it to a container Country france = new Country("France"); Object itemId = countries.addEntity(france); // Get the managed entity france = countries.getItem(itemId).getEntity(); // Use the managed entity in entity references persons.addEntity(new Person("Jeanne Calment", 122, france));
The getItem()
method is defined in the normal
Vaadin Container
interface. It returns
an EntityItem
, which is a wrapper over the
actual entity object. You can get the entity object with
getEntity()
.
An EntityItem
can have a number of states:
persistent, modified, dirty, and deleted. The dirty and deleted states
are meaningful when using container buffering,
while the modified state is meaningful when using item
buffering. Both levels of buffering can be used together -
user input is first written to the item buffer, then to the entity
instance, and finally to the database.
The isPersistent()
method tells if the item
is actually persistent, that is, fetched from a persistent storage, or
if it is just a transient entity created and buffered by the container.
The isModified()
method checks whether the
EntityItem
has changes that are not yet
committed to the entity instance. It is only relevant if the item
buffering is enabled with
setWriteThrough(false)
for the item.
The isDirty()
method checks whether the
entity object has been modified after it was fetched from the entity
provider. The dirty state is possible only when buffering is enabled
for the container.
The isDeleted()
method checks whether the
item has been marked for deletion with
removeItem()
in a buffered container.
In cases where you change JPAContainer
items
outside the container, for example by through an
EntityManager
, or when they change in the
database, you need to refresh the container.
The EntityContainer
interface
implemented by JPAContainer
provides two
methods to refresh a container. The refresh()
discards all container caches and buffers and refreshes all loaded
items in the container. All changes made to items provided by the
container are discarded. The refreshItem()
refreshes a single item.
If you have a one-to-one or many-to-one relationship, you can define the
properties of the referenced entity as nested in a
JPAContainer
. This way, you can access the
properties directly through the container of the first entity type as if
they were its properties. The interface is the same as with
BeanContainer
described in Section 9.4.1, “BeanContainer
”. You just need to add each
nested property with addNestedContainerProperty()
using dot-separated path to the property.
// Have a persistent container JPAContainer<Person> persons = JPAContainerFactory.make(Person.class, "book-examples"); // Add a nested property to a many-to-one property persons.addNestedContainerProperty("country.name"); // Show the persons in a table, except the "country" column, // which is an object - show the nested property instead Table personTable = new Table("The Persistent People", persons); personTable.setVisibleColumns(new String[]{"name","age", "country.name"}); // Have a nicer caption for the country.name column personTable.setColumnHeader("country.name", "Nationality");
The result is shown in Figure 18.5, “Nested Properties”. Notice that the
country
property in the container remains after adding
the nested property, so we had to make that column
invisible. Alternatively, we could have redefined the
toString()
method in the country object to show
the name instead of an object reference.
You can use the *
wildcard to add all properties in a
nested item, for example, "country.*
".
JPAContainer
implements the
Container.Hierarchical
interface and can be
bound to hierarchical components such as a Tree
or
TreeTable
. The feature requires that the hierarchy
is represented with a parent property that refers to
the parent item. At database level, this would be a column with IDs.
The representation would be as follows:
@Entity public class CelestialBody implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; @ManyToOne private CelestialBody parent; ... } ... // Create some entities CelestialBody sun = new CelestialBody("The Sun", null); CelestialBody mercury = new CelestialBody("Mercury", sun); CelestialBody venus = new CelestialBody("Venus", sun); CelestialBody earth = new CelestialBody("Earth", sun); CelestialBody moon = new CelestialBody("The Moon", earth); CelestialBody mars = new CelestialBody("Mars", sun); ...
You set up a JPAContainer
to have hierarchy by
calling setParentProperty()
with the name of the
property that refers to the parent. Coincidentally, it is named
"parent
" in the example:
// Create the container JPAContainer<CelestialBody> bodies = JPAContainerFactory.make(CelestialBody.class, "my-unit"); // Set it up for hierarchical representation bodies.setParentProperty("parent"); // Bind it to a hierarhical component Tree tree = new Tree("Celestial Bodies", bodies); tree.setItemCaptionMode(Tree.ITEM_CAPTION_MODE_PROPERTY); tree.setItemCaptionPropertyId("name");
You can use the rootItemIds()
to acquire the item
IDs of the root elements with no parent.
// Expand the tree for (Object rootId: bodies.rootItemIds()) tree.expandItemsRecursively(rootId);
Using setParent()
in the container to define
parenthood is not supported.
Also, the current implementation does not support
setChildrenAllowed(), which controls whether the
user can expand a node by clicking a toggle. The toggle is by default
visible for all nodes, even if they have no children. The method is
not supported because it would require storing the information outside
the entities. You can override
areChildrenAllowed()
to implement the
functionality using a custom logic.
// Customize JPAContainer to define the logic for // displaying the node expansion indicator JPAContainer<CelestialBody> bodies = new JPAContainer<CelestialBody>(CelestialBody.class) { @Override public boolean areChildrenAllowed(Object itemId) { // Some simple logic return getChildren(itemId).size() > 0; } }; bodies.setEntityProvider( new CachingLocalEntityProvider<CelestialBody>( CelestialBody.class, em));