We have the following classes to convert:
- LoanDao
- BookDao
- Library
There’s only one test left to convert, LibraryTest. We’ll perform all of these conversions at once and see what we end up with (it won’t be pretty).
BookDao
- Rename BookDao –> BookDaoBean
- Add @Stateless annotation to BookDaoBean
- Extract interface BookDao from BookDaoBean
LoanDao
- Rename LoanDao –> LoanDaoBean
- Add @Stateless annotation to LoanDaoBean
- Extract interface LoanDao from LoanDaoBean
Library
- Rename Library –> LibraryBean
- Add @Stateless annotation to LibraryBean
- Extract interface Library from LibraryBean
- Use @EJB to have the dao’s injected
Here’s the top of LibraryBean:
LibraryTest
- Rename LibraryTest –> LibraryBeanTest
- Remove Base Class from LibraryBeanTest
- Update setupLibrary to simply lookup the library and set the library attribute.
- Add method with @BeforeClass annotation that initializes the container
Changes to LibraryBeanTest:
We need to change how LibraryBeanTest sets itself up. Currently it has one @Before method and one @BeforeClass method. Ultimately we will have one @Before method and two @BeforeClass methods.
We need to change from this:
To the following:
While we’re at it, 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. Out of 20 tests we have 8 errors and 9 failures. On the other hand, three tests passed successfully so it’s not all bad.
Fixing The Tests
addBook
The last line of the addBook method fails. After a little research it turns out that the book’s authors does not appear to contain all of the authors. If we step through all of this, it turns out that it does not contain any.
Here’s a fact about the containAll() method on collections. It requires a proper definition of equals() and/or hashCode() depending on the type of collection. While Author has both hashCode and equals, both of these methods depend on Name.equals() and Name.hashCode(), neither of which are defined. So we need to add these missing methods to fix this problem.
We need to add the following methods to Name.java:
Run the test and after making this change, you’ll notice that addBook passes.
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. The method findResourceById currently uses EntityNotFoundException, but we don’t own that exception so we will make our own exception class and throw it instead.
Here’s a new exception:
EntityDoesNotExist Exception
Now we need to update two things:
- Update the method to throw this new exception
- Change the (expected = ) clause of the unit test
Here’s the updated method in LibraryBean:
And the updated test method:
lookupPatronThatDoesNotExist
The test suffers from the Same problem as the above example. Do the same thing.
checkoutBook
After digging into this problem a bit, you’ll discover that Patron is missing equals() and hashCode():
returnBook
There are two problems with this test. First, we’re using detached objects after they have been updated. Second, there’s a lazily-initialized relationship. We’ll fix the relationship first and the re-write the test to perform some additional lookups.
Once we make these changes and re-run the test, we get the following exception:
OK, what does this mean? After some researching and guessing, you’ll discover that this probably means you are trying to delete some object and doing so violates a foreign key constraint. It mentions Loan. If you do a little more digging, you’ll find out that when you try to remove a loan from a collection of loans in a Patron, the loan is not removed. Why? No equals() or hashCode(). Here they are:
We need to make two more updates to get rid of this foreign key constraint.
Update LoanDaoBean
Update Loan
One Final Change
Here’s one more thing that has to do with how JPA reads JoinTables. In the case of our Loan join table, it will read two records for each one record. (Insert reference as to why.) There is an easy fix. In the Patron we store a List
- Replace all occurrences of **List
** with **Set ** - Replace all occurrences of **ArrayList
()** with **HashSet ()**
Finally, run the test to verify that it now works.
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, look up the patron after returning the resource and just before the asserts.
To fix the lazy relationship, add fetch=FetchType.EAGER to the fines attribute.
To fix the List
returnResourceThatsNotCheckedOut
We are throwing an exception, ResourceNotCheckedOut, that has not had the @ApplicationException annotation added to it.
checkoutBookThatIsAlreadyCheckedOut
Same problem as with the previous test.
checkoutBookThatDoesNotExist
We should replace EntityNotFoundException with EntityDoesNotExist Exception.
checkoutBookToPatronThatDoesNotExist
Same problem as the previous test.
findOverdueBooks
This test is actually failing because of previous tests. Since we have not made our tests isolated, we cannot really fix this test. However, we can verify that this test is not broken. Clean up the database and run this test to verify that it works.
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.
If you’d like to add a temporary method to your test class to clean up after each test, here is one that will do it:
Notes
Additional Jar
To get this to work, you’ll need to add an optional library to your classpath:
ehcache-1.2.jar
If you’ve used the same directories as these instructions, you’ll find the file here:
C:\libs\jboss-EJB-3.0_Embeddable_ALPHA_9\optional-lib
Possible Reordering
Also, if you managed to fix the OneToOne, the order from above changes. Move dvd, directory book and resource before loan.
This Is a Temporary Fix
Note, once we work on making each of our tests isolated, 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. So this really is temporary scaffolding until we can get to the next phase of cleaning up properly after each test.
patronsWithOverdueBooks
Same problem as above.
payFineInsufficientFunds
InsufficientFunds needs to be an application exception.
patronCannotCheckoutWithFines
PatronHasFines class should be an application exception.
checkoutDvd
This is a detached object problem. After the call to checkout and before the asserts, make sure to get a fresh version of the dvd.
returnDvdLate
This is a detached object problem. You need to update both the patron and the dvd before the asserts.
checkoutDvdAndBook
This is a detached object problem. You need to update both the dvd and the book before the asserts. —-
Test Isolation
Finally, we need to make our test clean up after themselves. Along the way we’re going to have to make a few big changes to make all of this work. We’ll clean up each test one after the other.
addBook
This one is straightforward. We can use the method removeBookAndAuthors in the ResourceDaoBeanTest:
To test this, make sure your database is clean. Next, comment out or delete the cleanupDatabase method (and make sure to get the annotation). Run this test by itself and verify that nothing remains in the database after executing the test.
lookupBookThatDoesNotExist
This test creates no objects so no cleanup is necessary.
addPatron
We have a method in PatronDaoBeanTest that we could use, but we need to make two changes:
- Make the method PatronDaoBeanTest.removePatron public and static
- Make the metho PatronDaoBeanTest.getDao() static
Once you’ve done that, you can change the test:
lookupPatronThatDoesNotExist
This test creates no objects so no cleanup is necessary.
checkoutBook
When we checkout a book, we create a loan. So in addition to removing the two books and patrons that are created as a result of this test, we must also remove the loan.
This one requires a bit more work. First the updated test:
The finally block uses a method Library.removePatron that is new. We need to add it both to the Library interface and provide an implementation for this method in the LibraryBean:
We also added the method ResourceDao.removeFine. We need to add it to the interface and to ResourceDaoBean:
returnBook
Give the support for removing patrons, we can now use that in the returnBook test. Here’s the skeleton:
returnResourceLate
This test can use the same skeleton as returnBook to clean up after itself.
returnResourceThatsNotCheckedOut
This test only needs to remove a book. Follow the skeleton from returnBook.
checkoutBookThatIsAlreadyCheckedOut
Remove the two Patrons then remove the book. Follow the skeleton from returnBook.
checkoutBookThatDoesNotExist
Remove the created patron. Follow the skeleton from returnBook.
checkoutBookToPatronThatDoesNotExist
Remove the created book. Follow the skeleton from returnBook.
findOverdueBooks
Remove the patron that is created then the two books. Follow the skeleton from returnBook.
patronsWithOverdueBooks
Remove the patron that is created then the two books. Follow the skeleton from returnBook.
calculateTotalFinesForPatron
Remove the patron that is created then the two books. Follow the skeleton from returnBook.
payFineExactAmount
Up to this point we were doing so well. Unfortunately, when we pay fines, we remove fines from our entities but we do not remove them properly. You can tell this by stepping through the code and the useful stack trace.
To fix this, we need to add just a bit of infrastructure. First the background. When we call Library.tenderFine(), a message goes to Patron. The patron removes fines from its collection based on the amount tendered and then returns the balance. Unfortunately, the fines removed from its collection need to be deleted. 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 fined.
The first option potentially creates a circular dependency and also has and entity dealing with the database, which we have not had to do so far. We’ll take option 2. Here are all the necessary changes.
FinesPaidAndBalance
Patron.pay
LibraryBean.tenderFine
payFineInsufficientFunds
Remove the created patron and book. Follow the skeleton from returnBook.
patronCannotCheckoutWithFines
Remove the created patron and book following the skeleton from returnBook.
checkoutDvd
Remove the patron following the skeleton from returnBook.
Your challenge is to somehow call the ResourceDao.remove() method passing in the id of the dvd. You’ll also need to remove the director.
returnDvdLate
Remove the patron following the skeleton from returnBook.
Your challenge is to somehow call the ResourceDao.remove() method passing in the id of the dvd. You’ll also need to remove the director.
checkoutDvdAndBook
Remove the patron and the book using the skeleton from returnBook.
Your challenge is to somehow call the ResourceDao.remove() method passing in the id of the dvd. You’ll also need to remove the director.
Comments