There are 3 types of EJB Beans: Session Beans, Entity Beans, and Message Driven Beans. The Entity Bean has been deprecated and replaced by the Entity Bean in JPA. The Message Driven Bean is used in message driven systems like Web Services. The beans we implement with JSF are Session Beans. (One item of note here is that we do not access an EJB directly from JSF EL as we do the CDI or Managed Bean, although as you will see, we can easily access it indirectly.)
There are three types of Session Bean: Stateless, Stateful, and Singleton. The Singleton is not used in this tutorial, but briefly, there is only one per JVM or container. Creation can occur as the container starts up. Access can be controlled via the container or the bean. Locking mechanisms can be put in place. And generally all the issues of any other singleton exist with the Singleton Bean. State is shared globally.
As implied, a Stateless Bean holds no state, and a Stateful Bean does. Stateful Beans are similar to the SessionScoped bean we saw with the JSF Beans, but there are some major differences. We'll see some of these differences as we review the example.
When we interact with persistence in an EJB, the way we do so differs depending on which type of Session Bean we are using. If we are using a stateful or singleton session bean we can use an extended persistence context. This means the context is not closed at the end of the method call, or at the end of a transaction. The context can be managed by the bean as opposed to being managed by the container. The same is true with transactions: we can manage our own transactions. When the persistence context is closed, all entities still in existence become detached. To persist them, we have to reattach them. Let me explain this another way: When we open a connection to the database, we create a persistence context. This persistence context is a cache of records the ORM (Hibernate) is managing. When we flush the cache, we are no longer managing those records. If we are using a Stateless Bean, no state is kept, so the context is automatically flushed. But, if we have an entity that is representative of a record in the database and we keep that entity longer then we keep our persistence context, we have to tell the next persistence context that we want it to manage or re-manage that entity. The process of managing an entity that is outside the scope of the persistence context is called attaching an entity. Now that was confusing - see the example at the bottom, it will be clearer, I promise.
In this tutorial, most of our examples are with stateless session beans to demonstrate how to handle persistence in a stateless environment. There are issues with doing this. There are no inherent locking mechanisms, such that if two users modify the same record, the last one to commit his changes will "win" and overwrite the changes made by the other user. This is not to say that this method is incorrect, but to say that your requirements will determine what type of Session Bean and persistence context you will use. A Stateful Bean has its own set of issues. You must think about transaction management (although, you can still use container managed transactions); you must destroy your bean and close your resources; you must think about issues with caching, flushing, locking, synchronization, and threading. Also, there are scalability issues. Both bean types are powerful, useful and will be selected based on your applications needs. I could have written this sample application entirely with Stateful or Stateless beans - but by choosing one, it determines other aspects of the application.
Imagine for a moment you are Amazon.com with a gazillion servers and a gazillion users. All of them want to buy the new Foo unit for Christmas this year. Your database servers are clustered together to be able to handle all the transactions simultaneously. No user can obtain a lock and hold it while they shop for hours - thus holding up the thousands that wish to purchase that item, but yet you have to hold the state of the shopping cart so they can purchase the item when they are done shopping. So, how are you going to implement it. One method would be to have a SessionScoped bean hold what the user is going to purchase, and when the order is commited, a Stateless Bean will connect to the database and commit the changes. It can validate the order at the same time. Thus, it can decrement the inventory, submit the order, and charge the account all in one transaction without any locks. If it fails, it simply rolls back and reports to the user that the inventory is no longer there.
Now let us say you were downgrading the security clearance of an account. You might want to lock the record until you were finished authorizing, or deauthorizing his privileges. You wouldn't want him accessing information while you were in the midst of changing his privileges would you? To lock the record, you would need a Stateful Bean.
These contrived examples are for use case demonstration, your use cases will determine the proper bean type to use. In this tutorial, I mostly used the Stateless Bean although I give an example of both. Because most of the functionality was implemented with the Stateless Bean, the Stateful Bean does not quite use all the features you may find in an application that primarily uses Stateful Beans, but the example should suffice for demonstrating the differences and proper use of the two bean types.
EJBs may be accessed locally or remotely. In earlier versions, all EJBs had to have an interface defined, now only remotely accessed EJBs must have an interface defined. EJBs with an interface are called Interface Beans, and the others are Interfaceless Beans. To inject an Interface Bean, you use the interface name. You can define any bean to be an Interface Bean just by implementing an interface for it.
Here we implement a Stateful Bean:
LoginBean.java
package com.sample;
import java.io.Serializable;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.ejb.LocalBean;
import javax.ejb.Remove;
import javax.ejb.SessionContext;
import javax.ejb.Stateful;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;
import javax.transaction.UserTransaction;
@Stateful
@LocalBean
@TransactionManagement(TransactionManagementType.BEAN)
public class LoginBean implements Serializable {
public static final String NOT_LOGGED_IN = "Not Logged In";
private String name;
private String password;
private String userName;
private UserEntity user;
@PersistenceContext(unitName = "JSFDemoPU", type = PersistenceContextType.EXTENDED)
private EntityManager em;
@Resource
SessionContext sessionContext;
@PostConstruct
public void init() {
userName = NOT_LOGGED_IN;
}
public boolean authorize() {
if (user != null && user.getName().equals(userName)) {
user.setCounter(user.getCounter() + 1);
} else {
Query q = em.createQuery("select u from UserEntity u where name=?0 and password=?1");
q.setParameter(0, name);
q.setParameter(1, password);
List list = q.getResultList();
if (list.isEmpty()) {
userName = NOT_LOGGED_IN;
} else {
user = (UserEntity) list.get(0);
userName = user.toString();
user.setCounter(user.getCounter() + 1);
}
}
return true;
}
@Remove
public void logout() {
UserTransaction transaction = sessionContext.getUserTransaction();
try {
transaction.begin();
if (user != null) {
em.persist(user);
}
transaction.commit();
} catch (Exception e) {
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
And the supporting enity class: UserEntity.java
package com.sample;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="Users")
public class UserEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(name="name", unique=true)
private String name;
@Column(name="password")
private String password;
private int counter;
public UserEntity() {
}
public UserEntity(String name, String password) {
this.name = name;
this.password = password;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (object==null || !(object instanceof UserEntity)) {
return false;
}
if (this == object) {
return true;
}
UserEntity other = (UserEntity) object;
if (id == null || other.id == null) {
return false;
}
return this.id.equals(other.id);
}
@Override
public String toString() {
return id + ": " + name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getCounter() {
return counter;
}
public void setCounter(int counter) {
this.counter = counter;
}
}
The first annotation declares this to be a @Stateful bean. The second isn't required since it is the default, but demonstrates that this bean is @Local vs a @Remote bean. The third, @TransactionManagement, determines what will manage the transactions. Here, we have chosen that the bean will manage them instead of the default which is the container. The container managed transactions we will explore in the Stateless Bean example.
We have declared the class to be Serializable. We do this because the container may "passivate" the bean, which means it will write it to disk if it hasn't been used in a while and retrieve it when necessary. To do this, the bean must be Serializable (remember, to be Serializable, the fields contained within the object must be primitives or Serializable objects).
We inject our persistence unit, and a session context. The persistence unit is extended so that the context will continue managing the entities even after a transaction is committed. And, the session context is needed in order to create our transaction. Our setup is finished by initializing our bean with the @PostConstruct method,
We have declared the class to be Serializable. We do this because the container may "passivate" the bean, which means it will write it to disk if it hasn't been used in a while and retrieve it when necessary. To do this, the bean must be Serializable (remember, to be Serializable, the fields contained within the object must be primitives or Serializable objects).
We inject our persistence unit, and a session context. The persistence unit is extended so that the context will continue managing the entities even after a transaction is committed. And, the session context is needed in order to create our transaction. Our setup is finished by initializing our bean with the @PostConstruct method,
Now we have a method called authorize, which retrieves a record from the database based on the supplied username and password (Note: this is not meant to be secure, we will deal with security in another project). Once retrieved, each time the user authorizes, a counter is incremented, but we do not persist the data. If this were a stateless bean or we simply used the defaults, the counter would be committed to the database at the end of the method call. But, we don't want to commit our changes yet. Maybe there are other things that must occur before we commit. In a Stateful bean, we can have complete control over the context, the transactions, the rollback, locking, etc. here, no transaction is initiated until the logout method when we finally update the database with the current LoginUser entity.
The logout method is annotated with the @Remove. This designates this EJB will be sent to garbage collection at the end of the method call. Every Stateful Bean must have at least one @Remove method. Since the container doesn't know when to remove the Bean, you must call the designated @Remove method to tell the container when you are done. You could let the bean time out, but that doesn't stop you from needing the @Remove method, and is sloppy programming. As we go through the tutorial, you will see that we call the logout method from our CDI bean if the CDI session ends. This will clean up the Stateful bean.
When we persist our entity, we started the transaction and called em.persist(user) wrapped in a try catch block (since the transactions may throw an exception). But, notice all we did was call persist because the persistence context was still managing the user entity.
This is a great tutorial. Still working my way through it but it's sooooo much easier to understand than the others I've been reading. Thanks!
ReplyDeleteWell thank your for your comment. You are the first to comment in the whole blog. Glad you liked it.
Delete