5 Persisting and Displaying Remaining Entities

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.

Start Cloud IDE

My-Campaign Persist Campaigns

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:

Start Cloud IDE

My-Campaign Persist Donations

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.


  1. 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.