An intro to simple-jpa: a bookstore tutorial


simple-jpa is a Griffon’s plugin for developing JPA and Swing based desktop application. The main goal of simple-jpa is to allow developer to concentrate on business logic. simple-jpa provides much functionality that is needed when working with JPA, therefore, frees developer from writing high-ceremony code.

simple-jpa is very useful for rapidly developing Swing-based database oriented desktop application. It can also be used for prototyping.

The following is a list of some of simple-jpa’s features:

Scaffolding – simple-jpa can generate an MVCGroup based on a domain class. This will speed up development.

Dynamic finders – simple-jpa injects dynamic finders to controllers (or services). With dynamic finders, developer can perform a query on JPA entities (or domain objects) quickly and easily. simple-jpa also supports the execution of JPA named query, JPQL and native SQL.

Transaction management – Unlike web-based applications, desktop applications do not require Java Transaction API (JTA). simple-jpa automatically provides and manages transaction for each method in controllers (can be configured by using annotation). By default, simple-jpa will share EntityManager across transaction in a way that is suitable for desktop application.

Bean Validation API (JSR-303) support – In the case of failed validation, simple-jpa will automatically present error messages in Swing-based view. Developer can also configure error notification and its behavior.

Common database application features – simple-jpa adds the following to all domain classes: an id (auto generated primary key), fields that store created time and last modified time (will be filled automatically), and a soft delete flag (soft delete is marking the object as inactive without deleting it from database).

Swing nodes for database application – simple-jpa provides template renderer for effortlessly represent domain object in JTable, JList or JComboBox. It also provides new nodes that can be used in Griffon’s view such as tagChooser, numberTextField, maskTextField, and dateTimePicker.

Integration testing – simple-jpa is using dbUnit in integration testing to fill database with predefined data from a Microsoft Excel file (or csv file). This way, every test cases will be executed with the same table data.

This tutorial will assume that you are using the following software:

  1. Windows operating system
  2. Java Development Kit 7
  3. Griffon 1.2
  4. simple-jpa 0.4
  5. IntelliJ IDEA
  6. MySQL Server

This tutorial will assume that you are using the following software:

  1. Windows operating system
  2. Java Development Kit 7
  3. Griffon 1.2
  4. simple-jpa 0.4
  5. IntelliJ IDEA
  6. MySQL Server

The first step is creating a new Griffon application. You can do this by opening Command Prompt and entering the following commands:

mkdir c:\projects
cd c:\projects
griffon create-app bookstore

