Version 15

    This page is created to discuss the current problem for ejb3 sfsb clustering. In the current ejb3 spec, there is a first requirement for ejb3 sfsb to have a ExtendedPersistentContext (XPC) injected, e.g.,

     

       @PersistenceContext(type=PersistenceContextType.EXTENDED) EntityManager em;

     

    that will declare the EntityManager to preserve its managed entity states across multiple ejb calls (potentially multiple transactions). Case in point is SEAM is using this to span the so-called conversation state. So there are issues of entity state and when it will be flushed.

     

    Then there another requirement for the nested SFSB, e.g.,

     

       @EJB private Contained contained;
    

     

    means if Contained is another SFSB, the XPC will need to propagate to Contained automatically as well. This is also means that both Parent and Contained bean will share the XPC (with object relationship).

     

    SESSION-level replication

     

    The current SFSB replication implementation uses the default SESSION granularity. In Bill's implementation, every replication request (possibly during tx boundary if there is one or end of method call if no tx is required) will replicate the whole user beans along with the StateSessinContext that contains the XPC, if there is any.

     

    While this is straightforward, it can be little heavy. E.g., here is the replication message size when I do a simple sfsb clustering:

     

    simple sfsb

    1.6kb

    1 nested sfsb (along with parent) only (w/o XPC)

    3.7 kb

    1 nested with XPC and 1 simple entity

    4.1kb

     

    Two reasons why we need to replication the whole StateSessionContext are:

    1. ejb3 interceptors. Since the lifecycle of the user-defined interceptor is the same as the sfsb, if there are states, then we need to replicate them.

    2. XPC. Since the container has no way of knowing when the managed entity is dirty, we need to replicate whenever we can.

     

    The first one we can probably get by without replicating it by claiming that defining a state the interceptor is not supported in our clustering offering.

     

    The second on is the focus of this page: can Hibernate underlying Session provide a delta API such that the SFSB container only needs to replicate the delta (if any)? This way, we don't need to replicate the static data in the StatefulSessionContext.

     

    So here is a typical use case that currently exists in the testsuite. Again, we have a nested bean along with XPC declaration.

     

    First one is the parent bean of which is a shopping cart.

     

    @Stateful
    @Clustered
    @Remote(ShoppingCart.class)
    @CacheConfig(maxSize = 1000, idleTimeoutSeconds = 2)
    public class ShoppingCartBean implements ShoppingCart, Serializable
    {
    
       @PersistenceContext(type=PersistenceContextType.EXTENDED) EntityManager em;
    
       @EJB StatelessLocal stateless;
    
       private Customer customer;
    
       @EJB private Contained contained;
    
       public long createCustomer()
       {
          customer = new Customer();
          customer.setName("William");
          em.persist(customer);
          System.out.println("********* created *****");
          return customer.getId();
       }
    
       public void setContainedCustomer()
       {
          contained.setCustomer(customer.getId());
       }
    
       public void updateContained()
       {
          contained.updateCustomer();
       }
    
       public void update()
       {
          System.out.println("********* update() *****");
          customer.setName("Bill");
       }
    
       public void update3()
       {
          stateless.update(customer);
       }
    
       public Customer find(long id)
       {
          return em.find(Customer.class, id);
       }
    
       @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
       public void never()
       {
          customer.setName("Bob");
       }
    
       @Remove
       public void checkout() {}
    }
    

     

    and this is the nested bean:

    @Stateful
    @Clustered
    @CacheConfig(maxSize = 1000, idleTimeoutSeconds = 2)
    public class ContainedBean implements Contained
    {
       @PersistenceContext(type= PersistenceContextType.EXTENDED) EntityManager em;
    
       Customer customer;
    
       public Customer find(long id)
       {
          return em.find(Customer.class, id);
       }
    
       public void setCustomer(long id)
       {
          customer = find(id);
       }
    
       public Customer getCustomer()
       {
          return customer;
       }
    
       public void updateCustomer()
       {
          customer.setName("contained modified");
       }
    }
    

     

    Finally we have the entity of which is just a POJO,

     

    @Entity
    public class Customer implements java.io.Serializable
    {
       long id;
       String name;
    
       public
       Customer()
       {
       }
    
       @Id @GeneratedValue(strategy=GenerationType.IDENTITY)
       public
       long getId()
       {
          return id;
       }
    
       public
       String getName()
       {
          return name;
       }
    
       public
       void setId(long long1)
       {
          id = long1;
       }
    
       public
       void setName(String string)
       {
          name = string;
       }
    }
    

     

    From the client side, this is a code snippet:

          ShoppingCart cart = (ShoppingCart) getInitialContext(0).lookup("ShoppingCartBean/remote");
          StatelessRemote stateless = (StatelessRemote) getInitialContext(0).lookup("StatelessSessionBean/remote");
          Customer customer;
    
    
          long id = cart.createCustomer();
          customer = cart.find(id);
    
          tx.begin()
          cart.update();
          cart.updateContained();
          tx.commit(); // First tx, meaning replication
    
          tx.begin()      
          cart.update3();
          cart.setContainedCustomer();
          tx.commit(); // First tx, meaning replication
    
          tx.never(); // No tx at all, meaning replication as well.
    
          cart.checkout(); // bean destroyed
    

     

    Passivation vs. Replication

    Both requires serialization. But replication happens more frequently than passivation. Currently implementation we don't distinguish between the two (passivation are handled transparently by JBossCache). But if we use the Session delta, then they needs to be treated differently.

     

    FIELD-level replication

    And then there is another issue of FIELD-level replication. If we can provide the delta API, can we use FIELD-level? If it is possible, then upon the bean creation, we will replicate the StateSessionContext. But afterwards, between every replication boundary, we will replicate only the field changes along with the entity delta.

     

    Here are the issues to be resolved for FIELD-level replication:

     

    • Serialization scope. Currently, Hibernate Session (through em in the StatefulSessionContext) and the sfsb will share the same reference for the entity, e.g.,

      Customer

      . So to keep the same reference upon replication, we will need reconstruct the customer reference in Cart (from Hibernate Session). Is an entity ID enough to do that?

     

    • Managed and unmanaged state. Say, during the lifetime of

      Customer

      in

      ShoppingCart

      , it can become unmanaged and managed interchangably. For example, untill

      em.persist(customer)

      is called,

      customer

      is not yet managed by Hibernate. In this case, we will need to know when is the entity not managed? If it is not, then the SFSB container will need to replicate the entity as well. If it is managed, we will rely on Hibernate to do that for us.

     

    • Question of em.flush()? Will this impact the way that Hibernate track the entity states?

     

    • What happens if the caller obtains the em (XPC) directly instead of relying on SFSB?

     

     

     

    Referenced by: