EJB 3 Migration

I personally have very little experience with EJB 2, however the company that I work for like so many other companies still use EJB 2. Projects have started and then stopped when trying to migrate to EJB 3 as it can be a very time consuming exercise, especially the UAT testing. I will describe the compatibility, interoperability and migration issues you may face but i am no expert, thus you may have to resort to Goggle to obtain more information.

EJB 3 is completely different to EJB 2, the newer containers fully support EJB 2 but whether your application can run directly from a EJB 3 container without modification would require a bit of luck, it seems that you may have to do something in order to get it to work. Both EJB 3 and EJB 2 can co-exist, which means that the migration path can be easy by slowly migrating your EJB2 components to EJB 3.

The first EJB 3 migration item to be aware of is that if you want to package both EJB 3 and EJB 2 together then you must set the version attribute of the ejb-jar module to 3.0, if you user a older version then the container will not scan for annotations, so no EJB3 beans will be detected.

EJB 2/3 packaging

<?xml version = '1.0' encoding = 'windows-1252'?>
<ejb-jar version="3.0"
         xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
         http://java.sun.com/xml/ns/j2ee/ejb-jar_3_0.xsd"
>
...
</ejb-jar>

You can invoke a EJB 2 bean (session or entity) from a EJB 3 bean (session or MDB), you can even use dependency injection.

Injecting a EJB 2 bean

@Stateful
public PlaceOrderBean implements PlaceOrder {
..
@EJB
public ChargeCreditHome creditHome;
..
void chargeCreditCard(){

  ChargeCredit chargeCredit = creditHome.create();

  String confirmationNo = chargeCredit.add(billingInfo, amount);
..
}

Note: ChargeCredit and ChargeCreditHome are the local and remote interfaces, use create method to get a reference to the remote interface and then invoke the desired business method.

Invoke a EJB 2 entity bean @Stateless
public PlaceBidBean implements PlaceBid {
  ..
  @EJB
  public BidLocalHome bidLocalHome;
  ...
  BidLocal bidLocal = bidLocalHome.create(BidDTO);
  ...
}

Dependency injection/JNDI lookup can be summarized in the table below

EJB 2.x
EJB 3

Define resource-ref in ejb-jar.xml

Lookup resource

Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup("java:/comp/env/ActionBazaarDS");
Connection conn = ds.getConnection();

Can use dependency injection

@Resource(name="ActionBazaarDS")
private DataSource ds;

Connection conn = ds.getConnection();

You can also call a EJB 3 bean (session and JPA) from a EJB 2 bean. however if you server does not support EJB 2 dependency injection, you must use good old-fashioned JNDI lookup to access the EJB 3 session bean and the EJB 3 EntityManager from EJB 2 bean.

Using EJB 3 session beans from EJB 2 <session>
  <ejb-name>PlaceBidBean</ejb-name>
  <home>actionbazaar.buslogic.PlaceBidHome</home>
  <remote>actionbazaar.buslogic.PlaceBid</remote>
  <ejb-class>actionbazaar.buslogic.PlaceBidBean</ejb-class>
  <session-type>stateless</session-type>
  ...
  <ejb-local-ref>
    <ejb-ref-name>ejb/CheckCredit</ejb-ref-name>
    <ejb-ref-type>Session</ejb-ref-type>
    <local>kabadibazaar.busilogic.CheckCredit</local>
    <ejb-link>kabadibazaar-ejb.jar#CheckCreditBean</ejb-link>
  <ejb-local-ref>
</session>
Using the EJB 3 JPA from EJB 2 <session>
  <ejb-name>PlaceBidBean</ejb-name>
  ...
  <persistence-context-ref>
    <persistence-context-ref-name>
      ActionBazaarEntityManager
    </persistence-context-ref-name>
    <persistence-unit-name>actionBazaar</persistence-unit-name>
  </persistence-context-ref>
</session>

There are a number of reasons why you might want to upgrade session beans

All you have to do is the following

Here is a table to summarize the changes between EJB 2 and EJB 3

Components of a Session Bean EJB 2 EJB 3
Remote or local component interface Extends either EJBObject or EJBLocalObject Business interface (POJI)
Home interface Extends EJBHome or EJBLocalHome Optional for maintaining EJB 2 client view. Candidate for removal
Bean Class implements javax.ejb.SessionBean Implements business interface

The lifecycle methods have changed as well, the table below details both versions

Bean Type EJB 2 Methods EJB 3 Methods
Stateless, Stateful ejbCreate Constructor
Stateless, Stateful ejbPostCreate Method annotated with @PostConstruct
Stateful ejbPassivate Method annotated with @PrePassivate
Stateful ejbActivate Method annotated with @PostActivate
Stateless, Stateful ejbRemove Method annotated with @PreDestroy
Stateful remove method in home interface Method annotated with @Remove
Stateful create method in home interface Method annotated with @Init if the EJB 3 bean has a home interface
Stateless ejbTimeout Method annotated with @Timeout

EJB 2 does not define any default transactions and security settings for EJB's. You have to specify the definitions yourself for each and every bean method in a session bean, if you don't you will see different behaviors in different EJB containers. EJB 3 defines CMT as the default transaction management type for a bean and REQUIRED as the default transaction attribute for bean methods. Therefore you only need to change when REQUIRED needs to be changed.

MDB's (message-driven beans) can be easy migrated to EJB 3, you do not require to implement the javax.ejb.MessageDrivenBean interface and you may use the @MessageDriven and @ActivationConfigProperty annotations instead.

The most difficult task is migrating CMP entity beans to the EJB 3 JPA and requires careful planning.CMP entity beans are coarse-gained data objects where as EJB 3 entities are lightweight domain objects that represent fine-grained data, they also lack remoteness, declarative security and transactions but this is probably a good thing. You should redesign your design model when migrating entities to EJB 3, thus you can take advantage of the OO features that EJB 3 offers.

EJB 2 entity

public abstract class UserBean implements EntityBean {

  private EntityContext context;

  public abstract String getUserId();
  public abstract String getFirstName();
  ... another 20+ getters/setter methods ...
  ...

  public String ejbCreate() { return null; }

  public void ejbPostCreate() {}

  public void setEntityContext(EntityContext context) throws EJBException { context = context; }

  public void unsetEntityContext() throws EJBException { context = null; }

  public void ejbRemove() throws EJBException, RemoveException {}

  public void ejbActivate() throws EJBException {}

  public void ejbPassivate() throws EJBException {}

  public void ejbLoad() throws EJBException {}

  public void ejbStore() throws EJBException {}

}

EJB 3 entity @Entity
@Table(name="USERS")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "USER_TYPE",
                     discriminatorType = DiscriminatorType.STRING,
                     length = 1)

