[RSS]

Best Practices with JPA and Beans Binding

by Jan Stola.

NetBeans GUI builder (since NB 6.0 release) provides support for JSR 295 Beans Binding. Synchronization of the application's data model with the information displayed in UI components is usually a daunting task when coding a new GUI. Beans Binding simplifies this process by providing an easy way to connect two properties. Hence if your data model is based on JavaBeans specification, e.g. if it has get<Attribute> and set<Attribute> methods for all its important attributes, then it is simple to use NetBeans binding support to bind your model to Swing components. You may argue that many Swing components are not beans with nice attributes/properties (for example the model property of JTable is quite complex), but you don't need to worry about that. Beans Binding improves the behaviour of some Swing components (e.g. adds property change notification when text of JTextComponent is modified) and adds few handy artificial properties where standard components lack them - like elements property of JTable that allows you to set the table model in terms of collection of JavaBeans.

The data processed by the application are stored in a relational database frequently. So, you need to load the data somehow and create bean wrappers to be able to use beans binding. Java Persistence API (JPA) is ideal for this purpose. JPA is a standard object-relational mapping for Java e.g. it is a technology that does exactly what we need, it maps Java objects to corresponding data in the database. JPA started in, but is not specific to J2EE world. There are already several implementations available for standalone applications as well. The NetBeans distribution includes one of them: TopLink - JPA implementation by Oracle.

The easiest way to start with NetBeans beans/data binding support is to create a Java Desktop Application/Database Application Shell. You can find it in the New Project ... wizard. This wizard creates a skeleton of simple CRUD (create, read, update and delete) database application with master/detail view. In the sequel we assume that we have an application called CustomerApp created from CUSTOMER master table and PURCHASE_ORDER detail table.

Let's examine the generated application. The most important class is CustomerView that contains the GUI of the application's main window. If you select the detailTable then you can see the properties of this table in Properties window. The Binding section of this window contains information about bound properties. You can, for example, see that the elements property of detailTable is bound to ${selectedElement.purchaseOrderCollection} expression of masterTable. The binding source (e.g. masterTable in this case) represents a context in which the binding expression is evaluated whenever sychronization is required (for example when selection in the masterTable changes). The binding expressions are usually simple properties (${foreground}) or property paths (${selectedElement.address.street}), but they can be arbitrary EL expressions known from JSP (${firstName} ${lastName} or ${selectedElement != null}). The Advanced tab of the binding customizer allows you to affect additional binding aspects: coverter (when the types of bound properties do not match), validator (to avoid corruption of the source of the data) etc.

Besides the main view (CustomerView.java), about dialog (CustomerAboutBox.java) and the application main class (CustomerApp.java) the project contains two data access classes (called entities): Customer.java and PurchaseOrder.java. The annotations in these classes help JPA to map the data from DB tables and columns to the corresponding entities and fields/properties in the entities.

The entities for our application were generated when the project was created, but you can generate additional entities for other DB tables using Entity Classes from Database wizard (accessible through Persistence category of New File dialog).

If the DB tables that we would like to access have some foreign keys e.g. if the corresponding entities have some relations (many-one, one-many etc.) then it is important to generate all the entities together to make sure these relations are correctly expressed in generated classes. For example, if you generate PurchaseOrder entity without Customer entity then the customerId field that represents the relation between these entities will have type int (instead of Customer) or will not be generated at all.

When the entity classes are used with beans binding then it is necessary to add property change support into them. Firstly, the entity should have addPropertyChangeListener() and removePropertyChangeListener() methods for (un)-registration of property change listeners. Secondly, every setter (set<Property>() method) should notify all registered listeners about the preformed change. The GUI builder attempts to add this support for you whenever it is needed, but you should not depend on it. It is sometimes not possible to determine that automatically. Therefore double check your property change support whenever you encounter a problem in sychronization done by beans binding.

By default, it is necessary to set (value of) field/property that correspond to the primary key before the data can be stored into the database. This may be fine if your primary key makes sense in real world (e.g. it is a social number of the customer), but it is annoying if the key should be just a unique number (possibly generated from some sequence). If your DB provides this support then you can utilize it in your entities as well. It is sufficient to add something like @GeneratedValue(strategy=GenerationType.IDENTITY) just below @Id annotation next to your primary key.