You can change c:\projects in the above commands to another directory. If that directory already exists, you can skip the ‘mkdir` command.

Griffon will create a subdirectory called bookstore that contains your project’s files, including source code. You will need to move to this directory by entering the following command:

cd bookstore

Next, you will install simple-jpa plugin by entering the following command:

griffon install-plugin simple-jpa 0.4

If simple-jpa has never been installed on your computer, Griffon will download required files for the first time. simple-jpa is a fat plugin with a lot of dependencies, so you may need to wait.

After simple-jpa has been downloaded and installed, the next step is integrating your project with IntelliJ IDEA by entering the following command:

griffon integrate-with -–idea

You can also replace –idea with –eclipse if you’re using Eclipse. In the above command, Griffon will create an IntelliJ IDEA project in your current directory. You can also create a new Griffon’s project in IntelliJ IDEA from the beginning, without using any command line as in this tutorial, but there is a drawback. If you create project and install simple-jpa from inside IntelliJ IDEA, dependencies for current project will not setup properly and autocomplete for project dependencies will not work.

You can start IntelliJ IDEA now. With IntelliJ IDEA running, select Open Project, browse to C:\projects\bookstore, and then click on OK button to open your project.

Opening Griffon's project in IntelliJ IDEA

Opening Griffon’s project in IntelliJ IDEA

JPA application development usually starts with creating persistence.xml. This file will contain information required for database connection and JPA configurations. It will also contain a list of entities that will be managed by JPA.

The next most common thing to start with is creating database user and schema.

To keep things simple, you will use create-simple-jpa command. This command will generate persistence.xml for you. If you want it to also generate database schema and a new user, you will need to provide MySQL’s root password. Note that MySQL’s root password will not be saved and will not be used inside application.

In this step, you will create persistence.xml, a new database named bookstore and a database user steven (whose password is 12345). You will need to select Tools, Griffon, Run Target (or press Ctrl+Alt+G) and enter the following command:

create-simple-jpa --user=steven --password=12345 --database=bookstore --rootPassword=adminpassword
Entering Griffon's command in IntelliJ IDEA

Entering Griffon’s command in IntelliJ IDEA

The command above is assuming that your root password is adminpassword. In some cases, your root password may be empty, so you will need to use the following command instead:

create-simple-jpa --user=steven --password=12345 --database=bookstore --rootPassword=

If you only want to generate persistence.xml without performing any database operations, you can use the following command instead:

create-simple-jpa --user=steven --password=12345 --database=bookstore --skipDatabase=true

The next step will be creating domain classes. Your bookstore application will have the following domain classes: TransactionOrder, OrderItem, Book, and Author. To create domain classes and add their location to persistence.xml, you will need to enter the following command:

create-domain-class TransactionOrder OrderItem Book Author

You will find all generated domain classes in the domain package. They are plain JPA entities that follow JPA rules.

simple-jpa domain classes

simple-jpa domain classes

Open TransactionOrder.groovy and add some members to this domain class:

package domain
import …

@DomainModel @Entity @Canonical
class TransactionOrder {

   @NotEmpty @Size(min=5, max=5)
   String orderNumber

   @Type(type="org.jadira.usertype.dateandtime.joda.PersistentLocalDate")
   LocalDate orderDate

   @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true , mappedBy="transactionOrder")
   List<OrderItem> orderItemList = []

   BigDecimal getAmount() {
      orderItemList.sum { it.amount }
   }

}

P.S.: If you didn’t add cascade and orphanRemoval to @OneToMany annotation, the code generated by scaffolding will not work. You’re also expected to initialize the Collection.

Open OrderItem.groovy and add some members to this domain class:

package domain

import ...

@DomainModel @Entity @Canonical (excludes = "transactionOrder")
class OrderItem {

   @NotNull @ManyToOne
   Book book

   @NotNull @Min(1l)
   Integer quantity

   @ManyToOne
   TransactionOrder transactionOrder

   BigDecimal getAmount() {
      book.price * quantity
   }

}

Open Author.groovy and add some members to this domain class:

package domain

import ...

@DomainModel @Entity @Canonical (excludes = "bookList")
class Author {

   @NotEmpty @Size(min=2, max=100)
   String firstName

   @Size(min=2, max=100)
   String lastName

   @Email
   String email

   @ManyToMany(mappedBy="authorList")
   List<Book> bookList

}

Open Book.groovy and add some members to this domain class:

package domain

import ...

@DomainModel @Entity @Canonical
class Book {

   @NotEmpty @Size(min=9, max=13)
   String isbn

   @NotEmpty @Size(min=2, max=100)
   String title

   @NotNull @Min(1l)
   BigDecimal price

   @NotEmpty @ManyToMany
   List<Author> authorList

}

Your next step will be generating Griffon’s MVCGroups based on your domain classes. To do this, enter the following command:

generate-all * --startupGroup=MainGroup

simple-jpa will create one MVCGroup per domain class. Each MVCGroup has model, view and controller that are capable of performing create, read, update and delete operation.

The above command will also generate one special MVCGroup named MainGroup which is a startup group. This MVCGroup will act as a main window that provides button to launch other MVCGroups.

By default, generate-all will not replace existing files. If you really want to overwrite existing files, you can use the following command instead:

generate-all * --startupGroup=MainGroup --force-overwrite

Up to this point, you already have a working bookstore application. You don’t need to worry about creating database tables because simple-jpa has added hibernate.hbm2ddl.auto to persistence.xml. This configuration will instruct Hibernate JPA to automatically create tables for domain classes in application startup.

You can start your application by entering the following command:

run-app

The startup group’s view contains buttons that link to the others.

simple-jpa startup group

simple-jpa startup group

P.S.: Every time you start your application, data in tables will be discarded. You will need to remove hibernate.hbm2dll.auto in persistence.xml if you want to preserve data in your tables.

One of simple-jpa’s nice features is that it knows about your domain classes’ relationship. simple-jpa will generate a special MVCGroup if it finds one-to-one and one-to-many relationship. For example, if you open Transaction Order view, you will find an Order Item List button. This is an example of one-to-many relationship. If you click on the button, a dialog to input one or more OrderItem will be displayed.

scaffolding one-to-many relationship

scaffolding one-to-many relationship

If you have examined the startup group clearly, you will find that there is an Order Item menu in your startup group. This is usually undesirable, because, user will always open Order Item view from Transaction Order view. You can remove it by opening MainGroupView.groovy, deleting line 12 and 27 as shown below:

actions {
   …
   action(id: 'orderItem', name: 'Order Item', actionCommandKey: 'orderItem', closure: controller.switchPage)
    …
}

toolBar(constraints: BorderLayout.PAGE_START, floatable: false) {
    …
    button(action: orderItem, verticalTextPosition: SwingConstants.BOTTOM, horizontalTextPosition: SwingConstants.CENTER)
    …
}

Your bookstore application will perform validation based on the annotations in domain classes. For example, firstName in Author class has @NotEmpty annotation. If user didn’t input author’s first name when entering new data, your application will display an error message.

simple-jpa validation

simple-jpa validation

If user input a new Book, he will need to choose authors (one or more) from simple-jpa’s tagChooser. By default, simple-jpa will use tagChooser if it encounters many-to-many relationship. tagChooser will display String version of domain object which is obtained by calling toString(). This is often seen as undesirable.

simple-jpa tagChooser

simple-jpa tagChooser

You can use template renderer to represent domain object in more informative text. For example, open BookView.groovy and change line 53 to the following:

tagChooser(model: model.authorList, templateString: '${value.firstName} - ${value.lastName}',
   constraints: 'grow,push,span,wrap', errorPath: 'authorList')

In the code above, you’re using simple-jpa’s template renderer to display Author’s first name and last name in tagChooser. Template renderer can accept any valid Groovy expression.

simple-jpa template renderer

simple-jpa template renderer

You can also use template renderer in table. For example, in the picture above, the fourth column (author list) is hard to read. Open BookView.groovy and change line 36 to the following:

columnValues: ['${value.isbn}', '${value.title}', '${value.price}',
    '<% out << value.authorList.collect{ "${it.firstName} - ${it.lastName}"}.join(", ") %>'])

The expression above will display a comma separated list of authors for each book.

Using simple-jpa template renderer in table

Using simple-jpa template renderer in table

If you are displaying number or money amount in template renderer, you can also use simple-jpa’s formatter functions such as numberFormat() or currencyFormat(). For example, open TransactionOrderView.groovy and change line 36 to the following:

columnValues: ['${value.orderNumber}', '${value.orderDate}',
    '${value.orderItemList.size()} items, Total ${currencyFormat(value.amount)}'])
Using formatter in template renderer

Using formatter in template renderer

Perihal Solid Snake
I'm nothing...

7 Responses to An intro to simple-jpa: a bookstore tutorial

  1. Tolhah Hamzah mengatakan:

    Mas, mau tanya, saya sudah mengikuti tutorial di atas, tapi menjumpai error semacam ini saat input data book ke database..

    2014-04-06 20:16:09,784 [pool-2-thread-2] ERROR griffon.util.GriffonExceptionHan
    dler – Uncaught Exception
    org.codehaus.groovy.runtime.InvokerInvocationException: java.lang.StackOverflowE
    rror
    at org.codehaus.griffon.runtime.util.AbstractUIThreadHandler$1.run(Abstr
    actUIThreadHandler.java:41)
    Caused by: java.lang.StackOverflowError
    at domain.Author.toString(Author.groovy)
    at domain.Book.toString(Book.groovy)
    at domain.Author.toString(Author.groovy)
    at domain.Book.toString(Book.groovy)
    at domain.Author.toString(Author.groovy)
    at domain.Book.toString(Book.groovy)
    at domain.Author.toString(Author.groovy)

    mohon bantuannya mas, terima kasih banyak…

    • Solid Snake mengatakan:

      Hal ini terjadi karena kode program menggunakan @Cannonical untuk menghasilkan toString(), equals(), hashcode(), dan constructor secara otomatis. Permasalahannya adalah fitur dari Groovy tersebut tidak dapat mendeteksi referensi recursive sehingga terjadi stackoverflow.

      Solusinya: Pastikan pada class Author, annotation terlihat seperti @Canonical (excludes = "bookList") seperti pada contoh kode program. Hal ini menyebabkan bookList diabaikan sehingga tidak terjadi referensi recursive. Periksa juga untuk domain class lain.

  2. Tolhah Hamzah mengatakan:

    It works!

    benar sekali, saya belum menyertakan annotation (excludes = “bookList”) pada class Author dan class lain seperti yang dijelaskan…

    terima kasih mas atas jawabannya😀

  3. Tolhah Hamzah mengatakan:

    Permisi,
    Saya mencoba meng-update griffon saya menjadi 1.5, dan simple-jpa menjadi versi 0.6.
    Setelah saya coba jalankan aplikasi griffonnya, saya mencoba menambahkan Book (yang memiliki beberapa Author),
    tapi saya mendapatkan error seperti di bawah ini:

    [pool-2-thread-2] ERROR griffon.util.GriffonExceptionHandler – Uncaught Exception
    org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: domain.Author.bookList, could not initialize proxy – no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:572)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:212)
    at org.hibernate.collection.internal.AbstractPersistentCollection.readElementExistence(AbstractPersistentCollection.java:319)
    at org.hibernate.collection.internal.PersistentBag.contains(PersistentBag.java:288)
    at java_util_List$contains$9.call(Unknown Source)
    at project.LevelController$_closure3_closure13_closure14.doCall(BookController.groovy:55)
    at project.BookController$_closure3_closure13.doCall(BookController.groovy:54)
    at project.BookController$_closure3_closure13.doCall(BookController.groovy)
    at org.codehaus.griffon.runtime.util.AbstractUIThreadHandler$1.run(AbstractUIThreadHandler.java:41)

    ……..
    ini terlihat seperti kesalahan session atau lazy loading,

    Itu kenapa bisa terjadi ya mas..? semoga ada solusi untuk permasalahan di atas.
    Terima kasih banyak

  4. Tolhah Hamzah mengatakan:

    Permisi,
    Saya mencoba meng-update griffon saya menjadi 1.5, dan simple-jpa menjadi versi 0.6.
    Setelah saya coba jalankan aplikasi griffonnya, saya mencoba menambahkan Book (yang memiliki beberapa Author),
    tapi saya mendapatkan error seperti di bawah ini:

    [pool-2-thread-2] ERROR griffon.util.GriffonExceptionHandler – Uncaught Exception
    org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: domain.Author.bookList, could not initialize proxy – no Session
    at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:572)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:212)
    at org.hibernate.collection.internal.AbstractPersistentCollection.readElementExistence(AbstractPersistentCollection.java:319)
    at org.hibernate.collection.internal.PersistentBag.contains(PersistentBag.java:288)
    at java_util_List$contains$9.call(Unknown Source)
    at project.BookController$_closure3_closure13_closure14.doCall(BookController.groovy:55)
    at project.BookController$_closure3_closure13.doCall(BookController.groovy:54)
    at project.BookController$_closure3_closure13.doCall(BookController.groovy)
    at org.codehaus.griffon.runtime.util.AbstractUIThreadHandler$1.run(AbstractUIThreadHandler.java:41)

    ……..
    ini terlihat seperti kesalahan session atau lazy loading,

    Itu kenapa bisa terjadi ya mas..? semoga ada solusi untuk permasalahan di atas.
    Terima kasih banyak

    • Solid Snake mengatakan:

      Ada perubahan besar pada manajemen session sejak versi 0.5. Coba ganti annotation seperti @ManyToMany(mappedBy="authorList") menjadi @ManyToMany(mappedBy="authorList", fetch=FetchType.EAGER). Lakukan hal yang sama untuk @OneToMany dan @ManyToMany lainnya agar melakukan eager fetching.

      Selain itu, pastikan juga controller memiliki annotation @Transaction. Pada versi 0.5, nama annotation dari @SimpleJpaTransaction diganti menjadi @Transaction. Kemudian nama annotation dari @DomainModel diganti menjadi @DomainClass.

      Bila kesalahan masih muncul, coba post kode program controller yang bermasalah.

      Tutorial ini tidak berlaku lagi sejak versi 0.5 dan perlu diperbaharui.

  5. Tolhah Hamzah mengatakan:

    Berhasil!
    Terima kasih atas solusinya (:

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: