Kontakt

JPA support in Tapestry

Posted on Mittwoch, 25th Mai, 2011

In this article I’m going to describe the Tapestry/JPA 2 Integration library that provides out-of-the-box support for using JPA 2 as the back end for normal CRUD style Tapestry applications. I added this library to Tapestry’s trunk some weeks ago but didn’t have any time to write an article about it. Now that we are close to a first 5.3 release, I would like to give you a preview of this library.

Configuring JPA

The persistence.xml file is the standard configuration file in JPA used to define the persistence units. By default, this file is expected to be located on the classpath in the META-INF directory. Tapestry reads this file to create the EntityManagerFactory. The following example demonstrates a persistence.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             version="2.0">
   <persistence-unit name="Red"
                     transaction-type="RESOURCE_LOCAL">
       <properties>
          <property name="javax.persistence.jdbc.driver"
                    value="org.h2.Driver" />
          <property name="javax.persistence.jdbc.url"
                    value="jdbc:h2:mem:test" />
          <property name="javax.persistence.jdbc.username"
                    value="sa" />
          <property name="eclipselink.ddl-generation"
                    value="create-tables"/>
          <property name="eclipselink.logging.level"
                    value="fine"/>
      </properties>
   </persistence-unit>

   <persistence-unit name="Blue"
                     transaction-type="RESOURCE_LOCAL">
      <non-jta-data-source>
         jdbc/JPATest
      </non-jta-data-source>
   </persistence-unit>

</persistence>

Note that you need to provide unique names for persistence units. In the example above we defined two persistence units named Red and Blue.

If you want to place the persistence.xml file in an other directory or name it arbitrarily, you can make a contribution to the SymbolProvider service, as shown in the following example. This is a quite useful feature if you want to use a different persistence descriptor for tests.

public class AppModule {

   @Contribute(SymbolProvider.class)
   @ApplicationDefaults
   public static void provideFactoryDefaults(
       MappedConfiguration<String, String> configuration) {

      configuration.add(
            JpaSymbols.PERSISTENCE_DESCRIPTOR,
            "/jndi-datasource-persistence-unit.xml");
   }
}

For each of the persistence units defined in the persistence.xml file Tapestry creates a TapestryPersistenceUnitInfo. The interface TapestryPersistenceUnitInfo is mutable extension of the PersistenceUnitInfo interface which allows you to configure a persistence unit programmatically. This can be accomplished by making a contribution to the EntityManagerSource service, as shown in the following example.

public class AppModule {

   @Contribute(EntityManagerSource.class)
   public static void configurePersistenceUnitInfos(
            MappedConfiguration<String,PersistenceUnitConfigurer>
            cfg) {

      PersistenceUnitConfigurer configurer
                 = new PersistenceUnitConfigurer() {

         public void configure(
                  TapestryPersistenceUnitInfo unitInfo) {
            unitInfo.addManagedClass(User.class);
         }
      };

      cfg.add("Blue", configurer);
   }
}

The EntityManagerSource service’s configuration is a map in which a persistence unit name is associated with a PersistenceUnitConfigurer instance. A PersistenceUnitConfigurer is used to configure a TapestryPersistenceUnitInfo that has been read from the persistence.xml file.

Automatically adding managed classes

If only a single persistence unit is defined, Tapestry scans the  application-root-package.entities package. The classes in that package are automatically added as managed classes to the defined persistence unit.

If you have additional packages containing entities, you may contribute them to the JpaEntityPackageManager service configuration.

public class AppModule {

   @Contribute(JpaEntityPackageManager.class)
   public static void providePackages(
            Configuration<String> configuration) {
      configuration.add("org.example.myapp.domain");
   }
}

You may add as many packages as you wish.

Injecting the EntityManager into page and component classes

The created entity managers can be injected into page and component classes.  Depending on whether more than one persistence unit has been defined, the way to inject EntityManager varies slightly.

Let’s start with a simple scenario, where only a single persistence unit is defined. In this case, an EntityManager can be injected using the @Inject annotation.

public class CreateAddress {

   @Inject
   private EntityManager entityManager;

   @Property
   private Address address;

