About
Stateless EJB session beans as Data Access Object (DAO) implementation with JPA.
Articles Related
Transaction Type
RESOURCE_LOCAL
You must use the EntityManagerFactory to get an EntityManager
The resulting EntityManager instance is a PersistenceContext/Cache
An EntityManagerFactory can be injected via the @PersistenceUnit annotation only (not @PersistenceContext)
You are not allowed to use @PersistenceContext to refer to a unit of type RESOURCE_LOCAL
You must use the EntityTransaction API to begin/commit around every call to your EntityManger Calling entityManagerFactory.createEntityManager() twice results in two separate EntityManager instances and therefor two separate PersistenceContexts/Caches.
It is almost never a good idea to have more than one instance of an EntityManager in use (don't create a second one unless you've destroyed the first).
Steps
DAO
Interface
Interface definition with the JPA Entity
package com.oracle.ticketsystem.dao;
import java.util.List;
import com.oracle.ticketsystem.beans.Ticket;
/**
* A DAO that handles the ticket related requests,
* for example, adding a new ticket, updating an
* existing ticket, getting a specific ticket, and
* getting list of tickets by specific state.
*
*/
public interface ITicketDao {
/**
* Adds the given ticket.
*
* @param ticket to add
* @return <code>Ticket<code> reference to ticket being added
*/
public Ticket add(Integer productId,
String customerName, String customerEmail,
String title, String description);
/**
* Updates the ticket specified with the given ID.
* <p>
* Ignores the update request (and returns null), if could not find
* Ticket for the given ID.
* <p>
* It updates the current state of the ticket and also adds an entry
* for the ticket history.
*
* @param ticketId
* ID of the ticket to update
* @param technicianId
* ID of technician who is updating the ticket
* @param comment
* string comment about the update
* @param state
* state to update for the ticket
* @return the updated <code>TicketType<code>
*/
public Ticket update(Integer ticketId, String technicianId,
String comment, String state);
/**
* Returns <code>Ticket<code> reference for the given ticket ID,
* or null if does not found.
*
* @param ticketId
* ID of ticket to retrieve
* @return <code>Ticket<code> reference for the given ticket ID if found,
* otherwise null.
*/
public Ticket get(Integer ticketId);
/**
* Returns <code>List<code> of tickets assigned to technician with the given ID.
*
* @param technicianId get tickets assigned to technician with the given ID
* @return <code>List<code> of tickets.
*/
public List<Ticket> getTicketsOwnedByTechnician(String technicianId);
/**
* Returns <code>List<code> of open tickets i.e. either NEW or OPEN
*
* @return a list of open tickets i.e. either NEW or OPEN
*/
public List<Ticket> getOpenTickets();
/**
* Removes the ticket with the given ticket ID.
* @param ticketId
* the ID of the ticket to be removed
*/
public void remove(Integer ticketId);
}
Implementation
package com.oracle.ticketsystem.dao.impl;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import com.oracle.ticketsystem.beans.Product;
import com.oracle.ticketsystem.beans.Technician;
import com.oracle.ticketsystem.beans.Ticket;
import com.oracle.ticketsystem.beans.Tickethistory;
import com.oracle.ticketsystem.dao.ITicketDao;
public class TicketJPADao extends BaseJPADao implements ITicketDao {
/**
* Date pattern used across the application.
*/
public static final String DATE_PATTERN = "MM/dd/yyyy hh:mm:ss aa"; //$NON-NLS-1$
/**
* Default no-arg constructor
*/
public TicketJPADao() {
}
@Override
public Ticket add(Integer productId,
String customerName, String customerEmail,
String title, String description) {
Product product = getEntityManager().find(Product.class, productId);
if(product == null) {
throw new RuntimeException("While adding a new ticket, " +
"could not find reference to the given product Id: " + productId);
}
Ticket ticket = new Ticket();
ticket.setProduct(product);
ticket.setCustomername(customerName);
ticket.setCustomeremail(customerEmail);
ticket.setTitle(title);
ticket.setDescription(description);
ticket.setState("NEW"); // always NEW state
SimpleDateFormat dtFormat = new SimpleDateFormat(DATE_PATTERN);
ticket.setSubmissiondate(dtFormat.format(new Date()));
Long maxId = getMaxId("SELECT max(t.id) FROM Ticket t");
// setting the ticket Id
ticket.setId( (int) ((maxId == null) ? 0 : maxId + 1));
EntityTransaction t = getEntityManager().getTransaction();
t.begin();
getEntityManager().persist(ticket);
t.commit();
return ticket;
}
@Override
public Ticket get(Integer ticketId) {
return getEntityManager().find(Ticket.class, ticketId);
}
@SuppressWarnings("unchecked")
@Override
public List<Ticket> getTicketsOwnedByTechnician(String technicianId) {
Query query = getEntityManager().createQuery("SELECT t from Ticket t "+
"WHERE t.technician.id = :technicianId");
query.setParameter("technicianId", technicianId);
return query.getResultList();
}
@Override
public Ticket update(Integer ticketId, String technicianId, String comment,
String state) {
EntityTransaction t = getEntityManager().getTransaction();
t.begin();
Ticket ticket = get(ticketId);
if(ticket == null) {
return null;
}
ticket.setState(state);
Technician technician = null;
if(technicianId != null) {
technician = getEntityManager().find(Technician.class, technicianId);
if(technician == null) {
throw new RuntimeException("No technician found for the ID '" +
technicianId + "'");
}
}
ticket.setTechnician(technician);
Tickethistory ticketHistory = new Tickethistory();
Long maxTicketHistoryId = getMaxId("SELECT max(h.id) FROM Tickethistory h");
// setting the ticketHistory Id
ticketHistory.setId( (int) ((maxTicketHistoryId == null) ? 0 : maxTicketHistoryId + 1));
if(technician != null) {
ticketHistory.setTechnician(technician);
}
ticketHistory.setState(state);
ticketHistory.setComments(comment);
ticketHistory.setTicket(ticket);
SimpleDateFormat dtFormat = new SimpleDateFormat(DATE_PATTERN);
ticketHistory.setUpdatedate(dtFormat.format(new Date()));
getEntityManager().persist(ticketHistory);
ticket.getTicketHistory().add(ticketHistory);
t.commit();
return ticket;
}
@SuppressWarnings("unchecked")
@Override
public List<Ticket> getOpenTickets() {
Query openTicketsQuery = getEntityManager().createQuery(
"SELECT t FROM Ticket t " +
"WHERE t.state = :newState " +
"OR t.state = :openState");
openTicketsQuery.setParameter("newState", "NEW");
openTicketsQuery.setParameter("openState", "OPEN");
return openTicketsQuery.getResultList();
}
@Override
public void remove(Integer ticketId) {
Ticket ticket = get(ticketId);
if(ticket != null) {
EntityTransaction trx = getEntityManager().getTransaction();
trx.begin();
for(Tickethistory ticketHistory : ticket.getTicketHistory()) {
getEntityManager().remove(ticketHistory);
}
ticket.getTicketHistory().clear();
getEntityManager().remove(ticket);
trx.commit();
}
}
private long getMaxId(String maxQuery) {
Query maxIdQuery = getEntityManager().createQuery(maxQuery);
Long maxId = 1L;
if( (maxIdQuery.getResultList() != null) && (maxIdQuery.getResultList().size() > 0) ) {
maxId = (Long)maxIdQuery.getResultList().get(0);
}
return maxId;
}
}
where BaseJPADao is the following base class to call the EntityManager
EntityManager
A persistence context is a set of entity instances in which for any persistent entity identity there is a unique entity instance. Within the persistence context, the entity instances and their lifecycle are managed.
Factory
This class creates singleton instances of:
- EntityManagerFactory
- and EntityManager
using the persistence unit name as defined in the persistence.xml.
It has also a method to close the EntityManagerFactory for necessary clean-up during the application shutdown.
package com.oracle.ticketsystem.dao.impl;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
/**
* A JPA DAO factory for providing reference to EntityManager.
*
*/
public class JPADaoFactory {
private static final String PERSISTENCE_UNIT_NAME = "TroubleTicketSystemServer";
private static EntityManagerFactory entityManagerFactory;
private static EntityManager entityManager;
/**
* Returns reference to EntityManager instance. If null then create it
* using the persistence unit name as defined in the persistence.xml
*
* @return EntityManager
*/
public static EntityManager createEntityManager() {
if(entityManager == null) {
entityManagerFactory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
entityManager = entityManagerFactory.createEntityManager();
}
return entityManager;
}
public static void close() {
entityManager.close();
entityManagerFactory.close();
}
}
Base class
A base class for JPA DAO implementation classes.
The BaseJPADao class provides an accessor method for the EntityManager.
package com.oracle.ticketsystem.dao.impl;
import javax.persistence.EntityManager;
public class BaseJPADao {
/**
* Returns JPA EntityManager reference.
* @return
*/
public EntityManager getEntityManager() {
return JPADaoFactory.createEntityManager();
}
}
JUnit Test
Junit Test:
package com.oracle.ticketsystem.dao.tests;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import com.oracle.ticketsystem.beans.Ticket;
import com.oracle.ticketsystem.dao.ITicketDao;
import com.oracle.ticketsystem.dao.impl.TicketJPADao;
public class TicketDaoTest {
private ITicketDao ticketDao = null;
private static Integer testTicketId = 0;
@Before
public void setUp() throws Exception {
ticketDao = new TicketJPADao();
}
@Test
public void testAdd() {
// adding a test ticket
Ticket ticket = ticketDao.add(1001, "Mark", "[email protected]", "TestTicket",
"Ticket about the test");
assertTrue("Could not add test ticket.", (ticket.getId() > 0));
testTicketId = ticket.getId();
}
@Test
public void testGet() {
// finding a test ticket
Ticket ticket = ticketDao.get(testTicketId);
assertNotNull("Could not find test ticket.", ticket);
}
@Test
public void testUpdate() {
// finding a test ticket
Ticket ticket = ticketDao.update(testTicketId, "peter", "Getting the test ticket", "ASSIGNED");
assertNotNull("Could not update the test ticket.", ticket.getTechnician());
assertTrue("Could not add history to test ticket.", (ticket.getTicketHistory().size() > 0));
}
@Test
public void testGetTicketsOwnedByTechnician() {
// finding a test ticket
List<Ticket> tickets = ticketDao.getTicketsOwnedByTechnician("peter");
assertTrue("Could not find tickets assigned to technician 'peter'", (tickets.size() > 0));
}
@Test
public void testGetOpenTickets() {
// finding a test ticket
List<Ticket> tickets = ticketDao.getOpenTickets();
assertTrue("Could not find open tickets.", (tickets.size() > 0));
}
@Test
public void testRemove() {
// finding a test ticket
ticketDao.remove(testTicketId);
// the ticket has been removed
assertTrue(true);
}
}