Thursday, April 1, 2010

JPA with Hibernate on Glassfish 3

Intro
This post is simply a summary of a simple proof of concept. It shows how to:
  • create a stateless EJB3 bean
  • persist an entity using JPA with Hibernate as a JPA provider
  • configure JPA to use a datastore defined on Glassfish (uses pre-installed java/__default datastore, which uses pre-installed Derby/JavaDB) 
You can download the source code for this sample from here (EJB3_First_Project_with_Test.zip).
 

Entity
@Entity
public class Book implements Serializable {
 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 private int id;
 private String title;
 private float price;
 
 public Book() {
    super();
 }
...
}
Here, I have annotated the property, rather than the getter (not shown for brevity), since it feels more intuitive to me. I don't know if there is any advantage/difference of one vs the other other than my personal preference. The "@GeneratedValue(...)" annotation for primary key in JPA entity definition is not optional, as I expected. Using just the "@Id" annotation results in the primary key being not set (set to 0).


EJB Bean
The interface:
@Remote
public interface BookManager {
 public abstract int addBook(Book book);
 public abstract Book find(int bookId);
}

And the bean implementation:
@Stateless(name="BookManager", mappedName = "ejb/BookManagerJNDI")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class BookManagerImpl implements BookManager {

 // Dependency injection of Entity Manager for
 // the given persistence unit
 @PersistenceContext(unitName="pu1") EntityManager em;
 
 public int addBook(Book book) {

  // Transitions new instances to managed. On the
  // next flush or commit, the newly persisted
  // instances will be inserted into the datastore.
  em.persist(book);
  
  return book.getId();
 }
 
 @Override
 public Book find(int bookId) {
  return em.find(Book.class, bookId);
 }
}

The annotation @Stateless with the mappedName make the bean to get registered with JNDI. The
TransactionAttributeType.REQUIRED tells the container to wrap each method in the class with a transaction.
 
JPA Setup
The persistence.xml file (put it into META-INF folder under src folder so it will be deployed onto the server)
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
    <persistence-unit name="pu1">
     <!-- Persistence provider -->  
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>jdbc/__default</jta-data-source>

        <properties>
   <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
   <property name="hibernate.hbm2ddl.auto" value="create" />
   <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

The "hibernate." prefix in persistence.xml file is not optional when adding properties of the JPA provider (Hibernate in this case). They are ignored without it, without a warning.

Glassfish Setup
Nothing other than installing the Hibernate component using the Glassfish console.

Unit Tests
The unit test:
try {   
   InitialContext ctx = new InitialContext();
   BookManager bean = (BookManager) ctx.lookup("ejb/BookManagerJNDI");
   Book book = new Book("Chocolate Rain", 25.10f);
   int bookId = bean.addBook(book );
   book = null;
   
   book = bean.find(bookId);
   Assert.assertEquals("Chocolate Rain", book.getTitle());
  } catch (NamingException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
   Assert.fail("Failed. Exception thrown.");
  }

A Few Notes
  • Can JPA be used to create and index? Yes and no. Not in general. But it provides a limited capability. To create a unique constrain on a table, use @UniqueConstraint(columnNames={"EMP_BDAY", "EMP_NAME"})
    • How to specify the character column type length? Use @Column(length=50).
    • What the client can understand about the remote exception, in case the EJB bean fails? The remote exception is wrapped in a EJBException and returned to the client. For example, if the password for the DB is incorrect, the org.hibernate.exception.GenericJDBCException will be returned with a message "Cannot open connection" (when Hibernate is used as JPA provider).
     Conclusion
    A few things to try in the next proof of concept:
    • test the rollback function, if second of two persistence operations failed when both are in the same method of the BookManager class.
    • do a parent/child relationship.
     

    1 comment:

    Artem said...

    thanks for this good post