Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Master the New Persistence Paradigm with JPA : Page 2

In this article you will learn how to persist objects using the Java Persistence API (JPA), customize their mapping with annotations, and create a one-to-one relationship. Using the entity lifecycle you will control persistency and use the query language (JPQL) to query your objects.


advertisement
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 class t_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

Attribute Definition
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

Annotation Definition
@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.PostLoad When 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 @PreUpdate private 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 @PostUpdate public 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 class t_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.



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap