schuchert - Ejb3Tutorial3 - A Mini Aplication Revisited Printable

Ejb3Tutorial3 - A Mini Aplication Revisited Printable

EJB3 Tutorial 3 - A Mini Application
This tutorial revisits JPA Tutorial 3 and migrates it from a JSE Solution to a JEE solution. Along the way, we're going to notice several things that we "missed". These were either things that the JSE environment let slide or things we missed because of how we were testing our solutions.

A key difference between what we did and what we're going to do is transactional demarcation. In our JSE environment, we had a @Before method that started a transaction and an @After method that rolled the transaction back. This meant that multiple messages to, for example, the library, all happened in the same transaction. We can get similar behavior to this using EJB3 Session Beans with Extended Persistence Contexts. However, we're going to stick with Stateless Session Beans using transaction-scoped persistence contexts. By default, each outer-most execution of a session bean method will: Initialize the persistence context Start a transaction Execute the method Commit the transaction Clear the persistence context If, within a session bean, the code calls another session bean, the following happens: Work with existing persistence context Join existing transaction Execute the method With a regular persistence context, committing a transaction causes everything in the persistence context to be flushed. That means two things: Objects are no longer managed (they are detached) Lazily-initialized relationships can no longer be traversed If you use an extended persistence context, closing the transaction does not clear the persistence context. We will look at this further in a later tutorial. For now, we're sticking with the basics.

Setting up the Project
First we need to start with a project. Rather than having to copy all of JPA Tutorial 3, instead use the following 7-zip file: Ejb3Tutorial3.7z. You are welcome to use your version of Jpa Tutorial 3, however if you do these instructions might not match your experience. Note that this file already has a conf directory as described in EJB3 Tutorial 1 - Create and Configure and the classpath is already already set. 1. Extract this file using 7-zip. Place the contents of this archive under your workspace directory. For example, if your workspace directory is c:\workspaces\Ejb3JpaTutorials, after extracting the contents of this archive, you'll have a new directory named c:\workspaces\Ejb3JpaTutorials\Ejb3Tutorial3. 2. Next, import the project into your workspace 3. Start eclipse and open your workspace directory (if you're already in Eclipse, you do not need to restart) 4. Pull down the File menu and select import 5. Expand General 6. Select Existing Projects into Workspace 7. Click Next 8. Select root directory is already selected, click on Browse

1 of 23

10/17/2010 9:54 AM

schuchert - Ejb3Tutorial3 - A Mini Aplication Revisited Printable

9. Select the directory you created when you extracted the archive (c:\workspaces\Ejb3JpaTutorials \Ejb3Tutorial3) 10. Click OK 11. Click Finish Verify that everything compiled successfully. Once you've fixed any compilation problems, run the unit tests. You might notice a few warnings and even some Fatal logging statements, but the tests should pass. As we migrate this solution to use EJB's, these errors will eventually go away based on how we change our test setup.

Database Configuration
As we have seen with the previous EJB tutorials, the persistence.xml looks a little different for a JEE environment. Update the persistence.xml to resemble the following: persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence> <persistence-unit name="lis"> <jta-data-source>java:/HypersonicLocalServerDS</jta-data-source> <properties> <property name="" value="create-drop"/> <property name="" value="true"/> </properties> </persistence-unit> </persistence>

Data Source and Database
This persistence.xml makes use of a data source that we mentioned here. We're using this so that we have a database we can look at as we work through our tests to make sure we're cleaning everything up properly. If you're working with a preconfigured system, the startdb.bat file mentioned below will already exist, you just need to run it. The files you downloaded already contained changes in support of the hypersonic local server data source definition. You'll still need to start hypersonic. To do so, you can do the following: 1. Change to the directory where you installed hsqldb (c:\libs\hsqldb) 2. Make a new directory, called databases 3. Change to that directory 4. Start the database (requires you can execute a Java VM from the command line)
java -cp ../lib/hsqldb.jar org.hsqldb.Server -database.0 file:mydb -dbname.0 xdb

First EJB
Here is a list of the classes we'll convert to Session Beans: BookDao Library LoanDao PatronDao ResourceDao What about naming conventions? Every Session Bean has at least one interface and one class. We need to pick a name for the interface and the class. One convention is to add "Bean" after the name of the interface. So if we had an interface called RepairFacility, then the implementation would be called RepairFacilityBean. We'll use this convention and end up with the following names: Original BookDao Interface BookDao Bean BookDaoBean

2 of 23

10/17/2010 9:54 AM

