Tutorial web development (with JSF) IX – Application “Books”, Part II

In part VII of this tutorial you’ll find some requirements of the book application. The application is live now, and you can find it here [1]. The information about the books and the reviews is stored in a database. JavaServer Faces is well suited for database  driven server applications. Thus, developing web applications with JSF normally includes developing with database techniques. In the context of Java EE 6, this usually would be Java Persistence API (JPA). And often it touches other techniques like Context and Dependency Injection (CDI), Bean Validation and other stuff. The book application will use a couple of this too.

Ok, lets start with persistence. Information about the books has to be stored somewhere. A book is defined as an entity and is stored in a database table (precisely: in a couple of tables, but first we cover the main one). Here is, how this table is created:

CREATE TABLE Book(
    bookId int IDENTITY PRIMARY KEY,
    bookTitle varchar(200),
    bookSubtitle varchar(200),
    bookAuthor varchar(255),
    bookPublisher varchar(45),
    bookYear int,
    bookLanguage varchar(10),
    bookISBN varchar(45),
    bookShorttext varchar(500),
    bookReference varchar(500),
    bookAdReference varchar(50)
)

The existing application uses Microsoft SQL Server. IDENTITY nominates this attribute to be an auto-increment. This might differ, depending on the database. For example, using MySQL, it has to be AUTO_INCREMENT instead.

Every attribute starts with a prefix of “book”. This might be beneficial for joins, cause no qualifier for the database is needed. But it is only beneficial if a developer creates SQL statements by herself. For JPA, there is no need for such a prefix. Contrary, this causes additional work (for a special mapping). But it is a way (not the only one), DB admins like. Often, you have to use existing databases, and then you might have a similar scenario.

     

If you start from the scratch, with no DB schema defined, you may omit this work totally. If no table is available, JPA has the ability to create it for you (from an entity class you created manually).

To start with JPA, we first need a so called Persistence Unit. It will describe how to access the database, based on the information of a Data Source. NetBeans assists in creating it. Choose New File -> Persistence -> Persistence Unit.

In the Data Source, select New Data Source…, which opens the Create Data Source dialog. Give it the name jdbc/books and select your database connection. If it isn’t setup yet, you may choose Create New Database Connection… to create one. Click Finish. NetBeans creates a new Persistence Unit (PU) and opens it in the editor. It is showed in design mode, but you may switch to source. Under the hood, this is nothing, but a XML file.

     
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="BooksPU" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/books</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
    </properties>
  </persistence-unit>
</persistence>

The application server will provide you the database connection using these properties (and the data source, which is defined outside of the PU, as well). I would not discuss every detail of the PU. To dig deeper than described in this tutorial, you may read the full JPA spec at [2]. If you like to check or change the data source, you’ll find it on the services tab.

Next, we will create an entity class related to the table defined above. NetBeans offers a wizard for this task too. Choose New File -> Persistence -> Entity Classes from Database. Select the correct data source and then the table.

If the wizard hasn’t displayed the tables, check whether you defined a schema name. As the time of writing, a connection to the default schema (using MS SQL Server) would perform good, but fails within this wizard.

If ok, click Next and provide a package name (de.muellerbruehl.books in the books app). Uncheck Generate JAXB Annotations, which is not needed here. Click Finish. NetBeans creates a full-fledged entity class for you, with additional information like named queries. Every table attribute is mapped to a object attribute and contains the database limits to check the attributes length via Bean Validation which is enabled for JPA Entities in a JSF application by default.