public class User {}

@Entity
@DiscriminatorValue(value = "S")
public class Seller extends User {}

@Entity
@DiscriminatorValue(value = "B")
public class Bidder extends User {}

After refactoring the entities you should not need to change the database structure, but you need thoroughly test your application.

DTO (data transfer object) are no longer required because entities are themselves POJO's that can be transferred between clients and servers. Also relationships are defined in XML files which can now be converted to annotations (see Domain Models for more information).

DTO public class UserDTO implements Serializable {

  private String userId;
  private Date birthDate;
  ...

  private Collection bids;

  public UserDTO() {}

  public String getUserId() { return userId; }

  public void setUserId(String userId;) { this.userId = userId; }

  ...
}
EJB 3 entity @Entity
@Table(name = "USERS")
public class User implements Serializable {

  @Id
  @Column(name = "USER_ID")
  private String userId;
  private Date birthDate;
  ...
}

In EJB 2 the home interface acted as a factory interface to provide methods to create, remove and find entity bean instances. Clients used these methods to persist, remove and query bean instances. In the EJB 3 world applications should use the EntityManager API. You can migrate the home interface to a session facade by moving all factory methods such as create, remove and find to this facade.

UserLocalHome interface

public interface UserLocalHome {

  User create(String userId, String firstName, String lastName, String userType) throws CreateException;

  User findByPrimaryKey(String primaryKey) throws FinderException;

  Collection findAll() throws FinderException;

  Collection findByFirstName(String name) throws FinderException;
}

Session implementation @Stateless
public class UserLocalHomeBean implements UserLocalHome {

  @PersistenceContext
  EntityManager em;

  User create(String userId, String firstName, String lastName, String userType) throws CreateException   {
    User user = new User(userId, firstName, lastName, userType);

    try {
      em.persist(user);
    } catch (Exception ex) {
        throw new CreateException(e.getMessage());
    }

    return user;
  }

  User findByPrimaryKey(String primaryKey) throws FinderException {
  
    try {
      return (User) em.find(User.class, primaryKey);
    } catch (Exception ex) {
      throw new FinderException(e.getMessage());
    }
  }
}

Like I said at the beginning, I am not to familiar with EJB 2 and if I do get involved with an EJB 2 I upgrade to EJB 3 I will update this document accordingly