Introduction to Java Persistence API

Рубрика: Development, Java | 30 January 2008, 11:10 | Vadim Voituk

Ранее мне не приходилось в полноценной промышленной разработке использовать различные ORM-фреймворки а-ля iBATIS, Hibernate, Toplink etc.
Все мои познания в них ограничивались “поиграться и бросить” Hibernate & iBATIS.
Сейчас же, в предверии перехода на EJB3, жизнь заставила разобраться с JPA – оттуда и “ростут ноги” этой заметки.

Постараюсь продемонстирировать какой простой и понятной становится задача отображения этого класса на таблицы реляционной БД с ипользованием JPA.
При этом, дабы не отвлекаться на конфигурирование клиент-серверного окружения, проведем демонстрацию на обычном J2SE приложении.
Для хранения данных приложения воспользуемся Java-базой данных Apache Derby и JPA-провайдером
Oracle TopLink Essentials

Предположим у нас есть простой привычный POJO-класс представляющий сущность заметки в блог:)

public class BlogPost {
    private long id;
    private String title;
    private String body;
    private Date date;
    private boolean published;

    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    // ...тут будут ещё другие методы доступа к полям класса...
}

Добавим к нему немного “магии” JPA-аннотаций:

@Entity                    // Указываем что класс является сущностью
@Table(name = "post_list") // + и хранится в таблице post_list
public class BlogPost { ... }

Стоит сразу отметить что все аннотации, которые относятся к JPA находятся в пакете javax.persistence.*

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

Далее следует указать, что уникальным ID записи у нас будет свойство “id”, а также, что будем использовать автоматический механизм генерации его значения:

public class BlogPost {
    @Id
    @GeneratedValue (strategy = GenerationType.AUTO)
    private long id;
    ...

Также спецификация JPA для свойств типа java.util.Date и java.util.Calendar требует указания типа временного поля в БД.
Делается это с помощью аннотации @Temporal:

@Temporal(TemporalType.TIMESTAMP)
private Date date;

Завершаем манипуляции с многострадальным классом и создаем файл META-INF/persistence.xml такого содержания:

<?xml version="1.0" encoding="UTF-8" ?>
<persistence version="1.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_1_0.xsd">

<persistence-unit name="TestStore" transaction-type="RESOURCE_LOCAL">
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<class>com.voituk.jpaexample.BlogPost</class>
<properties>
<property name="toplink.jdbc.url"    value="jdbc:derby:myteststore;create=true" />
<property name="toplink.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="toplink.jdbc.user" value="app" />
<property name="toplink.jdbc.password" value="app" />
<property name="toplink.ddl-generation" value="create-tables" />
<property name="toplink.application-location" value="./db-schema"/>
<property name="toplink.logging.level" value="FINE" />
<property name="toplink.target-database" value="Derby" />
</properties>
</persistence-unit>
</persistence>

Основной конфигурационной единицей JPA является Persistence Unit – в нем описывается тип провайдера, который предоставляет “услуги” JPA а также список классов, за которые он “отвечает” – JPA-сущностей.
В блоке <properties> описываются параметры доступа к базе данных, её тип, настройки логирования etc.

На этом заканчивается вся магия и метапрограммирование, и переходим к написанию кода.

В первую очередь, для удобства инициализации обьектов добавим в класс BlogPost такой конструктор:

public BlogPost(String title, String body, Date date, boolean published) {
    this.title = title;
    this.body = body;
    this.date = date;
    this.published = published;
}

А так как JPA требует наличия у каждого класса-сущности конструктора по умолчанию, то добавляем и его:

public BlogPost() { /* Do nothing */ }

Далее программно создадим несколько записей, сохраним их в БД и выведем их на экран.

Управление сущностями (записями) в JPA производится с помощью экземпляра EntityManager-а, предоставляемого фабрикой, которая в свою очередь предоставляется провайдером JPA.
Для получения экземпляра EntityManager воспользуемя кодом:

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
//...

EntityManagerFactory factory = null;
EntityManager manager = null;
try {
    factory = Persistence.createEntityManagerFactory("TestStore");
    manager = factory.createEntityManager();

    //TODO: Add several blogPosts entities to database using EntityManager
    //TODO: Read all entities from database using EntityManager

} finally {
    if (manager!=null) manager.close();
    if (factory!=null) factory.close();
}

где “TestStore” – имя нашего PersistenceUnit-а, описанного в файле META-INF/persistence.xml

Код по добавлению записей в БД с использованием JPA выглядит просто и элегантно:

//Add several blogPosts entities to database using EntityManager
manager.getTransaction().begin();
manager.persist( new BlogPost("BlogPost 1", "This is first blog post", new Date(), true) );
manager.persist( new BlogPost("Just another blog post", "This is second blog post", new Date(), true) );
manager.getTransaction().commit();

Для получения из БД всех записей можно воспользоваться таким кодом:

Query query = manager.createQuery("SELECT obj FROM BlogPost AS obj ORDER BY obj.date DESC");
List list = (List) query.getResultList();

for (Iterator iterator = list.iterator(); iterator.hasNext();) {
    BlogPost blogPost = (BlogPost) iterator.next();
    System.out.println(blogPost.getId() + " - " + blogPost.getTitle());
}

В результате получим что-то вроде:
302 - Just another blog post
301 - BlogPost 1

При этом хочу сразу отметить, что строка запроса в вызове createQuery – это никак не SQL, а Java Persistence Query Language – более обьектно-ориентированная альтернатива SQL.
В то же время JPA предоставляет возможность задавать запросы и с помощью “старого доброго” SQL.

Если вас смущает обилие технической информации, которую TopLink “выбрасывает” на экран – можно отключить логирование установкой параметра
toplink.logging.level=OFF в файле META-INF/persistence.xml

Надеюсь такой небольшой простой примерчик показал основы JPA.
За более подробной информацией стоит глянуть приведнные ниже ссылки.

Полный исходный код приведенного примера (4,1 Мб)

Ссылки:
Standardizing Java Persistence with the EJB3 Java Persistence API
Understanding JPA
Java Persistence Query Language в Java EE5 Tutorial
Oracle TopLink JPA
How-To use TopLink JPA in Java SE – нюансы по использованию ToplLink в J2SE-среде

Комментариев: 13

13 Responses to “Introduction to Java Persistence API”

Комментарии:

  1. Chabster

    > предверии перехода на EJB3
    Который год оно уже длится? С лета 2005-го? 2.5 года? А еще ни один коммерческий сервер не ушел в продакшн. Круто!

  2. Andrey

    Если быть точным то финал спеков вышел в мае 2006-го, веблоджик 10 вышел в марте 2007-го и включал EJB 3.0., ну и для вебсферы ejb 3.0 feature pack уже есть какое то время.

  3. Vadim Voituk

    Вообще-то я имелл ввиду “переход на EJB3 в своем проекте” :)
    Но комментарии выдались полезными :)

  4. Boris

    Что можете сказать о JBoss Seam, годится он для написания серьезных приложений?

  5. Denis

    To Boris:
    С использованием JBoss Seam пишут серъезные приложения.

  6. Wissenstein

    Aha, автомат поел угловые скобки. Попробую исправить:

    В этом фрагменте кода:

    List<blogpost> list = (List<blogpost>) query.getResultList();

    for (Iterator iterator = list.iterator(); iterator.hasNext();) {
    BlogPost blogPost = (BlogPost) iterator.next();
    System.out.println(blogPost.getId() + ” – ” + blogPost.getTitle());
    }
    </blogpost></blogpost>
    очевидно, ошибка: параметр настраиваемого класса спутан с тэгом разметки документа. На мой взгляд, здесь надо либо убрать лишние фрагменты кода:

    List list = query.getResultList();

    for (Iterator iterator = list.iterator(); iterator.hasNext();) {
    BlogPost blogPost = (BlogPost) iterator.next();
    System.out.println(blogPost.getId() + ” – ” + blogPost.getTitle());
    }

    либо переработать его с использованием всех возможностей Java 2 v 5:

    List<BlogPost> list = (List<BlogPost>) query.getResultList();

    for (BlogPost blogPost: list) {
    System.out.println(blogPost.getId() + ” – ” + blogPost.getTitle());
    }

  7. Sergey Druzkin

    Спасибо, было занятно посмотреть. Пошел просматривать свою подборку книг по EJB3

  8. Vadim Voituk

    Сергей, самое приятное что JPA можно использовать и без EJB.
    Но лично я начинал с ним разбираться именно пользуясь книгой “EJB3 in Action”

  9. Armen

    full persistence unit under tomcat

    org.hibernate.ejb.HibernatePersistence

  10. Armen

    org.hibernate.ejb.HibernatePersistence

  11. Olesya

    Отличный пример. Просто и наглядно.

  12. anonimous

    Спасибо.

  13. Zavizionov

    Просто и понятно, спасибо. :)

Leave a Reply