You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@tapestry.apache.org by "Pierce T. Wetter III" <pi...@paceap.com> on 2010/03/31 18:05:09 UTC

Answering my own JEE6/Tapestry Question [LONG]

  I asked before, didn't get a great answer, but I've figured some stuff out since then, so here goes:

 (Note that some of this stuff may be wrong, I'm not an EJB or Tapestry expert, I'm just trying to weld the two together. (pun intended)). 

   Q: How can/should I use EJBs/JPA in Tapestry?

    A:

       Tapestry currently doesn't support EJB injection, however, there's a tynamo-jpa project that support injecting EntityManager objects in a fashion similar to tapestry-hibernate. 

       You can lookup EJBs easily as needed using the EJB3.1 naming convention though. Between the two, you can actually produce a "best of both worlds" environment for using JEE6. That is, you can keep pretty light sessions mostly, but when you need to write data, you can do that via an EJB. 


    Read-Mostly using  tynamo-jpa:

           Tynamo-jpa, like tapestry-hibernate, works best with read-mostly data. The cycle is something like this:


              1. Link to page with primary key of entity stored in context portion of URL, page expects entity object.  
              2. Tapestry makes a new EntityManager (EntityManagers are lightweight objects in JPA). 
              3. Entity fetched from either database or level 2 cache, passed to Page. 
              4. Page generates, entities turned back into primary keys, stored in context. 
              5. Entity Manager thrown away. 

        If you need to write data, you can fetch something into memory, modify it, and then save it using @CommitAfter. 

      Despite the dramatic language ("throwing stuff away"), the trade offs are complex because of the various caches built into JPA. Think of it as if the only thing that Tapestry needs to store in the session is the primary keys that it can then, via JPA, use to lookup objects via the cache. 

      So for searching and displaying data, tynamo-jpa does the job. But there's an assumption there about what most of your data looks like. For something like a webstore, 90% of the data is read-only catalog data that should be cached across users. The read/write data is stuff like the shopping cart, and that's what needs to be stored in the session, not the catalog, and it doesn't need to be cached so much. 

      Or consider GMail. When you login to GMail, it has to fetch all of your mailboxes, but your data doesn't intersect with anyone elses data. So caching your mailboxes doesn't make sense, and fetching all of your mail data again for each page may not make sense. What makes more sense is to fetch the data once, and then keep it around in the session. 

    Using EJB session beans for read/write data:

       EJBs come in 3 flavors: Singleton, which means everyone gets the same EJB, Stateless, which means they're kept in pools and re-used, and Stateful which means "one per customer". 

       Normally, the container manages the distinction for you. Since Tapestry doesn't directly support EJB injection though, though you can trust Singleton's to be singleton's, I'm not sure what happens with Stateless session beans. But that's ok, because in this case, we want a Stateful session bean, because we want an EntityManager that has data that persists across requests. 

   This is what mine looks like:

@Stateful
public class SchemaEditEJB
{

    //~ Instance fields ----------------------------------------------------------------------------

    /** Information for EntityManager we want injected when EJB is built*/
    @PersistenceContext(
        unitName = "LocalDB2GlassFish",
        type     = PersistenceContextType.EXTENDED
    )
    protected EntityManager em;

    //~ Methods ------------------------------------------------------------------------------------

    /**
     * Static method to lookup our EJB using standardized name spaces
     *
     */
    static SchemaEditEJB lookupSchemaEditEJB()
    {

        for (String prefix: new String[]
                {
                    "",
                    "java:module/",
                    "java:app/MyApp/",
                    "java:global/MyApp/"
                }
        )
        {

            try
            {
                Context context = new InitialContext();

                return (SchemaEditEJB) context.lookup(prefix + SchemaEditEJB.class.getSimpleName());
            }
            catch (NamingException ex)
            {
                Logger.getLogger(SchemaEditEJB.class.getName()).log(Level.SEVERE, null, ex);

            }
        }

        return null;

    }

    /**
     * Return an entity manager
     */
    public EntityManager getEntityManager()
    {

        return em;
    }

    /**
     * Persist an object
     *
     */
    public void persist(Object o)
    {
        em.persist(o);
    }

    /**
     * revert an object
     *
     */
    public void revert(Object o)
    {
        em.refresh(o);
    }

    /**
     * Save everything to the database, and throw the EJB away
     */
    @Remove
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void commit()
    {
        em.commit();
    }