select Refactor:Extract Interface 3. we need to extract the interface automatically: 1.schuchert . Rename We'll start with PatronDao. To Rename it: 1. select Refactor:Rename 3. Optionally deselect Declare interface methods 'abstract' 8.lookup(PatronDao. Enter PatronDaoBean for the name 4. We need to updated getDao() in the following ways: It no longer should have the @Override annotation It simply uses the JBossUtil to look-up the dao Make sure to remove the dao instance variable and fix any resulting compilation errors by replacing dao with getDao() Here is an updated version of that method: public PatronDao getDao() { return JBossUtil. Right-click. Select PatronDao in the Package Explorer 2.wikispaces. Click OK Update Unit Test: PatronDaoTest The unit test inherits from BaseDbDaoTest. Make sure to select Use the extracted interface type where possible 6. you'd instead use /Remote. Optionally deselect Declare interface methods 'public' 7. } Notice two things about the name we provide. And if you leave this off. Annotate the class with @Stateless. you'll get a bad cast exception.class. We don't want to do any of this. This adds support for creating dao's entity managers. Also notice we need to add /local. Select PatronDaoBean in the Package Explorer 2. PatronDaoBean. Select all of the methods 5. Extract Interface Next. First. etc.. Right-click.Ejb3Tutorial3 . You might experiment with this to discover why. If you had a remote interface. Press OK Make it a Stateless Session Bean We need to annotate this class to declare it is a session bean. "PatronDaoBean/local"). 3 of 23 10/17/2010 9:54 AM . so we can safely remove the base class. we use the unqualified name of the bean class.A Mini Aplication Revisited Printable http://schuchert. Luckily Eclipse will take care of most of this for us.. Library LoanDao PatronDao ResourceDao Library LoanDao PatronDao ResourceDao LibraryBean LoanDaoBean PatronDaoBean ResourceDaoBean We are going to have to rename each of our beans and then create an interface. Enter PatronDao for the interface name

Second Attempt: Second Failure After adding in @PersistenceContext we get one test to pass and three to fail.. } Using the @PersistenceContext will tell the container to look up the named EntityManagerFactory.A Mini Aplication Revisited Printable http://schuchert. To get an entity manager .if you were to run all the tests in the project you would get misleading errors at this point ("could not insert [entity. However. we might want to rename the test to PatronDaoBeanTest.startDeployer(). We need to add a default constructor to Patron.. There was a method with the @Before annotation that set the entity manager on the PatronDao. we place that annotation in the base class. We can have the container inject the entity manager (or an entity manager factory if you'd like) into our PatronDaoBean. Since we inherit the entity manager attribute from a base class. Go back and do the steps you just did for all the dao classes Success 4 of 23 10/17/2010 9:54 AM . If you look at the stack trace in the JUnit window. in this case em. just PatronDaoBeanTest). We also need to initialize the EJB Container. We no longer inherit from that class so we no longer get that initialization.persist(p).Address]"). they will all fail. this is not how we should be initializing that attribute anyway.. since we are now testing PatronDaoBean instead of PatronDao. We're getting a null pointer exception on this line because getEm() returns null. Add the following method: @BeforeClass public static void initContainer() { JBossUtil.. we see that the Patron class does not have a default constructor. create an EntityManager for us and then place that entity manager into the variable.Ejb3Tutorial3 . as follows: public abstract class BaseDao { @PersistenceContext(unitName = "lis") private EntityManager em. This happens when we look up the PatronDao. we use the annotation @PersistenceContext on an attribute of type Execute the PatronDaoBeanTest tests only -. If you review the stack trace in the JUnit window. you'll notice that all the lines that fail look something like this: getEm().wikispaces. BaseDao. We can use the container to perform this initialization. } public Patron() { } Add it and re-run the tests (again. Run the PatronDaoBeanTest Tests: First Failure When you run the unit tests (just PatronDaoBeanTest).schuchert .

2 patrons are left in the database after the tests execute. we have not solved any problem. getDao(). so nothing got saved to the database.setPhoneNumber(NEW_PN). Or they are not isolated.schuchert . We want our tests to have no side-effects because they might run in any order and such side effects could cause other tests to fail. Clean up after ourselves The first option is tricky at best. } } @Test public void updateAPatron() { final Patron p = createAPatronImpl(). assertNotNull(found).getId()). more importantly. We have three options on how to avoid this: 1.getId()).A Mini Aplication Revisited Printable http://schuchert. There are 4 tests. 5 of 23 10/17/2010 9:54 AM . If you start with a clean database and run the tests. you can use Quantum DB. this is not how our system will be running so even if we the first option to work. Another looks up a Patron with a bogus key. However.retrieve(p. The second option is good but it has a few flaws: 1. so it does not have any side effects. For some reason when we ran this code in a JSE environment. This fixes all the tests in PatronDaoBeanTests. before we go on. Try to simulate the old behavior 2. we've not really improved anything. 2. final Patron found = getDao(). Create a new database every time 3.retrieve(p.. but we'd still have to do it after every test. One is to test removing a Patron so it runs clean. p.wikispaces. Really.. Overall this first conversion was fairly painless.update(p). } finally { removePatron(p). assertNotNull(found). We've already seen that we missed some things in a JSE environment and. after we execute the tests did we remember to remove everything we created? Review: Are We Isolated? At this point we need a tool to review the contents of the data base. are out tests isolated? That is.getPhoneNumber(). This leaves the following two tests we need to fix: createAPatron updateAPatron Here are the updates to PatronDaoBeanTest: @Test public void createAPatron() { final Patron p = createAPatronImpl(). It simply masks bad tests and unless we drop the database after every In the old way of doing things.Ejb3Tutorial3 . then you might want to try SQuirrel Sql Client. try { final String originalPhoneNumber = p. we started a transaction and then rolled it back. the best option is to write our tests so they clean up after themselves. If you want to work directly in Eclipse. What if we want to run our tests against a populated database? We could re-create the database. This means that the tests leave a foot print. If you prefer to work outside of Eclipse (and frankly with a more powerful tool). it "worked" even though it did not comply with the standard. try { final Patron found = getDao().

