Enterprise Java Development@TOPIC@
EJB will instantiate persistence context if does not yet exist
Stateless EJBs may only have transaction-scope persistence contexts
Stateful EJBs may have transaction-scope or extended persistence contexts
EJBs can share persistence contexts
Stateless EJB can propagate its tx-scope persistence context to a called EJB
Stateful EJB can propagate its tx-scoped or extended persistence context to a called EJB
Stateless EJB can work with extended persistence context provided by upstream Stateful client
Stateful EJB cannot transform propagated tx-scope persistence context into an extended
EJB Facade can act as sharing point for common persistence context
Each interaction is independent of the next except for what is stored in DB and at client
Figure 99.2. Stateless EJB Example Check-in
@Override
public Guest checkIn(Guest guest, Room room) throws RoomUnavailableExcepton {
Room hotelRoom = dao.getRoom(room.getNumber());
dao.addGuest(guest);
hotelRoom.setOccupant(guest);
return guest;
}
(Error checking removed)
Locate specific room
Add guest to DB
Associate room with guest
Figure 99.3. Stateless EJB Example Client Check-in
List<Room> availableRooms = hotelMgmt.getAvailableRooms(null, 0, 0);
logger.debug("we have {} available rooms", availableRooms.size());
List<Guest> members = new ArrayList<Guest>(availableRooms.size());
int i=0;
for (Room room: availableRooms) {
Guest member = new Guest("member " + i++);
member = hotelMgmt.checkIn(member, room);
members.add(member);
}
Client performs actions one at a time
Figure 99.4. EJB Gets Available Rooms from DB
client: hotelMgmt.getAvailableRooms(null, 0, 0);
[HotelMgmtEJB] *** HotelMgmtEJB(1543684701):init ***
[stdout] Hibernate:
[stdout] select
[stdout] room0_.ROOM_NUMBER as ROOM_NUM1_2_,
[stdout] room0_.FLOOR_ID as FLOOR_ID2_2_,
[stdout] room0_.OCCUPANT_ID as OCCUPANT3_2_
[stdout] from
[stdout] EJBJPA_ROOM room0_
[stdout] where
[stdout] room0_.OCCUPANT_ID is null
[stdout] order by
[stdout] room0_.ROOM_NUMBER asc
[HotelMgmtEJB] *** HotelMgmtEJB(1543684701):destroy ***
Persistence context created
Available rooms loaded into persistence context
Result is returned
Persistence context destroyed
Figure 99.5. EJB Gets Specific Room
Room hotelRoom = dao.getRoom(room.getNumber());
[HotelMgmtEJB] *** HotelMgmtEJB(613346935):init ***
[HotelMgmtEJB] checkin(guest=Guest [id=0, name=member 0], room=Room [number=1, occupant=null])
[stdout] Hibernate:
[stdout] select
[stdout] room0_.ROOM_NUMBER as ROOM_NUM1_2_0_,
[stdout] room0_.FLOOR_ID as FLOOR_ID2_2_0_,
[stdout] room0_.OCCUPANT_ID as OCCUPANT3_2_0_
[stdout] from
[stdout] EJBJPA_ROOM room0_
[stdout] where
[stdout] room0_.ROOM_NUMBER=?
Persistence context created
Specific room loaded into persistence context
Guest inserted into DB
Figure 99.6. EJB Adds Guest
dao.addGuest(guest);
[stdout] Hibernate:
[stdout] call next value for hibernate_sequence
[HotelMgmtEJB] *** HotelMgmtEJB(613346935):destroy ***
[stdout] Hibernate:
[stdout] insert
[stdout] into
[stdout] EJBJPA_GUEST
[stdout] (name, GUEST_ID)
[stdout] values
[stdout] (?, ?)
Guest inserted into DB
The transaction does not end until existing the Stateless EJB method. That means any DB constraint violations will not occur within the context of the EJB call that caused it unless you call em.flush() (as a form of debug) prior to exiting the business method.
Figure 99.7. EJB associates Guest with Room
hotelRoom.setOccupant(guest);
[stdout] Hibernate:
[stdout] update
[stdout] EJBJPA_ROOM
[stdout] set
[stdout] FLOOR_ID=?,
[stdout] OCCUPANT_ID=?
[stdout] where
[stdout] ROOM_NUMBER=?
Room.occupant foreign key updated
Guest returned to client
Persistence context destroyed
State can be cached in-memory on server-side
Facade EJB propagates persistence context to called EJBs
Figure 99.8. Example Stateful Reservation EJB Caches Guest Requests for Client
@Stateful
public class ReservationEJB implements ReservationRemote {
@PersistenceContext(unitName="ejbjpa-hotel", type=PersistenceContextType.EXTENDED)
private EntityManager em;
List<Guest> guests = new LinkedList<Guest>();
@Override
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public int addGuest(Guest guest) {
logger.debug("addGuest={}", guest);
if (guest!=null) {
guests.add(guest);
em.persist(guest); //<== no transaction active yet
}
return guests.size();
}
Stateful EJB injects @PersistenceContext -- propagated in downstream EJB calls
Extended persistence context -- may be associated with zero or more transactions
Performing in-memory actions outside of transaction boundary
Figure 99.9. Example Stateful Reservation EJB Acting on Cached State
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Remove
public List<Guest> reserveRooms() throws RoomUnavailableExcepton {
List<Room> rooms = hotelMgmt.getAvailableRooms(null, 0, guests.size());
//assign each one of them a room
em.flush(); //<== flush guests persisted outside of a TX for demonstration
List<Guest> completed = new ArrayList<Guest>(guests.size());
Iterator<Room> roomItr = rooms.iterator();
for (Guest guest: guests) {
Room room = roomItr.next();
try {
guest = hotelMgmt.checkIn(guest, room); //<== will attempt to also persist guest
completed.add(guest);
} catch (RoomUnavailableExcepton ex) {
//rollback any previous reservations
ctx.setRollbackOnly();
throw ex;
}
}
return completed;
}
Method executed within active JTA transaction with persistence context associated with transaction
Guests already managed but will be fully persisted in this method
Calls to Stateless HotelMgmtEJB executed on this EJB's persistence context
@Remove indicates stateful instance to be discarded after method called
Figure 99.10. Stateful EJB Example Client Check-in
int availableRooms = hotelMgmt.getAvailableRooms(null, 0, 0).size();
logger.debug("we have {} available rooms", availableRooms);
ReservationRemote checkin = (ReservationRemote) jndi.lookup(reservationJNDI);
for (int i=0; i<availableRooms; i++) {
Guest member = new Guest("member " + i);
int count=checkin.addGuest(member);
logger.debug("we have {} in our group so far", count);
}
List<Guest> guests = checkin.reserveRooms();
Multiple requests are issued to Stateful EJB
Specific method(s) act on that state
Figure 99.11. Stateful EJB Persists Guests Prior to Active JTA Transaction
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
...
em.persist(guest); //<== no transaction active yet
[ReservationEJB] *** ReservationEJB(12230192):init ***
[ReservationEJB] addGuest=Guest [id=0, name=member 0]
[stdout] Hibernate:
[stdout] call next value for hibernate_sequence
[ReservationEJB] addGuest=Guest [id=0, name=member 1]
[stdout] Hibernate:
[stdout] call next value for hibernate_sequence
[ReservationEJB] addGuest=Guest [id=0, name=member 2]
[stdout] Hibernate:
[stdout] call next value for hibernate_sequence
[ReservationEJB] addGuest=Guest [id=0, name=member 3]
[stdout] Hibernate:
[stdout] call next value for hibernate_sequence
em.persist() outside of transaction causing causing sequence call but no table inserts
Figure 99.12. Stateless EJB Populates Propagated Persistence Context with Rooms
List<Room> rooms = hotelMgmt.getAvailableRooms(null, 0, guests.size());
[HotelMgmtEJB] *** HotelMgmtEJB(1162892707):init ***
[stdout] Hibernate:
[stdout] select
[stdout] room0_.ROOM_NUMBER as ROOM_NUM1_2_,
[stdout] room0_.FLOOR_ID as FLOOR_ID2_2_,
[stdout] room0_.OCCUPANT_ID as OCCUPANT3_2_
[stdout] from
[stdout] EJBJPA_ROOM room0_
[stdout] where
[stdout] room0_.OCCUPANT_ID is null
[stdout] order by
[stdout] room0_.ROOM_NUMBER asc limit ?
[HotelMgmtEJB] *** HotelMgmtEJB(1162892707):destroy ***
Downstream Stateless HotelMgmtEJB queried for rooms
Rooms loaded into persistence context
Figure 99.13. Stateful EJB Method Activates Transaction and flush()es Guests in EntityManager Cache
@TransactionAttribute(TransactionAttributeType.REQUIRED)
...
logger.debug("reserving {} rooms for {} guests", rooms.size(), guests.size());
em.flush(); //<== flush guests persisted outside of a TX for demonstration
[ReservationEJB] reserving 4 rooms for 4 guests
[stdout] Hibernate:
[stdout] insert
[stdout] into
[stdout] EJBJPA_GUEST
[stdout] (name, GUEST_ID)
[stdout] values
[stdout] (?, ?)
SQL inserts issued to DB during flush (requires transaction active)
Figure 99.14. Stateful EJB uses pre-loaded Rooms and Guests without accessing DB (until association)
guest = hotelMgmt.checkIn(guest, room); //<== will attempt to also persist guest
[HotelMgmtEJB] *** HotelMgmtEJB(1086999670):init ***
[HotelMgmtEJB] checkin(guest=Guest [id=65, name=member 0], room=Room [number=1, occupant=null])
[HotelMgmtEJB] *** HotelMgmtEJB(1086999670):destroy ***
[HotelMgmtEJB] *** HotelMgmtEJB(99312186):init ***
[HotelMgmtEJB] checkin(guest=Guest [id=66, name=member 1], room=Room [number=100, occupant=null])
[HotelMgmtEJB] *** HotelMgmtEJB(99312186):destroy ***
[HotelMgmtEJB] *** HotelMgmtEJB(545116383):init ***
[HotelMgmtEJB] checkin(guest=Guest [id=67, name=member 2], room=Room [number=102, occupant=null])
[HotelMgmtEJB] *** HotelMgmtEJB(545116383):destroy ***
[HotelMgmtEJB] *** HotelMgmtEJB(605810979):init ***
[HotelMgmtEJB] checkin(guest=Guest [id=68, name=member 3], room=Room [number=201, occupant=null])
[HotelMgmtEJB] *** HotelMgmtEJB(605810979):destroy ***
[stdout] Hibernate:
[stdout] update
[stdout] EJBJPA_ROOM
[stdout] set
[stdout] FLOOR_ID=?,
[stdout] OCCUPANT_ID=?
[stdout] where
[stdout] ROOM_NUMBER=?
[stdout] Hibernate:
...
[ReservationEJB] *** ReservationEJB(12230192):destroy ***
Stateless HotelMgmtEJB accesses Rooms from EntityManager first-level cache -- no additional DB access
Stateless HotelMgmtEJB updates Room.occupant FK to reference Guest -- who is also already managed and in first-level cache
JTA Transaction committed by container after Stateful EJB method exits
Stateful EJB and persistence context also destroyed after method exits
Part of transaction ACID properties
Atomic
All or nothing
Consistent
Valid state
Isolation
Visibility of incomplete changes
Durable
Not forgotten once committed
Figure 99.15. Stateless EJB will Reject New Check-in when Occupied
@Override
public Guest checkIn(Guest guest, Room room) throws RoomUnavailableExcepton {
Room hotelRoom = dao.getRoom(room.getNumber());
if (hotelRoom==null) {
throw new RoomUnavailableExcepton(String.format("room [%d] does not exist", room.getNumber()));
}
if (hotelRoom.getOccupant()!=null) {
throw new RoomUnavailableExcepton(String.format("room is occupied by %s", hotelRoom.getOccupant()));
}
dao.addGuest(guest);
hotelRoom.setOccupant(guest);
return guest;
}
Rejects checkin when invalid
Completes check-in when valid
Transaction per call, no rolling back previous calls
Figure 99.16. Client uses up all Available Rooms
List<Guest> members = new ArrayList<Guest>(availableRooms.size());
int i=0;
for (Room room: availableRooms) {
Guest member = new Guest("member " + i++);
member = hotelMgmt.checkIn(member, room);
members.add(member);
}
Stateless EJB commits each of these check-ins
Figure 99.17. Client Attempts an Invalid Check-in
//try doing it again
Room room = availableRooms.get(0);
Guest member = new Guest("member " + i++);
try {
member = hotelMgmt.checkIn(member, room);
members.add(member);
fail("fail to detect bad checkin");
} catch (RoomUnavailableExcepton ex) {
logger.debug("expected exception making too many reservations:{}", ex.toString());
}
expected exception making too many reservations:info.ejava.examples.ejb.ejbjpa.bl.RoomUnavailableExcepton: room is occupied by Guest [id=103, name=member 0]
Additional check-in rejected -- nothing committed
Figure 99.18. Server still has Initial Committed Check-ins
logger.info("completed reservations for {} guests", members.size());
int availableRooms2 = hotelMgmt.getAvailableRooms(null, 0, 0).size();
logger.info("hotel has {} rooms available", availableRooms2);
assertEquals("", availableRooms.size()-members.size(), availableRooms2);
completed reservations for 4 guests hotel has 0 rooms available
Each check-in occured in own transaction
Later error did not impact previous completed transactions
Multiple resource actions part of same transaction
All-or-none performed
Figure 99.19. Client Issues Guests Up Front
ReservationRemote checkin = (ReservationRemote) jndi.lookup(reservationJNDI);
for (int i=0; i<availableRooms+1; i++) {
Guest member = new Guest("member " + i);
int count=checkin.addGuest(member);
logger.debug("we have {} in our group so far", count);
}
00:47:20 DEBUG HotelMgmtEJBIT:326 - we have 4 available rooms 00:47:20 DEBUG HotelMgmtEJBIT:332 - we have 1 in our group so far 00:47:20 DEBUG HotelMgmtEJBIT:332 - we have 2 in our group so far 00:47:20 DEBUG HotelMgmtEJBIT:332 - we have 3 in our group so far 00:47:20 DEBUG HotelMgmtEJBIT:332 - we have 4 in our group so far 00:47:20 DEBUG HotelMgmtEJBIT:332 - we have 5 in our group so far
Same stateful process as before -- but with one additional Guest (one too many)
Figure 99.20. Client Attempts to Check-in all Guests at Once
try {
checkin.reserveRooms();
fail("too many check-ins not detected");
} catch (RoomUnavailableExcepton ex) {
logger.debug("expected exception making too many reservations:{}", ex.toString());
}
Client attempts to check-in to all Rooms
Checked exception will be thrown
Figure 99.21. Stateful EJB Attempts to flush() Guests and Reserve all Rooms
@TransactionAttribute(TransactionAttributeType.REQUIRED)
...
em.flush(); //<== flush guests persisted outside of a TX for demonstration
[ReservationEJB] reserving 4 rooms for 5 guests
[stdout] Hibernate:
[stdout] insert
[stdout] into
[stdout] EJBJPA_GUEST
[stdout] (name, GUEST_ID)
[stdout] values
[stdout] (?, ?)
...
Five (5) Guests are flushed to database prior to the rollback
Figure 99.22. Stateful EJB rolls back Transaction on Stateless HotelMgmtEJB Exception
@Resource
private SessionContext ctx;
} catch (RoomUnavailableExcepton ex) {
//rollback any previous reservations
ctx.setRollbackOnly();
throw ex;
}
[ReservationEJB] *** ReservationEJB(271512057):destroy ***
expected exception making too many reservations:info.ejava.examples.ejb.ejbjpa.bl.RoomUnavailableExcepton: found on 4 out of 5 required
Persisted Guests removed from database as a part of transaction rollback
Early check-ins never flushed to DB -- discarded as part of rollback
Figure 99.23. Client Checks and finds Rooms Still Available -- all Check-ins Rolled Back
int availableRooms2 = hotelMgmt.getAvailableRooms(null, 0, 0).size();
logger.info("hotel has {} rooms available", availableRooms2);
assertEquals("unexpected room count", availableRooms, availableRooms2);
hotel has 4 rooms available
All check-ins associated with Rooms removed as a part of rollback
One concurrency strategy
Create/wait-for a lock on data up-front, prior to getting the data
Figure 99.24. Stateful Scenario Ontaining Pessamistic Lock with Rooms
@Override
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Remove
public List<Guest> reserveRoomsPessimistic() throws RoomUnavailableExcepton {
List<Room> rooms = em.createQuery(...)
.setFirstResult(0)
.setMaxResults(guests.size())
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.setHint("javax.persistence.lock.timeout", 5000) //can also be set globally as persistence.xml property
.getResultList();
return reserveRooms(rooms);
}
[stdout] select [stdout] room0_.ROOM_NUMBER as ROOM_NUM1_2_, [stdout] room0_.FLOOR_ID as FLOOR_ID2_2_, [stdout] room0_.OCCUPANT_ID as OCCUPANT3_2_ [stdout] from [stdout] EJBJPA_ROOM room0_ [stdout] where [stdout] room0_.OCCUPANT_ID is null [stdout] order by [stdout] room0_.ROOM_NUMBER asc limit ? for update
setLockMode(LockModeType.PESSIMISTIC_WRITE) - locks row (or table) for remainder of transaction
select ... FOR UPDATE issued for query