   @CommitAfter
   void onSuccess() {
      entityManager.persist(address);
   }
}

Alternatively, you can use the @PersistenceContext annotation to get the EntityManager injected into a page or component.

public class CreateAddress {

   @PersistenceContext
   private EntityManager entityManager;

   @Property
   private Address address;

   @CommitAfter
   void onSuccess() {
      entityManager.persist(address);
   }
}

If you have multiple instances of persistence-unit defined in the same application, you need to explicitly tell Tapestry which persistence unit you want to get injected. So,  just placing @Inject annotation on the injection place is not sufficient.

public class CreateAddress {

   @PersistenceContext(unitName = "Blue")
   private EntityManager entityManager;

   @Property
   private Address address;

   @CommitAfter
   @PersistenceContext(unitName = "Blue")
   void onSuccess() {
      entityManager.persist(address);
   }
}

In the example above, the @PersistenceContext annotation’ s name attribute is used to explicitly define the name of the unit to inject.

Injecting EntityManager into services

While component injection occurs only on fields, the injection in the IoC layer may be triggered by a  field or a constructor. The following example demonstrates field injection, when a single persistence unit is defined in the persistence descriptor.

public class UserDAOImpl implements UserDAO {
   @Inject
   private EntityManager entityManager;

   ...
}

The constructor injection is demonstrated in the following example.

public class UserDAOImpl implements UserDAO {

   private EntityManager entityManager;

   public UserDAOImpl(EntityManager entityManager) {
      this.entityManager = entityManager;
   }

   ...
}

Because @PersistenceContext annotation must not be placed on constructor parameters, you can’t use constructor injection if multiple persistence units are defined in the same application. In such a case, only field injection is supported, as shown in the following example.

public class UserDAOImpl implements UserDAO {
   @Inject
   @PersistenceContext(unitName = "Blue")
   private EntityManager entityManager;

   ...
}

Transaction management

As you already know from the Hibernate integration library, Tapestry automatically manages transactions for you. The JPA integration library defines the @CommitAfter annotation, which acts as the correspondent annotation from the Hibernate integration library. Let’s explore the UserDAO interface to see the annotation in action.

public interface UserDAO {

   @CommitAfter
   @PersistenceContext(unitName = "Blue")
   void add(User user);

   List<User> findAll();

   @CommitAfter
   @PersistenceContext(unitName = "Blue")
   void delete(User... users);
}

As you can see, the annotation may be placed on service method in order to mark that method as transactional. Any method marked with @CommitAfter annotation will have a transaction started before, and committed after it is called. Runtime exceptions thrown by by a transactional method will abort the transaction. Checked exceptions are ignored and the transaction will be committed anyway.

Note that EntityTransaction interface does not support two phase commit. Committing transactions of multiple EntityManagers in the same request might result in data consistency issues. That’s why @CommitAfter annotation must be accompanied by the @PersistenceContext annotation if multiple persistence unit are defined in an application. This way you can only commit the transaction of a single persistence unit. You should be very carefully, if you are committing multiple transactions manually in the same request.

After placing the @CommitAfter annotation on methods, you need to tell Tapestry to advise those methods. This is accomplished by adding the transaction advice, as shown in the following example.

public class AppModule {

   @Match("*DAO")
   public static void adviseTransactionally(
         JpaTransactionAdvisor advisor,
         MethodAdviceReceiver receiver) {

      advisor.addTransactionCommitAdvice(receiver);
   }
}

That’s it for the first part of the article on JPA 2 support in Tapestry. In the next article I’ll cover some further interesting features of the JPA integration library like providing ValueEncoder for managed classes, using @Persist and @SessionState annotations with entities and JPA-enabled Grid component.

 

  1. Mihail Slobodyanuk
  2. Igor Drobiazko
  3. Igor Drobiazko
  4. Maher
  5. Maher
  6. Maher
  7. Igor Drobiazko
  8. Maher
  9. Igor Drobiazko
  10. Maher
  11. Maher

Tapestry 5 Blog - Copyright © 2009 - Eclectic Theme by Your Inspiration Web - Powered by WordPress