@Entity
@Table(name = "Book")
...
public class Book implements Serializable {
    ...
    @Size(max = 200)
    @Column(name = "bookTitle")
    private String bookTitle;

The class itself is annotated to b an entity class, using table book.

If you omit the column annotations, it still works the same way. By default, JPA maps an Java attribute to a table attribute by it’s name. This annotation is needed only, if the names differ, e.g. usually in your app you would use “title”, not “bookTitle”.

The id, which is an auto-increment column, needs a slightly different annotation. The language field will be implemented as a Java Enum. And beside that main table, the application stores some additional information in related tables. Thus, the final entity class differs from the one just created by the wizard. In fact, you may instruct the wizard to use related tables too. This is beyond the scope of this tutorial. I invite you to experiment a bit in this direction.

The entity class, used in the book app is this:

package de.muellerbruehl.books.entities;

import de.muellerbruehl.books.enums.Language;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.*;
import javax.validation.constraints.Size;

/**
 * This entity provides information about books like title, publisher and
 * especial information about its review.
 *
 * @author mmueller
 * (c) 2012, 2013 by Michael Müller, michael.mueller@mueller-bruehl.de
 * Public domain for private usage only.
 * For any other usage, please contact the author.
 */
@Entity
@Table(name = "Book")
public class Book implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "bookId")
    private Integer _id = -1;
    @Size(max = 200)
    @Column(name = "bookTitle")
    private String _title;
    @Size(max = 200)
    @Column(name = "bookSubtitle")
    private String _subTitle;
    @Size(max = 255)
    @Column(name = "bookAuthor")
    private String _author;
    @Size(max = 45)
    @Column(name = "bookPublisher")
    private String _publisher;
    @Column(name = "bookYear")
    private Integer _year;
    @Column(name = "bookLanguage")
    @Enumerated(EnumType.STRING)
    private Language _language;
    @Size(max = 45)
    @Column(name = "bookISBN")
    private String _isbn;
    @Size(max = 500)
    @Column(name = "bookShorttext")
    private String _shortText;
    @Size(max = 500)
    @Column(name = "bookReference")
    private String _reference;
    @Size(max = 50)
    @Column(name = "bookAdReference")
    private String _adReference;
    @OneToMany
    @JoinTable(name = "mapBookCategory",
    joinColumns = @JoinColumn(name = "bcBookId"),
    inverseJoinColumns = @JoinColumn(name = "bcCategoryId"))
    private List<Category> _categories;
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "BookId", referencedColumnName = "bookId")
    private List<ReviewLink> _reviewLinks;
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "rvBookId", referencedColumnName = "bookId")
    private List<Review> _reviews;
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "btBookId", referencedColumnName = "bookId")
    private List<BookTrans> _translations;

    // <editor-fold defaultstate="collapsed" desc="getter / setter">    
    // omited for brevity
    // </editor-fold>
    // <editor-fold defaultstate="collapsed" desc="hashcode / equals / toString">    
    // omited for brevity
    // </editor-fold>

    /**
     * indicates, whether areview for the desired language exists
     * @param lang
     * @return 
     */
    public boolean hasReview(Language lang) {
        for (Review review : getReviews()) {
            if (review.getLanguage() == lang) {
                return true;
            }
        }
        return false;
    }

    /**
     * convenient method to check, whether a German review exists
     * @return 
     */
    public boolean hasGermanReview() {
        return hasReview(Language.de);
    }

    /**
     * convenient method to check, whether an English review exists
     * @return 
     */
    public boolean hasEnglishReview() {
        return hasReview(Language.en);
    }

    /**
     * Gets the short text for the given language.
     * If not found, return the one of the book entity as default.
     * @param lang
     * @return 
     */
    public String getShortTextOrDefault(Language lang) {
        String shortText = getShortText(lang);
        return shortText.isEmpty() ? getShortText() : shortText;
    }

    /**
     * Gets the short text for the given language.
     * If not found, return an empty string.
     * @param lang
     * @return 
     */
    public String getShortText(Language lang) {
        for (BookTrans translation : getTranslations()) {
            if (translation.getLanguage() == lang) {
                return translation.getShorttext();
            }
        }
        return "";
    }

    /**
     * sets the book's short text for the desired language
     * @param lang
     * @param shortText 
     */
    public void setShortText(Language lang, String shortText) {
        String trimText = shortText.trim();
        for (BookTrans translation : getTranslations()) {
            if (translation.getLanguage() == lang) {
                // found existing translation
                if (trimText.length() > 0) {
                    translation.setShorttext(trimText);
                } else {
                    getTranslations().remove(translation);
                }
                return;
            }
        }

        // no translation exists, create new one
        if (!trimText.isEmpty()) {
            BookTrans translation = new BookTrans();
            translation.setShorttext(trimText);
            translation.setBookId(_id);
            translation.setLanguage(lang);
            getTranslations().add(translation);
        }
    }

    /**
     * Gets the review for the given language.
     * If not found, return an empty string.
     * @param lang
     * @return 
     */
    public String getReviewText(Language lang) {
        for (Review review : getReviews()) {
            if (review.getLanguage() == lang) {
                return review.getText().replace("[p]", "\r\n"); // older entries used [p] for CRLF, thus replace it
            }
        }
        return "";
    }

    /**
     * sets the book's review text for the desired language
     * @param lang
     * @param shortText 
     */
    public void setReviewText(Language lang, String text) {
        String trimText = text.trim();
        for (Review review : getReviews()) {
            if (review.getLanguage() == lang) {
                // existing review found
                if (trimText.isEmpty()) {
                    getReviews().remove(review);
                } else {
                    review.setText(trimText);
                }
                return;
            }
        }
        // new review
        if (!trimText.isEmpty()) {
            Review review = new Review();
            review.setBookId(_id);
            review.setLanguage(lang);
            review.setText(trimText);
            getReviews().add(review);
        }
    }
}

Some special annotations, this is the magic needed to make an entity out of a POJO. Take a look at the @OneToMany annotations, which maps relations to other tables.

But how can it be used for data retrieval, for CRUD (create, read, update, delete) operations? And what are the relations to the other tables? This will be subject of future part.

[1] http://it-rezension.de/

[2] http://jcp.org/aboutJava/communityprocess/final/jsr317/index.html

 

To web development content.

This entry was posted in NetBeans, Programming, Tutorial, Web development and tagged , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>