In the single-JVM environment, we had all resources within reach; database connection, DDL, JDBC, JPA, DAOs, BOs, etc. It was easy to setup and contol the environment for testing. However, we have an issue when testing EJBs and using their @Remote interfaces to do so. Our unit tests no longer have access to the full suite of resources and must rely on the @Remote methods exposed from the application server -or- bring in the data access tier itself. The later approach should be avoided if possible because it couples an RMI Test to too many outside factors. For the later approach, we need to be careful not to add extra test methods to the EJB under test because they would not be appropriate for the intended operational business use.
This example will demonstrate the issue and offer a solution using a separate set of server-side resources deployed as an EJB.
$ cat ./javaeeExEJB/src/main/java/myorg/javaeeex/ejb/RegistrarRemote.java
...
import java.util.Collection;
...
@Remote
public interface RegistrarRemote {
void ping();
Person createPerson(Person person)
throws RegistrarException;
Person getPersonById(long id)
throws RegistrarException;
Collection<Person> getPeopleByName(String firstName, String lastName)
throws RegistrarException;
}$ cat ./javaeeExEJB/src/main/java/myorg/javaeeex/ejb/RegistrarEJB.java
...
import java.util.Collection;
...
public Collection<Person> getPeopleByName(
String firstName, String lastName)
throws RegistrarException {
log.debug("*** getPeopleByName() ***");
try {
return registrar.getPeopleByName(firstName, lastName);
}
catch (Throwable ex) {
log.error(ex);
throw new RegistrarException(ex.toString());
}
}$ cat ./javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
...
import java.util.Collection;
...
@Test
public void testLazy() throws Exception {
log.info("*** testLazy ***");
for(int i=0; i<10; i++) {
Person person = makePerson();
person.setLastName("smith" + i);
registrar.createPerson(person);
}
//the first time we are going to get people straight from the DAO,
//without cleaning the managed object or creating a new DTO.
Collection<Person> people = registrar.getPeopleByName("joe", "%");
assertEquals("unexpected number of lazy people",10, people.size());
}$ mvn clean install -rf :javaeeExEJB -*** testLazy *** Tests run: 3, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 2.704 sec <<< FAILURE! Results : Failed tests: testLazy(myorg.javaeeex.ejbclient.RegistrarIT): unexpected number of lazy people expected:<10> but was:<11> Tests run: 3, Failures: 1, Errors: 0, Skipped: 0 ... [INFO] Java EE Exercise EJB .............................. SUCCESS [7.366s] [INFO] Java EE Exercise EAR .............................. SUCCESS [1.416s] [INFO] Java EE Exercise Remote Test ...................... FAILURE [15.709s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE
Test set: myorg.javaeeex.ejbclient.RegistrarIT
-------------------------------------------------------------------------------
Tests run: 3, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 2.704 sec <<< FAILURE!
testLazy(myorg.javaeeex.ejbclient.RegistrarIT) Time elapsed: 0.503 sec <<< FAILURE!
java.lang.AssertionError: unexpected number of lazy people expected:<10> but was:<11>
at org.junit.Assert.fail(Assert.java:93)
at org.junit.Assert.failNotEquals(Assert.java:647)
at org.junit.Assert.assertEquals(Assert.java:128)
at org.junit.Assert.assertEquals(Assert.java:472)
at myorg.javaeeex.ejbclient.RegistrarIT.testLazy(RegistrarIT.java:85)$ cat javaeeExEJB/src/main/java/myorg/javaeeex/ejb/TestUtilRemote.java
package myorg.javaeeex.ejb;
import javax.ejb.Remote;
import myorg.javaeeex.bl.TestUtil;
@Remote
public interface TestUtilRemote extends TestUtil {
}$ cat javaeeExEJB/src/main/java/myorg/javaeeex/ejb/TestUtilEJB.java
package myorg.javaeeex.ejb;
import javax.annotation.PostConstruct;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import myorg.javaeeex.bl.TestUtil;
import myorg.javaeeex.blimpl.TestUtilImpl;
@Stateless
public class TestUtilEJB implements TestUtilRemote {
private static Log log = LogFactory.getLog(TestUtilEJB.class);
@PersistenceContext(unitName="javaeeEx")
private EntityManager em;
private TestUtil testUtil;
@PostConstruct
public void init() {
log.info(" *** TestUtilEJB:init() ***");
testUtil = new TestUtilImpl();
((TestUtilImpl)testUtil).setEntityManager(em);
}
public void resetAll() throws Exception {
try {
testUtil.resetAll();
}
catch (Exception ex) {
log.warn("error in resetAll", ex);
throw ex;
}
}
}javaeeEx |-- javaeeExEJB | |-- pom.xml | `-- src | `-- main | |-- java | | `-- myorg | | `-- javaeeex | | `-- ejb | | |-- RegistrarEJB.java | | |-- RegistrarLocal.java | | |-- RegistrarRemote.java | | |-- TestUtilEJB.java | | `-- TestUtilRemote.java | `-- resources | `-- META-INF | `-- persistence.xml
$ cat ./javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
...
import myorg.javaeeex.bl.TestUtil;
import myorg.javaeeex.ejb.TestUtilRemote;
...
private static final String testUtilJNDI = System.getProperty("jndi.name.testUtil",
"javaeeExEAR/javaeeExEJB/TestUtilEJB!myorg.javaeeex.ejb.TestUtilRemote");
private TestUtil testUtil;
public void setUp() throws Exception {
...
log.debug("jndi name:" + testUtilJNDI);
testUtil = (TestUtilRemote)jndi.lookup(testUtilJNDI);
log.debug("testUtil=" + testUtil);
}$ cat ./javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
...
public void setUp() throws Exception {
...
testUtil = (TestUtilRemote)jndi.lookup(testUtilJNDI);
cleanup();
}
protected void cleanup() throws Exception {
log.info("calling testUtil.resetAll()");
testUtil.resetAll();
log.info("testUtil.resetAll() complete");
}...
-testUtil=Proxy for remote EJB StatelessEJBLocator{appName='javaeeExEAR', moduleName='javaeeExEJB', distinctName='', beanName='TestUtilEJB', view='interface myorg.javaeeex.ejb.TestUtilRemote'}
-calling testUtil.resetAll()
-testUtil.resetAll() complete
-*** testLazy ***
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.419 sec
Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
...
[INFO] Java EE Exercise EJB .............................. SUCCESS [6.074s]
[INFO] Java EE Exercise EAR .............................. SUCCESS [1.098s]
[INFO] Java EE Exercise Remote Test ...................... SUCCESS [12.943s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS//SERVER LOG
02:48:09,757 DEBUG [myorg.javaeeex.ejb.RegistrarEJB] (EJB default - 6) **** init ****
02:48:09,758 DEBUG [myorg.javaeeex.ejb.RegistrarEJB] (EJB default - 6) em=org.jboss.as.jpa.container.TransactionScopedEntityManager@19e2011
02:48:09,761 DEBUG [myorg.javaeeex.ejb.RegistrarEJB] (EJB default - 6) init complete, registrar=myorg.javaeeex.blimpl.RegistrarImpl@1734643
02:48:09,762 DEBUG [myorg.javaeeex.ejb.RegistrarEJB] (EJB default - 6) ping called
02:48:09,860 DEBUG [myorg.javaeeex.jpa.DBUtil] (EJB default - 7) found 4 statements
02:48:09,861 DEBUG [myorg.javaeeex.jpa.DBUtil] (EJB default - 7) executing:
alter table JAVAEEEX_ADDRESS
drop constraint FKEB70B40A6E18CE38
02:48:09,864 DEBUG [myorg.javaeeex.jpa.DBUtil] (EJB default - 7) executing:
drop table JAVAEEEX_ADDRESS if exists
02:48:09,890 DEBUG [myorg.javaeeex.jpa.DBUtil] (EJB default - 7) executing:
drop table JAVAEEEX_PERSON if exists
02:48:09,892 DEBUG [myorg.javaeeex.jpa.DBUtil] (EJB default - 7) executing:
02:48:09,895 DEBUG [myorg.javaeeex.jpa.DBUtil] (EJB default - 7) found 4 statements
02:48:09,898 DEBUG [myorg.javaeeex.jpa.DBUtil] (EJB default - 7) executing:
create table JAVAEEEX_ADDRESS (
id bigint generated by default as identity (start with 1),
city varchar(255),
state varchar(255),
street varchar(255),
zip varchar(255),
PERSON_ID bigint,
primary key (id)
)
02:48:09,902 DEBUG [myorg.javaeeex.jpa.DBUtil] (EJB default - 7) executing:
create table JAVAEEEX_PERSON (
id bigint generated by default as identity (start with 1),
firstName varchar(255),
lastName varchar(255),
ssn varchar(255),
primary key (id)
)
02:48:09,913 DEBUG [myorg.javaeeex.jpa.DBUtil] (EJB default - 7) executing:
alter table JAVAEEEX_ADDRESS
add constraint FKEB70B40A6E18CE38
foreign key (PERSON_ID)
references JAVAEEEX_PERSON
02:48:09,924 DEBUG [myorg.javaeeex.jpa.DBUtil] (EJB default - 7) executing:
02:48:09,948 DEBUG [myorg.javaeeex.ejb.RegistrarEJB] (EJB default - 8) *** createPerson() ***
...It is temptingly easy to directly return the managed entities returned from the entity manager from the interface of the EJB. However, there is an issue doing so. The data within the database can have a rich set of relationships. If we walked all relationships, we might end up traversing a significant amount of the total database. The provide accounts for this by using lazy loading. In this approach, many of your related objects are initially loaded in as stand-in references and only loaded (hydrated) when needed. This is not a problem when the accessing code is within the same JVM as the EntityManager and the resources to access the database are still in place when a lazily loaded object is inspected. This is a problem when you pass one of these objects outside of the scope of the entity manager -- aka RMI clients.
$ cat ./javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
...
public void testLazy() throws Exception {
...
Collection<Person> people = registrar.getPeopleByName("joe", "%");
assertEquals("unexpected number of lazy people",10, people.size());
for (Person p: people) {
p.getAddresses().iterator().next().getZip();
} -*** testLazy ***
Tests run: 3, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 2.693 sec <<< FAILURE!
Results :
Tests in error:
testLazy(myorg.javaeeex.ejbclient.RegistrarIT): failed to lazily initialize a collection of role:
myorg.javaeeex.bo.Person.addresses, no session or session was closed
Tests run: 3, Failures: 0, Errors: 1, Skipped: 0
...
[INFO] Java EE Exercise EJB .............................. SUCCESS [6.687s]
[INFO] Java EE Exercise EAR .............................. SUCCESS [0.837s]
[INFO] Java EE Exercise Remote Test ...................... FAILURE [15.746s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE./javaeeExTest/target/surefire-reports/myorg.javaeeex.ejbclient.RegistrarIT.txt
::::::::::::::
Test set: myorg.javaeeex.ejbclient.RegistrarIT
-------------------------------------------------------------------------------
Tests run: 3, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 2.692 sec <<< FAILURE!
testLazy(myorg.javaeeex.ejbclient.RegistrarIT) Time elapsed: 0.484 sec <<< ERROR!
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: myorg.javaeeex.bo.Person.addresses, no session or session
was closed
...
at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:266)
at myorg.javaeeex.ejbclient.RegistrarIT.testLazy(RegistrarIT.java:105)$ cat ./javaeeExEJB/src/main/java/myorg/javaeeex/ejb/RegistrarRemote.java
...
public interface RegistrarRemote {
Collection<Person> getPeopleByNameHydrated(String firstName, String lastName)
throws RegistrarException;
}$ cat ./javaeeExEJB/src/main/java/myorg/javaeeex/ejb/RegistrarEJB.java
...
import myorg.javaeeex.bo.Address;
...
public Collection<Person> getPeopleByNameHydrated(
String firstName, String lastName)
throws RegistrarException {
log.debug("*** getPeopleByNameHydrated() ***");
try {
Collection<Person> people =
registrar.getPeopleByName(firstName, lastName);
for (Person p: people) {
hydratePerson(p);
}
return people;
}
catch (Throwable ex) {
log.error(ex);
throw new RegistrarException(ex.toString());
}
}
private void hydratePerson(Person person) {
for (Address address : person.getAddresses()) {
address.getZip();
}
}$ cat ./javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
...
import org.hibernate.LazyInitializationException;
...
@Test
public void testLazy() throws Exception {
...
Collection<Person> people = registrar.getPeopleByName("joe", "%");
assertEquals("unexpected number of lazy people",10, people.size());
try {
for (Person p: people) {
p.getAddresses().iterator().next().getZip();
}
fail("no lazy instantiation exception thrown");
}
catch (LazyInitializationException expected) {
log.info("got expected lazy instantiation exception:" + expected);
}
//this time, the EJB will be asked to walk the graph returned
people = registrar.getPeopleByNameHydrated("joe", "%");
assertEquals("unexpected number of loaded people",10, people.size());
for (Person p: people) {
p.getAddresses().iterator().next().getZip();
}$ mvn clean install
...
-*** testLazy ***
-got expected lazy instantiation exception:org.hibernate.LazyInitializationException:
failed to lazily initialize a collection of role: myorg.javaeeex.bo.Person.addresses, no session or session was closed
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.477 sec
Results :
Tests run: 3, Failures: 0, Errors: 0, Skipped: 0
...
[INFO] Java EE Exercise .................................. SUCCESS [0.658s]
[INFO] Java EE Exercise Impl ............................. SUCCESS [15.167s]
[INFO] Java EE Exercise EJB .............................. SUCCESS [3.415s]
[INFO] Java EE Exercise EAR .............................. SUCCESS [2.034s]
[INFO] Java EE Exercise Remote Test ...................... SUCCESS [14.965s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSWith lazy loading issues addressed, it again becomes tempingly easy to directly return (hydrated) entities returned from the entity manager directly from our EJB interface. However, once managed, an entity may have residue objects associated with it to implement the monitoring required during the managed state.
This exercise will demonstrate how our vanilla POJOs where poluted during their managed state and how they present provider-specific classes to the RMI Client. This issue with this is that it may require the RMI Client to add provider-specific classes to their classpath to handle these "not so plain" POJOs.
$ cat ./javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
...
@Test
public void testPOJO() throws Exception {
log.info("*** testPOJO ***");
for(int i=0; i<10; i++) {
Person person = makePerson();
person.setLastName("smith" + i);
registrar.createPerson(person);
}
//the objects returned will be fully loaded, but...
Collection<Person> people =
registrar.getPeopleByNameHydrated("joe", "%");
assertEquals("unexpected number of managed people",10, people.size());
//the collection class requires hibernate to be in the path
Class<?> clazz = people.iterator().next().getAddresses().getClass();
log.debug("collection class=" + clazz);
assertTrue("unexpected collection class",
clazz.getPackage().getName().contains("hibernate"));
}-*** testPOJO *** -collection class=class org.hibernate.collection.PersistentBag
In this first approach we will reuse our POJO class of the entity to construct a "plain" POJO. This will involve some tedious copying of data from the provider entity object to the plain POJO object.
$ cat ./javaeeExEJB/src/main/java/myorg/javaeeex/ejb/RegistrarRemote.java
...
public interface RegistrarRemote {
...
Collection<Person> getPeopleByName(String firstName, String lastName)
throws RegistrarException;
Collection<Person> getPeopleByNameHydrated(String firstName, String lastName)
throws RegistrarException;
Collection<Person> getPeopleByNameCleaned(String firstName, String lastName)
throws RegistrarException;
}$ cat ./javaeeExEJB/src/main/java/myorg/javaeeex/ejb/RegistrarEJB.java
...
import java.util.ArrayList;
...
public Collection<Person> getPeopleByNameCleaned(
String firstName, String lastName)
throws RegistrarException {
log.debug("*** getPeopleByNameCleaned() ***");
try {
Collection<Person> people = new ArrayList<Person>();
for (Person personBO :
registrar.getPeopleByName(firstName, lastName)) {
Person personPOJO = new Person(personBO.getId());
personPOJO.setFirstName(personBO.getFirstName());
personPOJO.setLastName(personBO.getLastName());
personPOJO.setSsn(personBO.getSsn());
for (Address addressBO : personBO.getAddresses()) {
Address addressPOJO = new Address(
addressBO.getId(),
addressBO.getStreet(),
addressBO.getCity(),
addressBO.getState(),
addressBO.getZip());
personPOJO.getAddresses().add(addressPOJO);
}
people.add(personPOJO);
}
return people;
}
catch (Throwable ex) {
log.error(ex);
throw new RegistrarException(ex.toString());
}
}$ cat ./javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
...
public void testPOJO() throws Exception {
...
//now get a graph of objects that contain pure POJO classes. The
//server will create fresh POJOs for DTOs and pass information from
//the business object POJO to the data transfer object POJO.
people = registrar.getPeopleByNameCleaned("joe", "%");
assertEquals("unexpected number of clean people",10, people.size());
for (Person p: people) {
p.getAddresses().iterator().next().getZip();
}
//the POJOs are cleansed of their hibernate types
clazz = people.iterator().next().getAddresses().getClass();
log.debug("collection class=" + clazz);
assertFalse("unexpected collection class",
clazz.getPackage().getName().contains("hibernate"));
}-*** testPOJO *** -collection class=class org.hibernate.collection.PersistentBag -collection class=class java.util.ArrayList
With the POJO cleaning approach, we manually instantiated and populated POJOs to be returned and used our entity classes as the implementation class for the POJOs. However, we could have also used DTOs just as easily, except for the effort of creating separate classes. See the next section for a coverage of DTOs.
Entity classes are meant to implement the data-centric business rules of an application. Because of their POJO design, it is tempting to resuse them to transfer data to/from the client. However, there are many times when the business rules applied on the server side and are not the same as when used on the client side. There are also cases where the entity represents more than the client wants or can handle. Many times, we are just looking to transfer data to/from the client and will want to create a set of transient classes specifically for this purpose.
$ mkdir -p javaeeExEJB/src/main/java/myorg/javaeeex/dto
$ cat javaeeExEJB/src/main/java/myorg/javaeeex/dto/PersonDTO.java
package myorg.javaeeex.dto;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
public class PersonDTO implements Serializable {
private static final long serialVersionUID = 1L;
private long id;
private String firstName;
private String lastName;
private Collection<AddressDTO> addresses = new ArrayList<AddressDTO>();
public PersonDTO() {}
public PersonDTO(long id) { setId(id); }
public long getId() {
return id;
}
private void setId(long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Collection<AddressDTO> getAddresses() {
return addresses;
}
public void setAddresses(Collection<AddressDTO> addresses) {
this.addresses = addresses;
}
public String toString() {
StringBuffer text = new StringBuffer();
text.append("id=" + id);
text.append(":" + firstName);
text.append(" " + lastName);
text.append(", addresses={");
for (AddressDTO address : addresses) {
text.append("{" + address.toString() + "},");
}
text.append("}");
return text.toString();
}
}$ cat javaeeExEJB/src/main/java/myorg/javaeeex/dto/AddressDTO.java
package myorg.javaeeex.dto;
import java.io.Serializable;
public class AddressDTO implements Serializable {
private static final long serialVersionUID = 1L;
private long id;
private String street;
private String city;
private String state;
private String zip;
public AddressDTO() {}
public AddressDTO(
long id, String street, String city, String state, String zip) {
this.id = id;
this.city = city;
this.state = state;
this.street = street;
this.zip = zip;
}
public long getId() {
return id;
}
@SuppressWarnings("unused")
private void setId(long id) {
this.id = id;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}
public String toString() {
StringBuilder text = new StringBuilder();
text.append(street + " ");
text.append(city + ", ");
text.append(state + " ");
text.append(zip);
return text.toString();
}
}javaeeEx |-- javaeeExEJB | |-- pom.xml | `-- src | `-- main | |-- java | | `-- myorg | | `-- javaeeex | | |-- dto | | | |-- AddressDTO.java | | | `-- PersonDTO.java | | `-- ejb | | |-- RegistrarEJB.java | | |-- RegistrarLocal.java | | |-- RegistrarRemote.java | | |-- TestUtilEJB.java | | `-- TestUtilRemote.java | `-- resources | `-- META-INF | `-- persistence.xml
$ cat ./javaeeExEJB/src/main/java/myorg/javaeeex/ejb/RegistrarRemote.java
...
import myorg.javaeeex.dto.PersonDTO;
public interface RegistrarRemote {
...
Collection<Person> getPeopleByName(String firstName, String lastName)
throws RegistrarException;
Collection<Person> getPeopleByNameHydrated(String firstName, String lastName)
throws RegistrarException;
Collection<Person> getPeopleByNameCleaned(String firstName, String lastName)
throws RegistrarException;
Collection<PersonDTO> getPeopleByNameDTO(String firstName, String lastName)
throws RegistrarException;
}$ cat ./javaeeExEJB/src/main/java/myorg/javaeeex/ejb/RegistrarEJB.java
...
import myorg.javaeeex.dto.AddressDTO;
import myorg.javaeeex.dto.PersonDTO;
...
public Collection<PersonDTO> getPeopleByNameDTO(
String firstName, String lastName)
throws RegistrarException {
log.debug("*** getPeopleByNameDTO() ***");
try {
Collection<PersonDTO> people = new ArrayList<PersonDTO>();
for (Person personBO :
registrar.getPeopleByName(firstName, lastName)) {
PersonDTO personDTO = makeDTO(personBO);
people.add(personDTO);
}
return people;
}
catch (Throwable ex) {
log.error(ex);
throw new RegistrarException(ex.toString());
}
}
private PersonDTO makeDTO(Person personBO) {
PersonDTO personDTO = new PersonDTO(personBO.getId());
personDTO.setFirstName(personBO.getFirstName());
personDTO.setLastName(personBO.getLastName());
//note that there is no SSN in the DTO
for (Address addressBO : personBO.getAddresses()) {
AddressDTO addressDTO = new AddressDTO(
addressBO.getId(),
addressBO.getStreet(),
addressBO.getCity(),
addressBO.getState(),
addressBO.getZip());
personDTO.getAddresses().add(addressDTO);
}
return personDTO;
}$ cat ./javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
...
import myorg.javaeeex.dto.AddressDTO;
import myorg.javaeeex.dto.PersonDTO;
...
@Test
public void testDTOs() throws Exception {
log.info("*** testDTOs ***");
for(int i=0; i<10; i++) {
Person person = makePerson();
person.setLastName("smith" + i);
registrar.createPerson(person);
}
//now get a graph of objects that contain pure DTOs versus BOs
Collection<PersonDTO>peopleDTO =
registrar.getPeopleByNameDTO("joe", "%");
assertEquals("unexpected number of DTO people",10, peopleDTO.size());
for (PersonDTO p: peopleDTO) {
Collection<AddressDTO> a = p.getAddresses();
a.iterator().next().getZip();
}
//the DTOs are POJOs that are designed to only contain what the
//clients needs to see. It contains no server-side behavior and
//could be represented as an XML document. In this example, we
//have excluded the SSN from the PersonDTO.
}$ mvn clean install -rf :javaeeExEJB ... -*** testDTOs *** Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 2.817 sec Results : Tests run: 5, Failures: 0, Errors: 0, Skipped: 0 ... [INFO] Java EE Exercise EJB .............................. SUCCESS [5.273s] [INFO] Java EE Exercise EAR .............................. SUCCESS [1.059s] [INFO] Java EE Exercise Remote Test ...................... SUCCESS [13.384s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS