Thus far, we have only created methods for persisting entities of the type Campaign
. However, our application also requires us to be able to modify Donation
objects. We’ll accomplish this step-by-step in the following section by applying the knowledge we have learned so far.
Again, we can use the project from the last chapter. If you have already closed it, you can start another temporary session by clicking the following link. Don’t forget that the whole work is volatile unless you sign up to Codenvy and persist the project in your own workspace.
5.1 Creating a Service to Handle Donations
To edit Donation
objects, we first require a service, which we’ll name DonationService
for the sake of consistency. This will provide two methods to our application: getDonationList
and addDonation
. Using the ID of a Campaign
object, the method getDonationList
returns a list of all the Donation
objects that have been created for the associated campaign. Our application also contains the method doDonation
, which allows a donation to be made to a campaign. This method requires the ID of the Campaign
object and the the Donation
object to be created as a parameter. The resulting service interface can be found in Listing 5-1; this should be stored in the package press.turngeek.mycampaign.services
.
package press.turngeek.mycampaign.services; import press.turngeek.mycampaign.model.Donation; import java.util.List; public interface DonationService { List<Donation> getDonationList(Long campaignId); void addDonation(Long campaignId, Donation donation); }
Listing 5-1 DonationService interface
We can now begin with the implementation of the DonationServiceBean
. Let’s start by looking at its method, getDonationList
:
public List<Donation> getDonationList(Long campaignId) { Campaign managedCampaign = entityManager.find(Campaign.class, campaignId); List<Donation> donations = managedCampaign.getDonations(); return donations; }
Here, the method find of the class EntityManager
returns the instance of the campaign that possesses the specified identity.
The method getDonations
is then invoked on this instance. This returns the list of Donation
objects assigned to the Campaign
object. This list can be passed to the invoker of the service using return.
Let’s now turn to the second method, addDonation
:
public void addDonation(Long campaignId, Donation donation) { Campaign managedCampaign = entityManager.find(Campaign.class, campaignId); donation.setCampaign(managedCampaign); entityManager.persist(donation); }
As above, we’ll begin by using the find method of the class EntityManager
to create a managed instance of the Campaign
object with the passed ID.
This instance is assigned to the passed Donation
object as a campaign using the method setCampaign
. The persist method of the class EntityManager
can then be used to persist the Donation
object. When this happens, JPA takes care of the relations automatically; the foreign key of the Campaign
object with the passed ID is therefore automatically entered in the campaign
column of the table DONATION
.
The complete implementation of the service can be found in Listing 5-2. Please save this file in the package press.turngeek.mycampaign.services
.
package press.turngeek.mycampaign.services; import press.turngeek.mycampaign.model.Campaign; import press.turngeek.mycampaign.model.Donation; import javax.ejb.Stateless; import javax.inject.Inject; import javax.persistence.EntityManager; import java.util.List; @Stateless public class DonationServiceBean implements DonationService { @Inject private EntityManager entityManager; @Override public List<Donation> getDonationList(Long campaignId) { Campaign managedCampaign = entityManager.find(Campaign.class, campaignId); List<Donation> donations = managedCampaign.getDonations(); return donations; } @Override public void addDonation(Long campaignId, Donation donation) { Campaign managedCampaign = entityManager.find(Campaign.class, campaignId); donation.setCampaign(managedCampaign); entityManager.persist(donation); } }
Listing 5-2 DonationServiceBean class
5.2 Finalising the Use Case Donate Money
Now that the implementation of the DonationService
has enabled us to persist Donation objects, the use case Donate money can be finalised. The implementation process is very simple. First, the bean DonateMoneyController
is afforded access to the DonationService
via the @Inject
annotation:
@Inject private DonationService donationService;
Next, the following two lines must be added at the beginning of doDonation
method of the DonateMoneyController
:
getDonation().setStatus(Status.IN_PROCESS); donationService.addDonation(getCampaignId(), getDonation());
The first line changes the status of the Donation
object to IN_PROCESS
. This tells a yet-to be created background process that this Donation
object still needs to be to edited. In the second line, the Donation
object and the campaign ID are passed to the DonationService
assigned to the donation form. The implementation of the use case Donate money is now complete.
As mentioned earlier Codenvy has problems resolving inner types. So please add the following import statement manually to the class DonateMoneyController
:
import press.turngeek.mycampaign.model.Donation.Status;
Restart the application with the changes from this section – you should now be able to create a new Donation
object utilizing the use case Donate Money. But be warned: at this stage, invoking the list of donations for a campaign will still result in an error. We’ll rectify this until section 5.4.
5.3 Calculating the Amount Donated So Far
Our requirements specification stipulates that the total amount donated to a campaign should be displayed to the organizer in the overiew of all campaigns.
To enable this, we previously provided the attribute amountDonatedSoFar within the entity Campaign
. Although this currently has no value, the amount donated to a campaign so far can be determined easily via the following JPQL query:
SELECT SUM(d.amount) FROM Donation d WHERE d.campaign = :campaign
The query generates the sum of the amount attributes for all Donation
objects assigned to a particular campaign. This query corresponds almost exactly to its equivalent in SQL. The only difference lies in the parameter to be passed and its type.
In JPQL, parameters are resolved using a preceding colon, “:”. Thus, the expression :campaign references the value of the parameter campaign. Since, in contrast to SQL, JPQL works with objects, this must store a Campaign
object, while in SQL it would contain a primary key of the table CAMPAIGN
.
We see once again that JPQL and SQL are very similar in terms of their syntax, but work with different data types (JPQL with objects and SQL with tables).
The value returned by the query must now be stored in the attribute amountDonatedSoFar
. To achieve this, we must first store the above query as a NamedQuery
in the class Campaign
. As a NamedQuery
, it looks as follows:
@NamedQuery(name = Campaign.getAmountDonatedSoFar, query = "SELECT SUM(d.amount) FROM Donation d WHERE d.campaign = :campaign")
We must now extend the Campaign
class with this query:
@NamedQueries({ @NamedQuery(name = Campaign.findAll, query = "SELECT c FROM Campaign c ORDER BY c.name"), @NamedQuery(name = Campaign.getAmountDonatedSoFar, query = "SELECT SUM(d.amount) FROM Donation d WHERE d.campaign = :campaign") }) @Entity public class Campaign { public static final String findAll = "Campaign.findAll"; public static final String getAmountDonatedSoFar = "Campaign.getAmountDonatedSoFar"; …
To enable us to reference the NamedQuery
in a type-safe way, we have once again created a constant. This time, the name of the attribute is Campaign.getAmountDonatedSoFar
.
By specifying this name, the CampaignServiceBean
can use this query to calculate the amount donated so far. The following method is added to the bean to accomplish this task:
private Double getAmountDonatedSoFar(Campaign campaign) { TypedQuery<Double> query = entityManager.createNamedQuery(Campaign.getAmountDonatedSoFar, Double.class); query.setParameter("campaign", campaign); Double result = query.getSingleResult(); if (result == null) result = 0d; return result; }
In this method, the TypedQuery<Double>
object of the NamedQuery
is created using the createNamedQuery
method of the class EntityManager
. The relevant Campaign
entity (that is, the Campaign
entity for which the amount donated so far is to be calculated) is passed on this object using the method setParameter
. The name of the parameter is campaign
, which means that it corresponds to the name in the NamedQuery
.
The query is executed using the method getSingleResult
and returns the sum of the donations made so far as Double
. If no donations have yet been made, this value is null
. Since this is undesirable, this case is intercepted and the value 0d
returned instead.
To inject the amount donated so far in a campaign object, campaign, we must use the following code:
campaign.setAmountDonatedSoFar(getAmountDonatedSoFar(campaign))
This is carried out at the appropriate point; in our case, when we use the CampaignServiceBean
to retrieve the list of Campaign
objects from the database. To accomplish this, we must modify the getAllCampaigns
method of the bean in such a way that the above code is run for each campaign. Using a simple for-loop, we get the following implementation (Sorry for the loop! Currently the Codenvy’s editor uses Java 7):
public List<Campaign> getAllCampaigns() { TypedQuery<Campaign> query = entityManager.createNamedQuery(Campaign.findAll, Campaign.class); List<Campaign> campaigns = query.getResultList(); for (Campaign campaign : campaigns) { campaign.setAmountDonatedSoFar(getAmountDonatedSoFar(campaign)); } return campaigns; }
The attribute amountDonatedSoFar
is currently persisted in the database. This, however, is not necessary, since we recalculate the attribute each time a Campaign
object is read. JPA now offers the option not to persist an attribute of an entity. To take advantage of this, the relevant attribute must be annotated with @Transient
from the package javax.persistence
. We will do this now for the attribute amountDonatedSoFar
in the class Campaign
:
@Transient private Double amountDonatedSoFar;
This means that the attribute will no longer be persisted and that the calculation of the amount donated to a campaign so far is complete. However, one issue remains: if you test this by invoking the list of donations following a restart, an exception will appear. We’ll work on resolving this in the following section.
5.4 Displaying Donations in the Donation List
If you now try to invoke the list of donations in your application, an exception of the type LazyInitializationException
will appear. In fact, this type of exception is encountered quite frequently when progamming with JPA. This is caused when an entity’s attribute requiring a database is accessed, but the instance of the entity is no longer managed by an EntityManager
object. This normally becomes the case as soon as a transaction is ended.
In our example, the Campaign
object supplied by the CampaignProducer
in the context of the bean ListDonationsController
no longer has a database connection. In light of this, it is not possible to access the donations attribute that would contain the assigned Donation
object based on a relation.
A simple solution to this problem is to request the list of Donation
objects from DonationService
and use the method setDonations
to deposit it in the Campaign
object used by the view of the use case Display list of donations (file listDonations.xhtml
).
To accomplish this, we must extend the method doListDonations
of the bean ListCampaignsController
to include this service access, since this is the method that forwards to the view and passes the Campaign
object to the CampaignProducer
.
We’ll start by adding the DonationService
to the ListCampaignsController
using the annotation @Inject
:
@Inject private DonationService donationService;
The method doListDonations
should then be modified to add the method setDonations
, which will assign the list of Donation
objects to the passed Campaign
object:
public String doListDonations(Campaign campaign) { final List<Donation> donations = donationService.getDonationList(campaign.getId()); campaign.setDonations(donations); campaignProducer.setSelectedCampaign(campaign); return Pages.LIST_DONATIONS; }
Again, Codenvy might have problems with inner types. Please add the import statements for the types press.turngeek.mycampaign.model.Donation
, press.turngeek.mycampaign.services.DonationService
and java.util.List
, manually.
Following a new build of the application (including a restart), we would expect the application to run smoothly. Instead, a further unexpected LazyInitializationException
appears.
Though a list of Donation
objects is now contained in the Campaign
object, this list has no database connection. This is because, like the Campaign
object, it is a non-managed instance.
The background to this situation is that even though the getDonationList
method of the DonationServiceBean
invokes the method getDonations
to create a list of Donation
objects, this method only returns the attribute donations. This, in turn, only stores a proxy, which does not contain any data in its initial state.
A corresponding query is only made to the database when this attribute is accessed. However, this must occur within the current transaction – that is, within the method getDonationList
. Accessing the attribute after the transaction leads to the LazyInitializationException
we have already observed.
A simple trick is to execute/run a method on the proxy that does not carry out any changes to the data. In the case of a list, this could be the method size
, which returns the number of elements in the list[1].
Invoking this method will lead to the list of Donation
objects being retrieved from the database. This means that we must modify getDonationList
in the class DonationServiceBean
as follows:
public List<Donation> getDonationList(Long campaignId) { Campaign managedCampaign = entityManager.find(Campaign.class, campaignId); List<Donation> donations = managedCampaign.getDonations(); donations.size(); return donations; }
Following a further build of the application, including a restart, (nearly) all errors will be rectified and the list of donations will finally be correctly displayed. One thing still does not work, i.e. deleting a campaign. We will come back to this later on.
If you have other problems with your project, please compare it with the project behind the following link:
Discussion
Use the message board below to give the authors your feedback or to discuss this page’s topic with other readers (in English please!). Please don’t expect the authors to answer directly, but they might update the content of this site according to your feedback.
- Alternatively, you can the solve the issue using a self-created
NamedQuery
that returns all donations belonging to a campaign; however, this will not be shown here. ↵