authors.hibernate. } We used a try {} finally block to make sure after the test finishes that we call a support method. 2. } } private void removePatron(final Patron p) { getDao(). "Author"))). 4. When we returned.retrieve(b..getId()).update(b).size()). the transaction committed and all objects in the persistence context were detached. assertTrue(found instanceof Book). final Resource found = getDao(). ((Book) found). When we retrieve the authors() from found. removePatron.yet. Directly access the collection while still in the session bean method to get it initialized (read in) When we ask the object returned from getAuthors() for its size(). Rename ResourceDaoTest --> ResourceDaoBeanTest Remove Base Class Remove dao attribute Rewrite getDao() to return a looked up ResourceDao Add method with @BeforeClass annotation that initializes the container Run Your Tests (ResourceDaoBeanTest): First Failures After making these changes.wikispaces. getDao(). } finally { removePatron(p). We also added a simple private method to actually perform the delete..LazyInitializationException: failed to lazily initialize a collection of role: entity.addAuthor(new Author(new Name("New". final int initialAuthorCount = b. Use Eager fetching 2. we have 3 tests that pass and one that fails. Remember that the object found is detached. Why? We are no longer in a method in the container.Ejb3Tutorial3 . assertFalse(NEW_PN.getId()). Add @Stateless annotation to ResourceDaoBean 3.A Mini Aplication Revisited Printable http://schuchert.equals(originalPhoneNumber)). no session or session was closed Here's the actual test: 01: 02: 03: 04: 05: 06: 07: 08: 09: 10: @Test public void updateABook() { final Book b = createABookImpl(). Extract interface ResourceDao from ResourceDaoBean Update Test 1.removePatron(p. assertEquals(NEW_PN. p. we have returned from the method.getAuthors(). The fix is trivial (so far). Change the test to send a message to the ResourceDao to ask for the number of authors associated with a particular 6 of 23 10/17/2010 9:54 AM .schuchert . Rename ResourceDao --> ResourceDaoBean 2. there's no problem -. it will show the last line.getPhoneNumber()).size(). assertEquals(initialAuthorCount + 1. 5. } If you double-click on the last line listed in the stack trace. line 9. 3. as the problem line. EJB 2: ResourceDao Update Dao 1. The error in the JUnit stack trace looks like this: org. The author’s relationship is lazily loaded by default (because it is a @ManyToMany and that's the default behavior). we're accessing not a collection but a proxy to a collection. We have three ways to fix this problem: 1. b.getAuthors().

.EAGER) private Set<Author> authors. cascade = { CascadeType.MERGE }. CascadeType. 7 of 23 10/17/2010 9:54 AM .addAuthor(this). } The problem is. we are not adding the book to the author. } Finally All of ResourceDaoBaseTest Running We're close.add(b). JPA is automatically inserting two foreign keys in to a join table called AUTHOR_BOOK.addBook(this). we need to remove the relationship between the book and the authors (both sides). } Run the tests in ResourceDaoBeanTest. However. Update the addAuthor() method in book to add the book to the author: public void addAuthor(final Author author) { getAuthors(). The change there is adding fetch = FetchType. Remember that we must maintain both sides of a bi-directional relationship. b. Here's a way to fix this: 1. book We'll take the easiest way out to fix this and make this relationship eagerly fetched rather than lazily fetched. } public Set<Book> getBooksWritten() { if(booksWritten == null) { booksWritten = new HashSet<Book>(). } return booksWritten. This is a bi-directional relationship.schuchert . Here's the change to Book. Well to remove the book. Nearly Finally Fixed There's a method in Author. Change booksWritten to getBookWritten() 2. The problem is since we started properly maintaining a bi-directional relationship between books and authors. things still do not work.Ejb3Tutorial3 .add(b).wikispaces. Lazily initialize booksWritten public void addBook(final Book b) { getBooksWritten().java: @ManyToMany(mappedBy = "booksWritten".EAGER Try #2 When you run this. Mini Aplication Revisited Printable http://schuchert. fetch = that looks like this: public void addBook(final Book b) { booksWritten. while we are adding an author to the book. This is getting worse before it gets better.add(author). the booksWritten attribute is never assigned. Now more tests are failing.

Add any required supporting methods in other classes 5. Have the resource dao delegate a message to all resources polymorphically.clear(). ResourceDao. which removes both books and dvd's as well as all kinds of resources. Make sure to actually call the remove method in the ResourceDao Add abstract method to Resource public abstract void remove().remove(r). Create an abstract method in Resource called remove().schuchert .wikispaces.removeBook(this).com/Ejb3Tutorial3+-+A+Mini+Aplication+R.Ejb3Tutorial3 . Use type-checking in the ResourceDao to do custom delete logic based on type 2. Test Isolation 8 of 23 10/17/2010 9:54 AM . if (r != null) { r. Add empty implementation to Dvd @Override public void remove() { } Add implementation to Book @Override public void remove() { for (Author a : getAuthors()) { a. } Run the tests and they now all pass. We have one dao.. just mostly always. } Call the remove() method in ResourceDao Just after looking up the resource and just before actually removing it.. } Add removeBook to Author public void removeBook(final Book book) { getBooksWritten(). The book has a dependency on Author that not all resources have.A Mini Aplication Revisited Printable http://schuchert. Add an empty implementation of this method to Dvd so it will compile 3. } getAuthors(). getEm().remove(book). So how can we still use the ResourceDao to remove a book if the book has specific logic? Here are two options: 1. } getEm(). We won't even consider that because polymorphism is the way to go here. Here are the three steps we're going to follow: 1.remove(). Add an implementation into Book to clean up all of its relationships 4. Here is the challenge. the book will take care of specific clean-up logic Type checking is not always bad. we need to call the remove() method on the Resource object: public void remove(Long id) { final Resource r = retrieve(id).flush(). 2.

a.Author. import entity.getAuthors().getBooksWritten()) { b. Rows We need to update each of the tests that create books and explicitly remove the books and authors created.Author. for (Book b : toDelete.Set.getBooksWritten(). Just updating the bi-directional relationships properly will fix that problem.A Mini Aplication Revisited Printable http://schuchert.util. It turns out we do not need to explicitly remove anything from Author_Book. import java. import javax.remove(toDelete). we need to clean up after ourselves.getId()). Here are the stats: Table Author Book Author_Book Resource 7 2 5 2 1.clear(). Delete Author To delete authors. } toDelete.Book.Stateless. getEm().find(Author. Now that all of our tests in ResourceDaoBeanTest pass.schuchert . } AuthorDaoBean package session.remove(toDelete).. import entity.Set. } } } Update Test 9 of 23 10/17/2010 9:54 AM . getEm(). public interface AuthorDao { void remove(final Set<Author> authors).com/Ejb3Tutorial3+-+A+Mini+Aplication+R..Ejb3Tutorial3 .wikispaces. we'll create a new Dao for Authors: AuthorDao package session.util.ejb.class. @Stateless public class AuthorDaoBean extends BaseDao implements AuthorDao { public void remove(final Set<Author> authors) { for (Author a : authors) { final Author toDelete = getEm().flush(). import entity. import java.

b. we must change getDao() to be a static method as well.remove(authors). assertEquals(initialAuthorCount + 1.A Mini Aplication Revisited Printable http://schuchert. Updated Tests Here are the updated tests that now use the support methods.getId()).getId()).retrieve(b.size()). b = (Book) getDao().getAuthors()). b.getAuthors()).getId()).getAuthors().getId()).addAuthor(new Author(new Name("New". However. assertNull(found).findById(book. "Author"))). } public void removeBookAndAuthors(final Book book) { final Book b = (Book) getDao(). } finally { removeBookAndAuthors(b).schuchert .retrieve(b. to make them static.Ejb3Tutorial3 .lookup(AuthorDao..wikispaces. try { final int initialAuthorCount = b. } } @Test public void updateABook() { Book b = createABookImpl().getId()).retrieve(b. try { final Resource found = getDao().class. assertNotNull(found).remove(b. assertNotNull(found).getAuthors(). } } 10 of 23 10/17/2010 9:54 AM . removeAuthors(book.retrieve(b. @Test public void createABook() { final Book b = createABookImpl(). } finally { removeBookAndAuthors(b)..getId()).remove(b. Support Methods First we need a few methods we can use to delete authors and books (we're working in ResourceDaoBeanTest. } finally { removeAuthors(b. found = getDao().getId()).size(). } } @Test public void removeABook() { final Book b = createABookImpl(). try { Resource found = getDao(). "AuthorDaoBean/local") . getDao(). getDao(). so this is a fine place to add these methods): public void removeAuthors(final Set<Author> authors) { JBossUtil. getDao(). } These methods are static because we might want to use them from other

Add @Stateless annotation to BookDaoBean 3. We need to change from this: @Before 11 of 23 10/17/2010 9:54 AM . Extract interface LoanDao from LoanDaoBean Library 1. Finish Conversion We have the following classes to convert: LoanDao BookDao Library There's only one test left to @EJB private BookDao bookDao. BookDao 1. Add @Stateless annotation to LoanDaoBean 3. Remove Base Class from LibraryBeanTest 3.A Mini Aplication Revisited Printable http://schuchert.. We'll perform all of these conversions at once and see what we end up with (it won't be pretty)..Ejb3Tutorial3 . Add @Stateless annotation to LibraryBean 3. Rename BookDao --> BookDaoBean 2. Currently it has one @Before method and one @BeforeClass method. Update setupLibrary to simply lookup the library and set the library attribute. Rename LibraryTest --> LibraryBeanTest 2. Ultimately we will have one @Before method and two @BeforeClass methods. Rename Library --> LibraryBean 2.. Rename LoanDao --> LoanDaoBean 2. } LibraryTest 1. LibraryTest. 4. // . Why do you suppose we need to do that? At this point you might want to go back and verify that the tests in PatronDaoBeanTest still pass. Extract interface Library from LibraryBean 4.. Use @EJB to have the dao's injected Here's the top of LibraryBean: @Stateless public class LibraryBean implements Library { @EJB private ResourceDao resourceDao. @EJB private PatronDao patronDao.schuchert . Add method with @BeforeClass annotation that initializes the container Changes to LibraryBeanTest: We need to change how LibraryBeanTest sets itself up. @EJB private LoanDao loanDao.wikispaces. Notice that we re-assign the variable b just before the assert equals and it is that updated version of b that is sent to removeBookAndAuthors(). Extract interface BookDao from BookDaoBean LoanDao 1.

final BookDao bd = new BookDao().DAY_OF_MONTH.getTime().Ejb3Tutorial3 . CURRENT_PLUS_6 = c. the following method is unchanged @BeforeClass public static void setupDates() { Calendar c = Calendar. rd.DAY_OF_MONTH. CURRENT_PLUS_14 = c.getTime(). final PatronDaoBean pd = new PatronDaoBean().schuchert .getInstance().setLoanDao(ld).getTime().getTime().com/Ejb3Tutorial3+-+A+Mini+Aplication+R.setEm(getEm()).getTime().add(Calendar. CURRENT_PLUS_6 = c. 6). library.wikispaces. 6).removeTimeFrom(c).add(Calendar.lookup(Library.setEm(getEm()). CURRENT_PLUS_8 = c. 2).add(Calendar.setResourceDao(rd). public void setupLibrary() { final ResourceDaoBean rd = new ResourceDaoBean(). final LoanDao ld = new LoanDao().add(Calendar.getInstance(). CURRENT_DATE = c.DAY_OF_MONTH.add(Calendar.getTime().setPatronDao(pd). CURRENT_PLUS_15 = c. library. CURRENT_PLUS_15 = c. 6). ld. c. 6).DAY_OF_MONTH.add(Calendar.setEm(getEm()). 1).getTime(). library. } // Note.. CURRENT_DATE = c. 2).setBookDao(bd). c. c. library = new Library(). } To the following: @Before public void setupLibrary() { library = JBossUtil.DAY_OF_MONTH. c. } @BeforeClass public static void initContainer() { JBossUtil. c.removeTimeFrom(c). DateTimeUtil. } @BeforeClass public static void setupDates() { Calendar c = Calendar.DAY_OF_MONTH.A Mini Aplication Revisited Printable http://schuchert.getTime().DAY_OF_MONTH.class. 1). c. library. DateTimeUtil. pd.startDeployer(). c.getTime()..getTime().add(Calendar.DAY_OF_MONTH. c.add(Calendar. CURRENT_PLUS_8 = c. } 12 of 23 10/17/2010 9:54 AM . "LibraryBean/local"). CURRENT_PLUS_14 = c.

Here's a fact about the containAll() method on collections. but we don't own that exception so we will make our own exception class and throw it instead. lookupBookThatDoesNotExist When a method on a session bean throws an exception it will either return wrapped in an EJBException or "raw" depending on if the exception has the annotation @ApplicationException. private final Class clazz.key = key. } public int hashCode() { return getFirstName().com/Ejb3Tutorial3+-+A+Mini+Aplication+R. } return false. We need to add the following methods to Name.getLastName(). you'll notice that addBook passes. private final Object key..ejb. While Author has both hashCode and equals.equals() and Name. The method findResourceById currently uses EntityNotFoundException.hashCode(). } Run the test and after making this change. return rhs. it turns out that it does not contain public boolean equals(final Object object) { if (object instanceof Name) { final Name rhs = (Name) object. 13 of 23 10/17/2010 9:54 AM .schuchert . final Object key) { this.equals(getFirstName()) && rhs.equals(getLastName()).Ejb3Tutorial3 . After a little research it turns out that the book's authors does not appear to contain all of the authors. public EntityDoesNotExist(final Class clazz.getFirstName(). Fixing The Tests addBook The last line of the addBook method fails. On the other hand.hashCode(). this. Out of 20 tests we have 8 errors and 9 failures. It requires a proper definition of equals() and/or hashCode() depending on the type of collection. three tests passed successfully so it's not all bad.hashCode() * getLastName().clazz = clazz.. neither of which are defined. import javax. we are no longer using the base classes so we can delete the following classes: BaseDbDaoTest EntityManagerBasedTest Run LibraryBean test and things look a bit bleak. both of these methods depend on Name.wikispaces. @ApplicationException public class EntityDoesNotExist extends RuntimeException { private static final long serialVersionUID = 2838964492920113727L.A Mini Aplication Revisited Printable http://schuchert. While we're at it.ApplicationException. If we step through all of this. Here's a new exception: EntityDoesNotExist Exception package exception. So we need to add these missing methods to fix this problem.

} } Now we need to update two things: 1.getId().checkout(p.. we're using detached objects after they have been updated. id).findResourceById(ID_DNE). We'll fix the relationship first and the re-write the test to perform some additional lookups..class. ((Patron) rhs). } return r. you'll discover that Patron is missing equals() and hashCode(): @Override public boolean equals(final Object rhs) { return rhs instanceof Patron && EqualsUtil. } public Class getClazz() { return } lookupPatronThatDoesNotExist The test suffers from the Same problem as the above example. First.hashCode().class) public void lookupBookThatDoesNotExist() { library.Ejb3Tutorial3 .schuchert . b. 14 of 23 10/17/2010 9:54 AM . if (r == null) { throw new EntityDoesNotExist(Resource. Update the method to throw this new exception 2.wikispaces. checkoutBook After digging into this problem a bit. } And the updated test method: @Test(expected = EntityDoesNotExist.A Mini Aplication Revisited Printable http://schuchert. Second. Change the (expected = ) clause of the unit test Here's the updated method in LibraryBean: public Resource findResourceById(Long id) { final Resource r = getResourceDao().equals(getId(). } @Override public int hashCode() { return getId(). } returnBook There are two problems with this test. Do the same thing. there's a lazily-initialized relationship.getId()).findById(id). } public Object getKey() { return key. CURRENT_DATE.getId()). library.

remove().A Mini Aplication Revisited Printable http://schuchert.returnResource( GlobalId=null: 1164776892890/6. Why? No equals() or hashCode().isCheckedOut()). If you do a little more digging.remove(l).getId()). Patron foundPatron = library... library.getResourceId().equals(getPatronId()) ((Loan) rhs).lang. you'll discover that this probably means you are trying to delete some object and doing so violates a foreign key constraint.size()). you'll find out that when you try to remove a loan from a collection of loans in a Patron.size()). tx=TransactionImpl:XidImpl[FormatId=257. BranchQual=null:1164776892890.nested throwable: (javax.isCheckedOut()). the loan is not removed.JBossRollbackException: Unable to commit. foundPatron . p. getEm(). what does this mean? After some researching and guessing. It mentions Loan.jboss. } One Final Change 15 of 23 10/17/2010 9:54 AM .findResourceById(b. assertTrue(foundBook.getId()).size().getCheckedOutResources() .getId()).flush().tm.getId()). Update LoanDaoBean public void remove(final Loan l) { l.hashCode(). getResource().findPatronById(p. localId=0:6]. @Override public int hashCode() { return getResourceId().schuchert . Resource foundBook = library.setLoan(null). Once we make these changes and re-run the test.getPatronId().wikispaces. we get the following exception: java. final int resourcesBefore = foundPatron.EntityNotFoundException: deleted entity passed to persist: [entity. assertEquals(0. assertEquals(resourcesBefore .getCheckedOutResources().RuntimeException: org.hashCode() * getPatronId().removeLoan(this).findPatronById(p.Loan#<null>]) OK.findResourceById(b. . status= STATUS_NO_TRANSACTION. Here they are: @Override public boolean return rhs && && } equals(final Object rhs) { instanceof Loan ((Loan) rhs). getEm(). } Update Loan public void remove() { getPatron(). } We need to make two more updates to get rid of this foreign key constraint.getFines().equals(getResourceId()).persistence.Ejb3Tutorial3 . assertFalse(b.1. foundPatron = library. foundBook. foundBook = library.getId()).

replace all List<Fine> with Set<Fine> and also replace all ArrayList<Fine>() with HashSet<Fine>().EAGER to the fines attribute. it will read two records for each one record. returnResourceThatsNotCheckedOut We are throwing an exception. There are two kinds of replacements you'll have to make: Replace all occurrences of List<Loan> with Set<Loan> Replace all occurrences of ArrayList<Loan>() with HashSet<Loan>() Finally.) There is an easy fix. findOverdueBooks This test is actually failing because of previous tests. 16 of 23 10/17/2010 9:54 AM . Here's one more thing that has to do with how JPA reads JoinTables.Ejb3Tutorial3 .. checkoutBookToPatronThatDoesNotExist Same problem as the previous test. However.wikispaces. run the test to verify that it now works. To fix the List<Fine>. we can verify that this test is not broken. we cannot really fix this test. returnResourceLate We have three problems with this test: Detached Object Lazy relationship Using List where we should use a Set To fix the detached object problem. checkoutBookThatDoesNotExist We should replace EntityNotFoundException with EntityDoesNotExist Exception. checkoutBookThatIsAlreadyCheckedOut Same problem as with the previous test. Since we have not made our tests isolated. Here's the order in which you can drop all records from the database: author_book patron_fine fine author book loan patron dvd director book resource address There are other orders you could use. but this one works. add In the case of our Loan join table. look up the patron after returning the resource and just before the asserts. ResourceNotCheckedOut.A Mini Aplication Revisited Printable http://schuchert.. (Insert reference as to why. Clean up the database and run this test to verify that it works. To fix the lazy relationship. change this to Set<Loan> and update all of the related code in Loan get it to compile. In the Patron we store a List<Loan>. that has not had the @ApplicationException annotation added to it.schuchert .

em.executeUpdate()..createNativeQuery("delete from dvd").createNativeQuery("delete from resource").A Mini Aplication Revisited Printable http://schuchert. once we work on making each of our tests isolated.Ejb3Tutorial3 . directory book and resource before loan. you'll need to add an optional library to your classpath: ehcache-1.executeUpdate(). payFineInsufficientFunds InsufficientFunds needs to be an application exception. patronCannotCheckoutWithFines PatronHasFines class should be an application exception.2. make sure to get a fresh version of the dvd. em.getTransaction().executeUpdate().executeUpdate(). returnDvdLate This is a detached object problem.createNativeQuery("delete from address"). em. checkoutDvdAndBook 17 of 23 10/17/2010 9:54 AM .createNativeQuery("delete from loan").executeUpdate().createNativeQuery("delete from patron_fine"). we'll need to remove this method. And this method makes it impossible to look at the contents of the database after running the tests. It also slows things down and would not work with a pre-populated database. em.schuchert .executeUpdate().begin(). em.wikispaces.jar If you've used the same directories as these instructions. Move dvd.createNativeQuery("delete from book").commit().createNativeQuery("delete from author"). here is one that will do it: @After public void cleanupDatabase() { EntityManagerFactory f = Persistence.executeUpdate().createEntityManager(). em. checkoutDvd This is a detached object problem. EntityManager em = f. the order from above changes.executeUpdate(). You need to update both the patron and the dvd before the asserts. em. After the call to checkout and before the asserts. em.createNativeQuery("delete from author_book").getTransaction(). This Is a Temporary Fix Note. em. em. em..0_Embeddable_ALPHA_9\optional-lib Possible Reordering Also.createEntityManagerFactory("lis").com/Ejb3Tutorial3+-+A+Mini+Aplication+R.createNativeQuery("delete from patron"). if you managed to fix the OneToOne.executeUpdate(). } Notes Additional Jar To get this to work.executeUpdate(). So this really is temporary scaffolding until we can get to the next phase of cleaning up properly after each test. em. em.createNativeQuery("delete from fine"). patronsWithOverdueBooks Same problem as above.executeUpdate(). you'll find the file here: C:\libs\jboss-EJB-3.createNativeQuery("delete from director"). If you'd like to add a temporary method to your test class to clean up after each test.

Make the metho PatronDaoBeanTest..getDao() static Once you've done that. comment out or delete the cleanupDatabase method (and make sure to get the annotation). assertTrue(((Book) found).getId()). Test Isolation Finally. } finally { PatronDaoBeanTest. You need to update both the dvd and the book before the asserts. Along the way we're going to have to make a few big changes to make all of this work. lookupBookThatDoesNotExist This test creates no objects so no cleanup is necessary. } finally { ResourceDaoBeanTest.A Mini Aplication Revisited Printable http://schuchert. we create a loan. This is a detached object problem. make sure your database is clean. Run this test by itself and verify that nothing remains in the database after executing the test. you can change the test: @Test public void addPatron() { final Patron p = createPatron(). So in addition to removing the two books and patrons that are created as a 18 of 23 10/17/2010 9:54 AM .getId()). addPatron We have a method in PatronDaoBeanTest that we could use. } } lookupPatronThatDoesNotExist This test creates no objects so no cleanup is necessary.removeBookAndAuthors(b).Ejb3Tutorial3 . Next. checkoutBook When we checkout a book. but we need to make two changes: 1. try { final Set<Author> authors = b.findPatronById(p.schuchert .com/Ejb3Tutorial3+-+A+Mini+Aplication+R. addBook This one is straightforward. assertNotNull(found).removePatron public and static 2. try { final Patron found = library. final Resource found = library.wikispaces. } } To test this.. Make the method PatronDaoBeanTest.getAuthors(). assertTrue(found instanceof Book).findResourceById(b.getAuthors().removePatron(p). we need to make our test clean up after themselves. We'll clean up each test one after the other.containsAll(authors)). We can use the method removeBookAndAuthors in the ResourceDaoBeanTest: @Test public void addBook() { final Book b = createBook().

found. } public void removeFine(final Fine f) { getResourceDao(). found. This one requires a bit more work. b2.dueDateEquals(CURRENT_PLUS_14)). ResourceDaoBeanTest. We need to add it both to the Library interface and provide an implementation for this method in the LibraryBean: public void removePatron(Long id) { final Patron found = findPatronById(id).removePatron(id). for (Fine f : fines) { removeFine(f). list. final Book b2 = createBook(). First the updated test: @Test public void checkoutBook() { final Book b1 = createBook().getId().A Mini Aplication Revisited Printable http://schuchert. for (Resource r : list) { assertTrue(r.getId()). ResourceDaoBeanTest.removeFine.schuchert . assertTrue(r. } } The finally block uses a method Library.removePatron that is new.getId()). we must also remove the loan.listResourcesOnLoanTo(p.getFines().find(Fine.wikispaces.isOnLoanTo(p)). final Patron p = createPatron().setFines(null). } } finally { library.getId()).removeBookAndAuthors(b1). } We also added the method ResourceDao. try { library.removeFind(f).getId()). assertEquals(2.removePatron( We need to add it to the interface and to ResourceDaoBean: public void removeFind(final Fine f) { final Fine found = getEm(). f.setCheckedOutResources(null). 19 of 23 10/17/2010 9:54 AM . final Set<Loan> loans = found. CURRENT_DATE.remove(l).getCheckedOutResources().getId(). } final Set<Fine> fines = found.removeBookAndAuthors(b2). final List<Resource> list = library .size()). b1.checkout(p.. result of this test. } patronDao..Ejb3Tutorial3 . for (Loan l : loans) { getLoanDao().class.

tenderFine().removePatron(p. payFineExactAmount Up to this point we were doing so well. findOverdueBooks Remove the patron that is created then the two books. Follow the skeleton from returnBook. You can tell this by stepping through the code and the useful stack trace. patronsWithOverdueBooks Remove the patron that is created then the two books.getId()). Unfortunately. final Patron p = createPatron().. When we call Library. So we have two options: The Patron entity uses some Dao to remove the Fine entities from the database The Patron dao returns both the fines remove and the balance and lets the caller deal with the Follow the skeleton from returnBook.. the fines removed from its collection need to be deleted. First the background..removeBookAndAuthors(b).. To fix this.schuchert . Here's the skeleton: final Book b = createBook(). if (found != null) { getEm(). ResourceDaoBeanTest. calculateTotalFinesForPatron Remove the patron that is created then the two books. try { // .A Mini Aplication Revisited Printable http://schuchert.remove(found). checkoutBookToPatronThatDoesNotExist Remove the created book. we remove fines from our entities but we do not remove them properly. when we pay fines. } returnResourceLate This test can use the same skeleton as returnBook to clean up after itself. checkoutBookThatDoesNotExist Remove the created patron.. Follow the skeleton from returnBook. a message goes to Patron. The patron removes fines from its collection based on the amount tendered and then returns the balance.. Follow the skeleton from returnBook. } } returnBook Give the support for removing patrons. unchanged . we need to add just a bit of infrastructure. Follow the skeleton from returnBook. } finally { library. Unfortunately. we can now use that in the returnBook test. 20 of 23 10/17/2010 9:54 AM . checkoutBookThatIsAlreadyCheckedOut Remove the two Patrons then remove the book.Ejb3Tutorial3 . returnResourceThatsNotCheckedOut This test only needs to remove a book. Follow the skeleton from returnBook. Follow the skeleton from returnBook.

finesPaid = finesPaid.balance = balance. import java. finesPaid. FinesPaidAndBalance package complexreturns.getFinesPaid()) { removeFine(f). getFines(). public FinesPaidAndBalance(final List<Fine> finesPaid.clear(). which we have not had to do so far. Here are all the necessary changes. public class FinesPaidAndBalance { final private List<Fine> finesPaid. } public double getBalance() { return balance.. return new FinesPaidAndBalance(finesPaid. We'll take option 2.A Mini Aplication Revisited Printable http://schuchert. final private double balance. } else { throw new InsufficientFunds().addAll(getFines()). double amountTendered) { final Patron p = getPatronDao(). this.retrieve(patronId).util. } 21 of 23 10/17/2010 9:54 AM .totalFines).Ejb3Tutorial3 .pay public FinesPaidAndBalance pay(final double amountTendered) { double totalFines = calculateTotalFines(). } } LibraryBean.size()). } public List<Fine> getFinesPaid() { return finesPaid. The first option potentially creates a circular dependency and also has and entity dealing with the database.wikispaces.tenderFine public double tenderFine(final Long amountTendered . } } Patron. final FinesPaidAndBalance finesPaid = p. for (Fine f : finesPaid. final double balance) { this.Fine. if (totalFines <= amountTendered) { List<Fine> finesPaid = new ArrayList<Fine>(getFines().pay(amountTendered)..getBalance().List.schuchert . import entity. } return finesPaid.

. organizational mandate. An object is told how to get ahold of something it needs by setting the reference before it becomes active. replace lists with collections Name magic for mappedBy: Side with 'mappedBy' is the inverse side. lookups. returnDvdLate Remove the patron following the skeleton from returnBook. checkoutDvdAndBook Remove the patron and the book using the skeleton from returnBook. Merge returns a new object (unless the object is already managed) Injection using @EJB Use a set when possible (instead of list). You'll also need to remove the director. * visual side discussion How to know when something detached/attached.. entity beans done right (jpa).. FAQ Q/A @EJB Automatically filling in a dao. Generally speaking.schuchert . commercial support. If the type of the Bean is unambigious. brett: transaction demarcation. Stateful: Things where requirements dictate holding on to objects. etc. Your challenge is to somehow call the ResourceDao. You'll also need to remove the director. patronCannotCheckoutWithFines Remove the created patron and book following the skeleton from returnBook.Ejb3Tutorial3 . (learning how to debug jpa issues) 22 of 23 10/17/2010 9:54 AM . checkoutDvd Remove the patron following the skeleton from faster. You'll also need to remove the director. Brett: Why use EJB3? class: security. Your challenge is to somehow call the ResourceDao.remove() method passing in the id of the dvd.xml must be in right location or le be your butt.. Would local interface imply different semantics than remote? The strictness could be better for method passing in the id of the dvd. How is sun making any money? Brett: Why should you use or not use stateful/stateless session beans? Stateless session beans: things you can fire and forget. payFineInsufficientFunds Remove the created patron and book. Could you explain injection again? A mechanism to implement Inversion of Control. Your challenge is to somehow call the ResourceDao. easy web services.. Take Aways persistence. Other side is the owner ('can exist alone'). Generated values might not make it back to your object if it runs outside the context of a transaction. How do you hold on to the same stateful bean object across requests? Store the delegate/ref in the httpsession.remove() method passing in the id of the dvd.A Mini Aplication Revisited Printable http://schuchert. Follow the skeleton from returnBook. then JNDI will automatically insert your session bean reference. standard (community+materials). Bi-directional relationships: how to properly delete (+verify it's cleaned up) Try/catch/finally (in test) sometimes better than @Before/@After for certain init/cleanup There's some value in having to suffer.

.Ejb3Tutorial3 . Portions not contributed by visitors are Copyright 2010 Tangient LLC.wikispaces.A Mini Aplication Revisited Printable are licensed under a Creative Commons Attribution Share-Alike 2. 23 of 23 10/17/2010 9:54 AM .com/Ejb3Tutorial3+-+A+Mini+Aplication+R. Extended Context: use w/stateful beans.schuchert .5 License.wikispaces. keeps the cache open after the end of transaction ** <--back Contributions to http://schuchert.

Sign up to vote on this title
UsefulNot useful