    /**
     *  Ignore changes, throw the EJB away
     */
    @Remove
    public void uncommit()
    {

    }

} // end class SchemaEditEJB

   This isn't much more than a wrapper around an EntityManager. The idea here is to use the EJB goodness to handle transactions, building the EntityManager, and holding the data between requests. I have a second object which is stored in the Tapestry SessionStore  that lazily creates the EJB when its needed:

 /**
     * Lazily get EJB as needed
     */
    public SchemaEditEJB getEJB()
    {

        if (null == ejb)
        {
            ejb = SchemaEditEJB.lookupSchemaEditEJB();
        }

        return ejb;
    }

    /**
     * return EJB's entityManager
     */
    public EntityManager getEntityManager()
    {
        return getEJB().getEntityManager();
    }

(Actually, I'm using a conversation pattern so multiple edits can be going on in parallel, but whatever)

  Gotchas: Since we're not using injection, the container can't throw away the EJB for us if we call commit/uncommit above. I have to do that manually when someone calls commit:

            if (ejb != null)
            {
                ejb.commit();
                ejb = null;
            }


 Why this is the best of both worlds:

     1. You can use tynamo-jpa for your read-mostly data where you want to fetch-display-toss information. For instance, in my case, I'm building a CRUD app, and part of the CRUD app is searching for items. Mostly those objects get used once and tossed, so a transient EntityManager is the way to go. 

     (This isn't exactly a unique insight, this is precisely what Stateless EJBs are for. )

    2. For write-focused data like a shopping cart, or different cache requirements like Gmail, you can lookup a stateful EJB that you can use to hold an extended entity manager to function as L1 cache and session store. In my CRUD app, if you're creating or updating, I spawn a conversation with an attached EJB. 

  Thoughts, comments, gotchas about EJB lifecycles I didn't consider?

 pierce




Re: Answering my own JEE6/Tapestry Question [LONG]

Posted by "Pierce T. Wetter III" <pi...@paceap.com>.
On May 6, 2010, at 3:24 AM, Sergey Didenko wrote:

> Hi Pierce,
> 
> Could you share what you think of this approach now, 2 months later? Do you
> still think it makes sense to combine Tapestry and EJBs (application
> server)?

  Yep, I'm still using it. Simple searches, etc. are done using tynamo-jpa's injection of an EntityManager. For edits, I spawn off a conversational thread that I'm calling the edit tree, that has an EJB with a persistent entity manager. There's one gotcha I've found which is that because I'm looking up the EJB instead of injecting it, after certain exceptions I can end up with a dead reference to the EJB. 

 Pierce


---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: Answering my own JEE6/Tapestry Question [LONG]

Posted by "Thiago H. de Paula Figueiredo" <th...@gmail.com>.
On Thu, 06 May 2010 07:24:21 -0300, Sergey Didenko  
<se...@gmail.com> wrote:

> Hi Pierce,

Hi!

> Could you share what you think of this approach now, 2 months later? Do  
> you still think it makes sense to combine Tapestry and EJBs (application
> server)?

If EJB is interesting for your application, I can't see why Tapestry and  
EJB wouldn't make sense. They work in different layers of your application.

-- 
Thiago H. de Paula Figueiredo
Independent Java, Apache Tapestry 5 and Hibernate consultant, developer,  
and instructor
Owner, Ars Machina Tecnologia da Informação Ltda.
http://www.arsmachina.com.br

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscribe@tapestry.apache.org
For additional commands, e-mail: users-help@tapestry.apache.org


Re: Answering my own JEE6/Tapestry Question [LONG]

Posted by Sergey Didenko <se...@gmail.com>.
Hi Pierce,

Could you share what you think of this approach now, 2 months later? Do you
still think it makes sense to combine Tapestry and EJBs (application
server)?

Regards, Sergey.

(
http://old.nabble.com/Answering-my-own-JEE6-Tapestry-Question--LONG--td28098096.html
 )

On Wed, Mar 31, 2010 at 7:05 PM, Pierce T. Wetter III <pi...@paceap.com>wrote:

>
>  I asked before, didn't get a great answer, but I've figured some stuff out
> since then, so here goes:
>
>  (Note that some of this stuff may be wrong, I'm not an EJB or Tapestry
> expert, I'm just trying to weld the two together. (pun intended)).
>
>   Q: How can/should I use EJBs/JPA in Tapestry?
> ...