Using the Java Persistence API with Amazon SimpleDB
AWS community developer Nathan A. Good digs into the Travel Log sample web application and shows how it uses the Java Persistence API with Amazon SimpleDB.
Submitted By: Craig@AWS
AWS Products Used: Amazon S3, Amazon SimpleDB
Language(s): Java
Created On: October 19, 2017
This how-to article introduces you to the TravelLog application, a sample implementation of the Java Persistence API (JPA) that you can use to connect to Amazon SimpleDB. Before you dive in, however, make sure you have the following prerequisites:
Amazon SimpleDB
Amazon SimpleDB is a non-relational, cloud-based database. Because it is based in the cloud, you don't need to have local database servers, administrators, or local infrastructure to support the database. Amazon SimpleDB offers the benefits of zero administration, scalability, and flexibility.
To use Amazon SimpleDB, you need to sign up for an Amazon Web Services (AWS) account. When that's done, log in to the AWS management console, click Account, and then click Security Credentials to get your keys. You'll use them later after you download the source code but before you compile the code and deploy it to Tomcat.
Amazon does charge for the use of Amazon SimpleDB. If you just want to play around or run something like this Travel Log application to see how it works, you may be able to take advantage of the Free Tier, which provides a certain amount of time at no cost.
JPA
The JPA is a framework that allows you to quickly wire your Java objects up to a database using annotations in your Java code. Using the JPA helps eliminate a lot of the tedious work of writing database access code or custom frameworks for the database. In addition, the JPA allows you to divorce your code from the specifics of your database connection. So long as you stick to the stock JPA annotations, you can relatively easily point your Java application to a different database and run your application without a whole lot of work—especially work that is typically required because of Structured Query Language (SQL) implementation differences.
A typical class marked up with JPA looks like this:
@Entity public class User implements Serializable, UserDetails { private static final long serialVersionUID = 1629672935573849314L; @Id @GeneratedValue(strategy=GenerationType.AUTO) private String id; @Column(unique=true, nullable=false) private String username; // Snipped... }
To learn what these attributes mean, see "Understanding the JPA Code" later in this article for more information about how they work in the TravelLog application.
The JPA implementation can be any implementation that complies with the application programming interface (API). By default, many applications use Hibernate as the JPA implementation. SimpleJPA, which the TravelLog application uses, is an implementation of the JPA specification that allows your application to connect to Amazon SimpleDB. See "Resources" at the end of this article to learn more about the JPA and SimpleJPA.
The TravelLog Sample Application
The TravelLog sample application is a Java project that demonstrates how to use SimpleJPA to store your information in Amazon SimpleDB. The application runs on a Web application server such as Tomcat. You can download the application source, build it, and run it to see how it works.
Getting the Source
To get the application source, go to https://aws.amazon.com/code/1264287584622066. After you've downloaded the source and before you compile the application, update the simplejpa.properties to include the access and secret key information you received earlier when you signed up for your AWS account.
Your version of the simplejpa.properties file should look like this:
# You must configure the accessKey, secretKey, and lobBucketName, before # running the TravelLog application accessKey=THISISMYACCESSKEY secretKey=mf8WXumD9+Yc4bIuYHK5NTL+Lf4IoD2xmX-jThOd # Note: lobBucketName should be all lower case lobBucketName=thisismyaccesskeytravellogbucket
When you are finished editing the file, save it. You should now be ready to build the Web archive (WAR) file and try out the TravelLog application in your browser.
Building the Source
With the application source code downloaded, you use Apache Maven to build the code. Open a terminal, use cd to enter the directory, and then type:
$ mvn compile war:war
The build process downloads all the dependencies that the application uses, compiles all the code, and builds the WAR file. The WAR file, called TravelLog-1.0.0.war, resides in the target directory of the project.
One dependency that Maven automatically downloads is the SimpleJPA library, which is listed in the pom.xml file. Because Maven handles the dependency for you, you don't have to download the library yourself.
When the build is complete, you will see a message like this:
[INFO] Building war: /home/user1/Projects/travellog/target/TravelLog-1.0.0.war [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESSFUL [INFO] ------------------------------------------------------------------------ [INFO] Total time: 15 seconds [INFO] Finished at: Sat Nov 20 18:57:15 CST 2010 [INFO] Final Memory: 19M/118M [INFO] ------------------------------------------------------------------------
Deploy this WAR file to the Tomcat server by copying the file to the webapps folder in Tomcat's home, like this:
$ cp TravelLog-1.0.0.war ${TOMCAT_HOME}/webapps
If you already have Tomcat started when you copy the WAR file, Tomcat picks up the change automatically and deploys the WAR file. If you see the WAR file unpacked in the webapps folder (it will be in the TravelLog-1.0.0 folder), Tomcat has found the WAR and deployed it. You can be certain that the WAR has started successfully be viewing the localhost.log file in your Tomcat's log folder. The log file will contain an entry that looks like this if the Web application has been deployed correctly:
INFO: Set web app root system property: 'webapp.root' = [/home/ngood/local/tomcat/webapps/TravelLog-1.0.0/] Nov 18, 2010 8:06:41 PM org.apache.catalina.core.ApplicationContext log INFO: ContextListener: contextInitialized()
Now that you have the Web application up and running, it's time to take a quick peek to see what it does.
Viewing the Application in Action
Open the TravelLog application by going to the Tomcat URL of the Web application. Because port 8080 is the default port for Tomcat, on your machine that URL is probably https://localhost:8080/TravelLog-1.0.0/. The server name may differ if you've deployed the application to a different server.
If for some reason the server URL does not work but you know that Tomcat is running, check the logs again to make sure nothing is wrong with the configuration. If you've specified incorrect keys for the access keys or if the bucket name is formatted incorrectly, the application will fail to start, and you should see the error message in the logs.
When you run the application for the first time, it prompts you to set up a user name and password. These are stored in the database, so when you start the application the next time, you won't have to register as a new user. Instead, you will sign into the application by clicking the link on the home page shown in Figure 1.
Figure 1. Home page with no entries
Notice that the description that you entered before is now printed on the home page for the application.
To add a new log entry, click the Add Journal Entry link. Add the information about the travel entry in the window provided, as Figure 2 shows.
Figure 2. Adding a new entry
When you're finished, you will see the entry in the Web page as shown in Figure 3.
Figure 3. Seeing the listed entries
You can add as many entries as you'd like. As you do so, the application stores them in Amazon SimpleDB using the JPA.
Understanding the JPA Code
Because you just added an entry in the journal, let's start looking at Java annotations added for the JPA by examining the Entry class, which resides in the com.amazon.aws.samplecode.travellog.entity package. The class is shown here:
package com.amazon.aws.samplecode.travellog.entity; import java.util.Date; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Lob; import javax.persistence.ManyToOne; import javax.persistence.Transient; import org.directwebremoting.annotations.RemoteProperty; import org.directwebremoting.annotations.RemoteProxy; import java.text.SimpleDateFormat; /** * The entry class maps to a single journal entry. */ @Entity @RemoteProxy public class Entry { @Id @GeneratedValue(strategy=GenerationType.AUTO) @RemoteProperty private String id; @RemoteProperty private String title; @RemoteProperty private String entryText; @RemoteProperty private String destination; private Date date; @RemoteProperty private String formattedDate; private String snsArn; private static final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); public Date getDate() { return date; } public void setDate(Date date) { this.date = date; this.formattedDate = formatter.format(date); } private Journal journal; @ManyToOne(cascade=CascadeType.ALL) public Journal getJournal() { return journal; } public void setJournal(Journal journal) { this.journal = journal; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } @Lob //Lob annotation tells SimpleJPA to store the entry text in S3 public String getEntryText() { return entryText; } public void setEntryText(String entryText) { this.entryText = entryText; } public String getDestination() { return destination; } public void setDestination(String destination) { this.destination = destination; } @Transient public String getFormattedDate() { return formattedDate; } public void setFormattedDate(String formattedDate) { this.formattedDate = formattedDate; } public String getSnsArn() { return snsArn; } public void setSnsArn(String snsArn) { this.snsArn = snsArn; } @Transient public SimpleDateFormat getFormatter() { return formatter; } }
First, notice that there are no Amazon.com-specific or SimpleJPA import statements in the Java code. This is one of the important aspects of good JPA code—that there are only annotations from the javax.persistence package.
The Entity annotation lets the framework know that the class represents an object that will be stored in the database. For now, ignore the RemoteProxy and RemoteProperty annotations: They are used to establish a connection between Java objects on the server and JavaScript code in the Web browser (see "Resources" for more information).
As shown in the Entry class code, there is a property called id of type String that uniquely identifies the property in the database. The property is marked up with the Id annotation. In addition, the GeneratedValue annotation tells the framework how to automatically generate the value for the identifier, so you don't have to be concerned with creating the value yourself.
The Entry class has a relationship with the Journal class. The Journal class represents the travel journal, so there is only one of them. Each journal will have many different entries, so the Entry class has the relationship shown like so:
@ManyToOne(cascade=CascadeType.ALL) public Journal getJournal() { return journal; }
The CascadeType.ALL annotation tells the JPA framework to cascade all changes between the journal and the entry.
Using an Entity Manager
Now that you understand how the TravelLog application's entities are marked up, take a look at the implementation of the javax.persistence.EntityManager interface, which you use to work with the marked-up entities. Fortunately, the TravelLog application does not have to provide its own implementation of the interface: That's what the SimpleJPA library provides. However, the code inside the TravelLog application does have to create an instance of the implementation so that the EntityManager can be used to put objects away, retrieve them, and delete them.
The TravelLogDOASimpleDBImpl class, which implements the TravelLogDAO interface, creates an instance of the SimpleJPA's implementation of the EntityManager interface and uses it to work with the entries. The code here demonstrates an example of saving an Entry:
public void saveEntry(Entry entry) { EntityManager em = null; try { //Storage fails if id is an empty string, so nullify it if (entry.getId()!=null && entry.getId().equals("")) { entry.setId(null); } em = factory.createEntityManager(); em.persist(entry); } finally { if (em!=null) { em.close(); } } }
Using the JPA, you can also write a query to get more than one entry using specific criteria. The code that follows demonstrates the method that gets all of the query entries for a specific journal:
@SuppressWarnings("unchecked") public List getEntries(Journal journal) { EntityManager em = null; try { em = factory.createEntityManager(); Query query = em.createQuery("select e from com.amazon.aws.samplecode.travellog.entity.Entry e " + "where e.journal=:journal and date is not null and id is not null order by e.date desc"); query.setParameter("journal",journal); return (List)query.getResultList(); } finally { if (em!=null) { em.close(); } } }
The query is written in a language called the Java Persistence Query Language (JPQL), which is a language for selecting objects that is similar to the SQL used in relational database management systems (RDBMSs). Similar to a PreparedStatement in Java's Java Database Connectivity (JDBC) API, you can call a method on the Query object to set the value for a parameter place holder. This method, called setParameter(), substitutes the value that you pass in into the query. As demonstrated in the query, you use the entity's name instead of a table name. The getResultList() method does the final work of getting the results of the query as a Java List.
To see where the TravelLogDAO implementation is used, see the TravelLogController class, which is responsible for coordinating the work in the Web application. Shown here is the doSaveEntry() method, which calls the saveEntry() method shown earlier.
@RequestMapping("/saveEntry.do") @Secured("ROLE_ADMIN") public ModelAndView doSaveEntry (Entry entry, BindingResult result, ModelMap map) { if (entry.getTitle().length()==0) { result.reject("title","You must enter a title for this entry"); } if (result.hasErrors()) { doHome(map); map.addAttribute("popupScreen","entry_div"); return new ModelAndView("home",map); } Journal journal = dao.getJournal(); entry.setJournal(journal); //Make an initial save to get entry id populated dao.saveEntry(entry); TravelLogSNSManager sns = new TravelLogSNSManager(); sns.createTopic(entry); //save with arn set dao.saveEntry(entry); doHome(map); return new ModelAndView("redirect:home.do"); }
Looking at the Configuration
Configuration provides the application with the specifics of the implementation so that the TravelLog application code isn't littered with hard-coded classes. Because the TravelLog application code does not contain references to specifc implementations and uses only the javax.persistence package, you can modify existing Java applications that use the JPA to use Amazon SimpleDB for the most part by changing a few configuration values.
The com.amazon.aws.samplecode.travellog.dao.TravelLogDAOSimpleDBImpl implementation is defined in the applicationContext.xml file for the travelLogDao. The entities are also defined in the applicationContext.xml file. This travelLogDao is then injected into the dao property on the TravelLogController.
In addition to the applicationContext.xml file, the persistence.xml file is used to list the entities that are included in the TravelLog persistence unit. This persistence unit is referred to in the TravelLogDAOSimpleDBImpl class in the creation of the SimpleJPA's custom implementation of the javax.persistence.EntityManagerFactory interface.
Summary
Amazon Web Services's sample application, TravelLog, demonstrates a real-world usage of using the JPA and an implementation library, SimpleJPA, to connect a Web application written in Java code with Amazon SimpleDB. Because Amazon SimpleDB operates in the cloud, it offers scalability, performance, and ease of use that is difficult to match.
Resources
This article highlights a few aspects of working with Amazon SimpleDB. Here are a few more resources available to Java developers to help you learn more:
- AWS—Learn more about each Web service in the AWS suite.
- Amazon SimpleDB—Learn more about Amazon SimpleDB on the AWS Web site.
- Create an AWS Account—Sign on to create an AWS account.
- Developer Connection—The community site for AWS developers includes forums on AWS, a Solutions Catalog for examples of what your peers have built, and more.
- Resource Center—Part of the Developer Connection site, the Resource Center has links to tutorials, code samples, technical documentation, and other resources for building your application on AWS.
- SimpleJPA library—Download and learn how to use the library to connect with Amazon SimpleDB using the JPA.
- Java Persistence API—A Simpler Programming Model for Entity Persistence
About the Author
Nathan A. Good lives in the Twin Cities area of Minnesota. Professionally, he does software development, software architecture, and systems administration. When he's not writing software, he enjoys building PCs and servers, reading about and working with new technologies, and trying to get his friends to make the move to open source software. He's written and co-written many books and articles, including Professional Red Hat Enterprise Linux 3, Regular Expression Recipes: A Problem-Solution Approach, and Foundations of PEAR: Rapid PHP Development.