Embedded Database

Usually the application's database is located on some DB server that is accessible over network, but there are some cases when it is preferable to have the DB locally for each installation of the application - for example when the application manages the set of your music records or books. It is ideal to use embedded DB in this case. You can use for example free lightweight embedded DB provided by Derby/Java DB. The usage of this DB is similar to the usage of network DBs. You just have to use org.apache.derby.jdbc.EmbeddedDriver instead of ClientDriver. Of course, you don't need to specify hostname and port in DB url. It is sufficient to include the DB name e.g. jdbc:derby:mydb. Derby will look for DB called mydb in a working directory.

If you want to create the DB in case it doesn't exist already (for example, during the first start after installation) then you can do that by setting create parameter in DB URL to true e.g. jdbc:derby:mydb;create=true Don't be confused if you don't see the data (or even tables) when you run your application in spite of the fact that you can see them in Services window of NetBeans. Recall that Derby is looking for the DB in the working directory and the working directory of NetBeans is different from the working directory of your project.

The create parameter ensures creation of the DB only. It doesn't help you with the creation of DB tables. Hapilly, you can utilize one nice feature of JPA. JPA is able to create missing DB tables that correspond to entity classes in your application. Open persistence.xml file in META-INF directory of your project. This file contains configuration data used by your JPA provider. If you change Table Generation Strategy to Create then missing DB tables will be created automatically for you.

Do not forget that Embedded DB requires the whole DB engine on the classpath. So, it is not sufficient to use derbyclient.jar that contains the driver only, you have to use derby.jar.

FAQs

Finally, I would like to mention few FAQs whose answers didn't make it into the above text:

How to specify an additional binding source?

The binding source combo box in the binding customizer offers the list of components added into the form (e.g. the components that you can see in Inspector window). Sometimes it is necessary to specify another binding source, for example your application model that is not added directly into your form. In this case, it is the best to add a getter for this object e.g. getModel() into the form class. After that you can use Form as the binding source and ${model.yourOriginalExpression} as the binding expression.

Why do my bound list/table/combo box is not updated when I add a new object into the corresponding list?

Standard JDK collections are not observable e.g. it is not possible to by notified about their changes. Fortunately, Beans Binding provides a set of observable collections that can be used as wrappers. Hence if you encounter the described problem then make sure that you wrap your list using ObservableCollections.observableList(originalList). Even when you use this wrapper you must ensure that you don't modify it directly using methods of the underlying originalList. You must add/remove elements through methods of the wrapper only (to allow the list to generate correct events).

Why do my entities show old values?

It might be due to TopLink's session cache. I will not delve into technical details, but it may help to call refresh() on your entity. If you find this annoying then you can try to set toplink.cache.shared.default property to false in persistence.xml. See http://weblogs.java.net/blog/guruwons/archive/2006/09/understanding_t.html for details.

How can I bind Date objects to some UI component easily?

Use JFormattedTextField and its value (not text!) property. Finally use property editor for formatterFactory property (of the formatted field) to customize the date/time format.

Attachments

bindingCustomizer1.jpg Info on bindingCustomizer1.jpg 69933 bytes
bindingCustomizer2.jpg Info on bindingCustomizer2.jpg 38729 bytes
bindingProperties.jpg Info on bindingProperties.jpg 16951 bytes
changeSupport.jpg Info on changeSupport.jpg 38390 bytes
createTables.jpg Info on createTables.jpg 22454 bytes
detailTable.jpg Info on detailTable.jpg 36840 bytes
entity.jpg Info on entity.jpg 25582 bytes
entityClassesFromDB.jpg Info on entityClassesFromDB.jpg 26690 bytes
generatedId.jpg Info on generatedId.jpg 7529 bytes
masterTable.jpg Info on masterTable.jpg 35058 bytes
newConnection.jpg Info on newConnection.jpg 21149 bytes
newDBShell.jpg Info on newDBShell.jpg 42146 bytes
newJDAApplication.jpg Info on newJDAApplication.jpg 17451 bytes
runningApp.jpg Info on runningApp.jpg 60949 bytes