devxlogo

Master the New Persistence Paradigm with JPA

Master the New Persistence Paradigm with JPA

bject/relational mapping (ORM)?in other words, persisting Java objects to a relational database?has become a major topic recently, thanks, in part, to a proliferation of advanced methods that attempt to make it easier. Among these various technologies are Entity Beans 2.x, TopLink, Hibernate, JDO, and even JDBC with DAO. Faced with so many incompatible choices, the Java EE expert group took inspiration from these popular frameworks and created the Java Persistence API (JPA), which is suitable for use with Java EE or SE applications.

In a nutshell, the JPA’s aim is to bring back the POJO programming model to persistence. Despite the fact that the specification is still bundled with EJB 3, JPA can be used outside the EJB container, and that’s the way this article will use it: Plain Old Java Objects running in a Java SE application.

In this article I will model a simple address book application for a fictional music company called Watermelon, to store customers’ home addresses in a database. Watermelon sells and delivers music articles such as instruments, amplifiers, scores, books and so on. I will use an incremental and iterative approach to develop and persist the business model (see Figure 1).

Author’s Note: A follow-up to this article is now available. Please see “Persistence Pays Off: Advanced Mapping with JPA“.
What You Need
To compile and execute the code that comes with this article you need the JDK 1.5 (http://java.sun.com/javase/downloads/index_jdk5.jsp) and Ant 1.7 (http://ant.apache.org/bindownload.cgi). Test classes are written in JUnit 4.1 (http://www.junit.org/index.htm). This article uses MySQL 5 (http://dev.mysql.com/downloads/mysql/5.0.html#downloads) and TopLink Essentials (http://www.oracle.com/technology/products/ias/toplink/jpa/download.html) as the implementation of JPA. Before running the code, you need to create a database called watermelonDB in MySQL with the root user and no password.

Figure 1. A class diagram for the Watermelon business model is shown.

How Does JPA Work ?
Inspired from ORM frameworks such as Hibernate, JPA uses annotations to map objects to a relational database. These objects, often called entities, have nothing in common with Entity Beans 2.x. JPA entities are POJOs that do not extend any class nor implement any interface. You don’t even need XML descriptors for your mapping. If you look at the API you will see that JPA is made up of only a few classes and interfaces. Most of the content of the javax.persistence package is annotations. With that, let’s look at some code.

@Entitypublic class Customer {  @Id  private Long id;  private String firstname;  private String lastname;  private String telephone;  private String email;  private Integer age;  // constuctors, getters, setters}

This bit of code (constructors, getters, and setters are not shown to make it easier to read) shows a simple Customer class. It has an identifier, a first name, last name, a telephone number, an email address and the age of the customer. To be persistent, this class has to follow some simple JPA rules:

  • It must be identified as an entity using the @javax.persistence.Entity annotation.
  • It must have an identifier attribute annotated with @javax.persistence.Id.
  • It must have a no argument constructor.
  • The code above shows you the minimum required to define a persistent object. Let’s now manipulate this object. I want to persist my customer, update some of its attributes, and delete it from the database. These CRUD operations are made through the javax.persistence.EntityManager interface of JPA. For those of you who are familiar with the DAO pattern, the EntityManager can be seen as a DAO class providing you with a set of life cycle methods (persist, remove) and finders (find) (see Listing 1).

    After creating an EntityManager using a factory (EntityManagerFactory), I instantiate my Customer object (using the new operator like any other Java object) and pass to it some data such as the id, the last name, the email address, the age, and so on. I use the EntityManager.persist() method to insert this object into the database. I can then find this object by its identifier using the EntityManager.find() method and update the email address by using set methods. The EntityManager doesn’t have an update method per se. Updates are made through setters. Next I delete the object using EntityManager.remove(). Note that this code uses explicit transactions. That’s why persist, update, and remove methods are surrounded by transaction.begin() and transaction.commit().

    Author’s Note: There are some differences when using the EntityManager in a Java EE application. You don’t create it using the factory (EntityManagerFactory). Instead you inject the persistence unit to the EntityManager using the @PersistenceContext(unitName = “watermelonPU”) annotation. In Java EE you can also avoid explicit transactions (begin, commit, rollback) and let the container deal with them.

    There is a piece of information missing in Listing 1: which database to use? The answer is in the EntityManagerFactory. It takes a parameter that refers to a specific persistence unit (watermelonPU in this case). Persistence units are declared in the persistence.xml file and contain information such as the database to use and the JDBC driver using some implementation-specific properties.

                oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider        entity.Customer                                              

    In the code above there is only one persistence unit, named watermelonPU (persistence.xml can contain several persistence units). You can see a persistence unit as a collection of entities (the class element) that share common properties. In this case, it refers to the watermelonDB database URL, JDBC driver, and credentials. Under the properties element you will find TopLink-specific properties such as toplink.ddl-generation. This property is used by TopLink to automatically generate the tables if they don’t already exist. This means that once I’ve executed Listing 1, TopLink has created a table to store the customer information. Table 1 shows the DDL (Data Definition Language) of the customer table.

    Author’s Note: As its name indicates, JPA is just an API that needs to be implemented by a persistence provider. For this article I used TopLink but there are others available such as Hibernate 3.2, OpenJPA, or Kodo. I also used MySQL but TopLink supports other databases such as Oracle, Sybase, and DB2.

    Table 1. DDL for the Customer Table.

    Field Type Null Key
    IDbigint(20)NOPRI
    LASTNAMEvarchar(255)YES 
    TELEPHONEvarchar(255)YES 
    FIRSTNAMEvarchar(255)YES 
    EMAILvarchar(255)YES 
    AGEint(11)YES 

    This is the DDL that JPA automatically generated from the annotated Customer class. Thanks to the coding-by-exception approach that guides JPA (and Java EE 5 in general) I didn’t have to do too much to get this DDL. Coding-by-exception makes life easier for the developer: You only need to add custom code when the default is inadequate. In my case, because I didn’t specify the table or column name on the Customer class, JPA assumes that the name of the table is equal to the name of the class, and that the columns have the same names as the attributes. Data types are also mapped to defaults (eg. String is mapped to varchar(255)).

    Adding Functionalities and Customizing the Mapping
    At this point I’d like to improve a few things. First of all, I don’t want to set the identifier of the object but instead I want JPA to automatically increment it. Thanks to annotations, that’s pretty easy to do: I just need to annotate my identifier attribute with @javax.persistence.GeneratedValue. This annotation generates a primary key in four possible ways:

    • AUTO (default) lets the persistent provider (TopLink in my case) decide which of the following three possibilities to chose from
    • SEQUENCE uses a SQL sequence to get the next primary key
    • TABLE requires a table with two columns: the name of a sequence and its value (that’s the default TopLink strategy)
    • IDENTITY uses an identity generator such as a column defined as auto_increment in MySQL.

    Now I want to improve my mapping. First I’ll change the name of the table to t_customer instead of just customer. Then, I’ll make the first name and last name of the customer mandatory. The maximum length of the telephone number should be 15 characters and the column email should be renamed e_mail with an underscore. All these little twists can be done with annotations (see Table 2).

    Table 2: Customizing the Customer Mapping

    Customer classt_customer DDL
    @Entity
    @Table(name = "t_customer")
    public class Customer {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(nullable = false)
    private String firstname;
    @Column(nullable = false, length = 30)
    private String lastname;
    @Column(length = 15)
    private String telephone;
    @Column(name = "e_mail")
    private String email;
    private Integer age;

    // constuctors, getters, setters
    }
    mysql>

     desc t_customer
    +-----------+--------------+------+
    | Field | Type | Null |
    +-----------+--------------+------+
    | ID | bigint(20) | NO |
    | FIRSTNAME | varchar(255) | NO |
    | LASTNAME | varchar(30) | NO |
    | TELEPHONE | varchar(15) | YES |
    | e_mail | varchar(255) | YES |
    | AGE | int(11) | YES |
    +-----------+--------------+------+

    Author’s Note: The annotations used on attributes (@Id, @Column, @GeneratedValue) can also be used on getters.

    To change the name of the table I annotate the class with @javax.persistence.Table. The @javax.persistence.Column annotation is used to twist the columns’ definitions and has a set of attributes shown in Table 3.

    Table 3: Attributes of the @Column annotation

    AttributeDefinition
    String name() default “”;Column name
    boolean unique() default false;Is the value unique?
    boolean nullable() default true;Does it accept the null value?
    boolean insertable() default true;boolean updatable() default true;Authorizes or not the column to be inserted or updated
    String columnDefinition() default “”;DDL definition of the column
    String table() default “”;When used with multi-tables, specifies which table the attribute is mapped to
    int length() default 255;Max length
    int precision() default 0;int scale() default 0;Precision used for numerical values

    Callback Annotations
    The mapping between the Customer class and the t_customer table suits me better now, thanks to the many attributes of the @Column and @Table annotations. There are two other things that I’d like to do. First, ensure that every phone number is entered using international codes, commencing with a ‘+’ symbol. Second, calculate the customer’s age from his/her date of birth. I have several choices of how to accomplish these tasks, but I’m going to use the callback annotations.

    During its lifecycle, an entity will be loaded, persisted, updated, or removed. An application can be notified before or after these events occur using annotations. JPA has a set of callback annotations that can be used on methods and let the developer add any business logic he/she wants. JPA will then call such annotated method before or after these events. Table 4 lists the callback annotations.

    Table 4: Callback Annotations

    AnnotationDefinition
    @javax.persistence.PrePersist
    @javax.persistence.PostPersist
    Before and after persisting the object
    @javax.persistence.PreUpdate
    @javax.persistence.PostUpdate
    Before and after updating the object attributes
    @javax.persistence.PreRemove
    @javax.persistence.PostRemove
    Before and after removing the object
    @javax.persistence.PostLoadWhen the data of the object is loaded from database

    How can I use these annotations for my needs? First, I’ll deal with the telephone number format. I want to check that the first character of the telephone number is ‘+’. I can do that before the entity gets persisted or updated. I just have to create a method (validatePhoneNumber in my example but the name is irrelevant) with some business logic that I annotate with @PrePersist and @PreUpdate; JPA will do the rest.

    @PrePersist@PreUpdateprivate void validatePhoneNumber() {  if (telephone.charAt(0) != '+')    throw new IllegalArgumentException("Invalid phone number");  }}

    For the customer’s age, I’ll do something similar. I’ll calculate the customer’s age after the date of birth has been inserted (@PostPersist) or updated (@PostUpdate), and of course, each time a customer is loaded from the database (@PostLoad).

    @PostLoad@PostPersist@PostUpdatepublic void calculateAge() {  Calendar birth = new GregorianCalendar();  birth.setTime(dateOfBirth);  Calendar now = new GregorianCalendar();  now.setTime(new Date());  int adjust = 0;  if (now.get(Calendar.DAY_OF_YEAR) - birth.get(Calendar.DAY_OF_YEAR) < 0) {    adjust = -1;  }  age = now.get(Calendar.YEAR) - birth.get(Calendar.YEAR) + adjust;}

    To make this work, I need to add a new attribute to my Customer class: date of birth. To notify JPA to map this attribute to a date I use the @Temporal annotation with a TemporalType.DATE attribute (choices are DATE, TIME, and TIMESTAMP). That looks good. I am able to calculate the age of the customer but do I really need to persist this information? No, knowing that the value changes every year. To make the attribute age transient I can use the @Transient annotation (the table will not have an age column anymore, see Table 5).

    Table 5: @Transient and @Temporal annotation

    Customer classt_customer DDL
    @Entity
    @Table(name = "t_customer")
    public class Customer {
    @Id
    @GeneratedValue
    private Long id;
    @Column(nullable = false)
    private String firstname;
    @Column(nullable = false, length = 30)
    private String lastname;
    @Column(length = 15)
    private String telephone;
    @Column(name = "e_mail")
    private String email;
    @Column(name = "date_of_birth")
    @Temporal(TemporalType.DATE)
    private Date dateOfBirth;
    @Transient
    private Integer age;}
    mysql> desc t_customer
    +---------------+--------------+------+
    | Field | Type | Null |
    +---------------+--------------+------+
    | ID | bigint(20) | NO |
    | FIRSTNAME | varchar(255) | NO |
    | LASTNAME | varchar(30) | NO |
    | TELEPHONE | varchar(15) | YES |
    | e_mail | varchar(255) | YES |
    | date_of_birth | date | YES |
    +---------------+--------------+------+

    If you now run Listing 1, “Manipulating a Customer,” you will get an exception because the phone number is incorrect.

    One-to-One Relationship
    Now that I have mapped my Customer class the way I want and used callback annotations to validate and calculate data, I need to add an address. One customer has one, and only one, address so that Watermelon can send the customer a birthday present. I represent it as a separate class Address with an id, a street, a city, a zip code, and a country (see Table 6).

    Table 6: A Customer Has an Address

    Customer classAddress class
    @Entity
    @Table(name = "t_customer")
    public class Customer {

       @Id
       @GeneratedValue(strategy = GenerationType.AUTO)
       private Long id;
       @Column(nullable = false)
       private String firstname;
       @Column(nullable = false, length = 30)
       private String lastname;
       @Column(length = 15)
       private String telephone;
       @Column(name = "e_mail")
       private String email;
       private Integer age;

       private Address homeAddress;

       // constuctors, getters, setters
    }
    @Entity
    @Table(name = "t_address")

    public class Address {

       @Id
       @GeneratedValue

       private Long id;
       private String street;
       @Column(length = 100)
       private String city;
       @Column(name = "zip_code",    length = 10)
       private String zipcode;
       @Column(length = 50)
       private String country;
       // constuctors, getters, setters
    }

    As you can see in Table 6, the class Address uses the @Entity annotation to notify JPA that this is a persistent class and @Column to customize the mapping. Creating the relationship between Customer and Address is simple: I simply add an Address attribute on the Customer class. To persist the customer’s address information use the code below:

    public void createCustomerWithAddress() {  // Instantiates a Customer and an Address objecy  Customer customer = new Customer("John", "Lennon", "+441909", "[email protected]", dateOfBirth);  Address homeAddress = new Address("Abbey Road", "London", "SW14", "UK");  customer.setHomeAddress(homeAddress);  // Persists the customer with its address  trans.begin();  em.persist(homeAddress);  em.persist(customer);  trans.commit();  // Deletes the customer and the address  trans.begin();  em.remove(customer);  em.remove(homeAddress);  trans.commit();}

    As this code shows, you have to first instantiate a customer and an address object. To link the two I used a setter method (setHomeAddress) and then persisted each object in the same transaction. Because it would not make sense to have an address in the system that is not linked to a customer, when I remove the customer I also remove the address.

    But persisting and removing both objects seems like more work than it needs to be. Wouldn’t it be better if I could persist or remove just the root object (the Customer) and allow the dependencies to be automatically persisted or removed? Furthermore, coding-by-exception makes associations optional and, based on my requirements, I want a Customer to have exactly one Address, meaning that a null value is not allowed. I can accomplish all of that using the @OneToOne annotation together with @JoinColumn.

    @Entity@Table(name = "t_customer")public class Customer {  @Id  @GeneratedValue  private Long id;  (...)  @Transient  private Integer age;  @OneToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.REMOVE})  @JoinColumn(name = "address_fk", nullable = false)  private Address homeAddress;  // constuctors, getters, setters}

    @OneToOne is used to annotate a one-to-one relationship. It has several attributes including cascade used for cascading any type of action. In this example I want to cascade the persist and remove action. This way, when I remove?or persist?a customer object it will automatically carry out this action for the address. The fetch attribute tells JPA which policy to use when loading a relation. Either it can lazy load (LAZY) an association or eagerly load it (EAGER). In this example, I am using EAGER because I want to load the homeAddress as soon as the Customer object is loaded.

    The @JoinColumn annotation has almost the same attributes as the @Column one (see Table 2) except it’s used for association attributes. In this example, I rename the foreign key into address_fk and I don’t allow any null value (nullable = false).

    JPA will then create the following DDLs with an integrity constraint for the relationship between tables t_customer and t_address (see Table 7).

    Table 7: DDLs with integrity constraint

    t_customer DDLt_address DDL
    mysql> desc t_customer
    +---------------+--------------+------+
    | Field | Type | Null |
    +---------------+--------------+------+
    | ID | bigint(20) | NO |
    | FIRSTNAME | varchar(255) | NO |
    | LASTNAME | varchar(30) | NO |
    | TELEPHONE | varchar(15) | YES |
    | e_mail | varchar(255) | YES |
    | date_of_birth | date | YES |
    | address_fk | bigint(20) | NO |
    +---------------+--------------+------+
    mysql> desc t_address
    +----------+--------------+------+
    | Field | Type | Null |
    +----------+--------------+------+
    | ID | bigint(20) | NO |
    | CITY | varchar(100) | YES |
    | zip_code | varchar(10) | YES |
    | STREET | varchar(255) | YES |
    | COUNTRY | varchar(50) | YES |
    +----------+--------------+------+
    ALTER TABLE t_customer ADD CONSTRAINT FK_customer_address FOREIGN KEY (address_fk) REFERENCES t_address (ID)

     

    Querying Objects
    Until now I’ve been using JPA to map my objects to a relational database and using the entity manager to do some CRUD operations. But JPA also allows you to query objects. It uses the Java Persistent Query Language (JPQL), which is similar to SQL and is also independent of the database. It is a rich language that allows you to query any complex object’s model (associations, inheritance, abstract classes…).

    Queries use the SELECT, FROM, and WHERE keywords, plus a set of operators to filter data (IN, NOT IN, EXIST, LIKE, IS NULL, IS NOT NULL) or to control collections (IS EMPTY, IS NOT EMPTY, MEMBER OF). There are also functions that deal with Strings (LOWER, UPPER, TRIM, CONCAT, LENGTH, SUBSTRING), numbers (ABS, SQRT, MOD), or collections (COUNT, MIN, MAX, SUM). Like SQL, you can also sort the results (ORDER BY) or group them (GROUP BY).

    To query objects I need the EntityManager to create a Query object. Then I get the result of the query by calling the getResultList or getSingleResult when there is a single object returned. In the example below I want to find all the customers who have the first name ‘John’. I can do this in two ways: Either the string ‘John’ is fixed and it can be part of the JPQL query or it’s a parameter and I need to use the setParameter method.

    // Finds the customers who are called JohnQuery query = em.createQuery("SELECT c FROM Customer c WHERE c.firstname='John'");List customers = query.getResultList();// Same query but using a parameterQuery query = em.createQuery("SELECT c FROM Customer c WHERE c.firstname=:param");query.setParameter(":param", "John");List customers = query.getResultList();

    As you can see in this query, JPQL uses the object notation. You don’t query a table but an object. The character c (the name is irrelevant) is the alias for a customer object and c.firstname is the first name attribute of the customer object. If you wanted to find all the customers living in the U.S., you could use this notation to get to the country attribute of the address object: SELECT c FROM Customer c WHERE c.homeAddress.country=’US’.

    Here is a set of queries that we can do with JPQL:

    Table 8: JPQL queries

    QueryComments
    SELECT c FROM Customer cReturns all the customers
    SELECT c FROM Customer c WHERE c.firstname=’John’Returns the customers which first name is ‘John’
    SELECT c FROM Customer c ORDER BY c.lastnameReturns all the customers ordered by last name
    SELECT c FROM Customer c WHERE c.homeAddress.country=’US’Customers who live in the US

    Ready to Learn More?
    This article introduced the basis of JPA: How to declare a persistent object, how to map it into a table, how to CRUD your object, query it and query its associations. In a future article (coming soon) I will show you how JPA can map inheritance and other types of relationships (one-to-many, many-to-many). I will also show how JPQL can deal with abstract classes and navigate through complex associations.

    devxblackblue

    About Our Editorial Process

    At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

    See our full editorial policy.

    About Our Journalist