Вместо дисклеймера


На Хабре уже есть множество статей на тему работы с Hibernate, однако, как мне показалось, все они довольно сложные для новичков. Эта статья направлена на разъяснение основ работы с ORM и будет полезна в первую очередь тем, кто только начинает разрабатывать собственные приложения и имеет мало опыта работы с базами данных в общем, и с инструментами, вроде Hibernate, в частности. Матерые разработчики вряд ли найдут в статье для себя что-то новенькое; всех остальных прошу под кат.

Что такое ORM?


Ни одно современное веб-приложение не обходится без хранения огромного количества различной информации. Эту задачу обычно возлагают на специальные программы — Систему Управления Базами Данных СУБД. По схеме организации базы данных делятся на несколько видов, и так сложилось, что самым распространенным видом оказались реляционные.

В реляционных базах, данные организованны в виде сущностный (таблиц) и связей между ними. Программисты, работающие с объектно-ориентированными языками программирования, зачастую сталкиваются с задачей преобразования данных из формы, понятной СУБД в привычную объектную форму. Почти всегда решение этих задач занимает огромное количество времени и заставляет писать такие конструкции:

public ArrayList<BookBean> getBooksByGenreId (int genre_id)
    {
        ArrayList<BookBean> result = new ArrayList<>();
        try
        {
            int i = 1;
            String query = "SELECT * FROM books " +
                    "LEFT JOIN genres2books " +
                    "ON genres2books.book_id=books.id " +
                    "WHERE genre_id=? AND books.approved = 1 " +
                    "ORDER BY user_rating DESC LIMIT 250";
            connection = getConnection();
            ps = connection.prepareStatement(query);
            ps.setInt(i++, genre_id);
            resultSet = ps.executeQuery();
            while (resultSet.next())
            {
                String name = resultSet.getString("name");
                String summary = resultSet.getString("summary");
                String cover_url = resultSet.getString("cover_url");
                String cover_min_url = resultSet.getString("cover_min_url");
                String example = resultSet.getString("example");
                String isbn = resultSet.getString("isbn");
                String foreign_id = resultSet.getString("foreign_id");
                double rev_rating = resultSet.getDouble("rev_rating");
                double usr_rating = resultSet.getDouble("user_rating");
                int user_id = resultSet.getInt("user_id");
                int id = resultSet.getInt("id");
                int approved = resultSet.getInt("approved");
                int top = resultSet.getInt("top");
                int partner_id = resultSet.getInt("partner_id");
                long sum_mark = resultSet.getLong("sum_mark");
                long votes_num = resultSet.getLong("votes_num");
                result.add( new BookBean(id, name, summary, cover_url, cover_min_url, example, rev_rating, usr_rating,
                        user_id, top, approved, sum_mark, votes_num, isbn, foreign_id, partner_id) );
            }
        } catch (SQLException | IllegalArgumentException e) {
            e.printStackTrace();
        } finally {
            try {
                if (ps!=null)
                    ps.close();
                if (connection!=null)
                    connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }


И это только один SELECT, а ведь нужно еще и организовать правильное подключение к СУБД и обеспечить нормальную одновременную работу нескольких пользователей.

Облегчить жизнь программистам и освободить нас от рутины призвана технология Object-Relational Mapping (ORM), которую реализует популярная библиотека Hibernate. Hibernate берет на себя задачу преобразования данных их реляционного вида в объектный, для чтения, и из объектного вида в реляционный — для записи. Кроме того, библиотека позволяет легко настроить подключение к СУБД и с помощью нее очень легко управлять транзакциями.

Быстрый старт


Сейчас мы попробуем с помощью с помощью Hibernate создать отображение в объектную форму вот такой таблицы, которая используется для хранения сессий:

image

Создадим Bean SessionBean. Бин — это класс, у которого есть констурктор без параметров, конструктор со всеми параметрами и определены get- и set- методы для всех полей.

@Entity //аннотация, регистрирующая класс как сущность БД
@Table(name = "sessions") //связываем с конкретной таблицей по названию
public class SessionBean implements Serializable{

    @Column(name = "username") //название таблицы в БД
    private String username; //название поля класса
    @Id //указывает на то, что следующее поле является ID и будет использоваться для поиска по умолчанию
    @Column(name = "series")
    private String series;
    @Column(name = "token")
    private String token;
    @Column(name = "last_used")
    private Timestamp lastUsed;

    public SessionBean() {}

    public SessionBean(String username, String series, String token, Timestamp lastUsed) {
        this.username = username;
        this.series = series;
        this.token = token;
        this.lastUsed = lastUsed;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getSeries() {
        return series;
    }

    public void setSeries(String series) {
        this.series = series;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public Timestamp getLastUsed() {
        return lastUsed;
    }

    public void setLastUsed(Timestamp last_used) {
        this.lastUsed = last_used;
    }
}


Также создадим DAO (Data Access Object) — специальный класс, который будет обеспечивать для нас операции чтения и записи в базу данных

public class NEXEntityDAO<Bean>
{
    protected final Class<Bean> typeParameterClass;


	public NEXEntityDAO(Class<Bean> typeParameterClass)
	{
        this.typeParameterClass = typeParameterClass;
    }

    @Override
    public void delete(int id) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        session.beginTransaction();
        Bean del = (Bean) session.get(typeParameterClass, id);
        session.delete(del);
        session.getTransaction().commit();
        if (session.isOpen()) {
            session.close();
        }
    }

    @Override
    public ArrayList<Bean> getAll() {
        Session session = HibernateUtil.getSessionFactory().openSession();
        session.beginTransaction();
        String hql = String.format("from %s",typeParameterClass.getCanonicalName());
        Query SQLQuery = session.createQuery(hql);
        ArrayList<Bean> result = (ArrayList<Bean>) SQLQuery.list();
        session.getTransaction().commit();
        if (session.isOpen()) {
            session.close();
        }
        return result;
    }

    public Bean getById(int id) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        session.beginTransaction();
        Bean result = (Bean) session.get(typeParameterClass, id);
        session.getTransaction().commit();
        if (session.isOpen()) {
            session.close();
        }
        return result;
    }

    @Override
    public void update(Bean object) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        session.beginTransaction();
        session.update(object);
        session.getTransaction().commit();
        if (session.isOpen()) {
            session.close();
        }
    }

    @Override
    public void add(Bean object) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        session.beginTransaction();
        session.save(object);
        session.getTransaction().commit();
        if (session.isOpen()) {
            session.close();
        }
    }

    @Deprecated
    public void clear()
    {
        Session session = HibernateUtil.getSessionFactory().openSession();
        session.beginTransaction();
        String hql = String.format("delete from %s",typeParameterClass.getCanonicalName());
        Query query = session.createQuery(hql);
        query.executeUpdate();
        session.getTransaction().commit();
        if (session.isOpen()) {
            session.close();
        }
    }
}


После чего становится очень просто извлекать, редактировать и добавлять данные в БД.

 NEXEntityDAO<SessionBean> sessionDAO = new NEXEntityDAO<>(SessionBean.class);
 SessionBean session = sessionDAO.getById(5) //Получить сессию с ид = 5
 ArrayList<SessionBean> allSessions = sessionDAO.getAll(); //получить список всех сессий
 session.setToken(“21313”);
 sessionDAO.update(session); //обновить существующую запись
 SessionBean adding = new SessionBean(“st”,”ri”,”ng”,ts);
 sessionDAO.add(adding); //добавить новую запись


На этом закончим знакомство с ORM. В следующих статьях мы рассмотрим наиболее интересные тонкости работы с этой технологией, а также приёмы тестирования баз данных.

Комментарии (9)


  1. bromzh
    15.04.2015 14:52

    В сети таких туториалов огромная куча. Ещё там обычно тестовый проект прилагается, чтобы новичок (на которого рассчитана такая статья) мог пощупать проект. Не описано, как подключить БД к приложению: можно ведь через xml-конфигурацию (которых тоже несколько видов — persistence.xml, orm.xml), а можно и в классе всё настроить. А ещё можно не использовать конкретную реализацию JPA-провайдера, а использовать ту, что предлагает сервер приложений и соединение с БД настроить на нём.
    Нет листинга HibernateUtil, который вроде как в коде присутствует.

    Рассказал бы лучше про Lazy Loading и как с ней бороться, каскадности там всякие, преимущества Hibernate перед просто спецификацией JPA.

    На правах рекламы — моё демо-приложение, использующее JavaEE, JPA и JAX-RS (и WildFly). И небольшое описание toster.ru/q/207938#answer_563870


    1. x22a Автор
      15.04.2015 15:05

      Спасибо за отзыв. думаю, в последующих статьях я начну выкладывать примеры для новичков. HibernateUtil нет, потому что в данной статье мы не делаем ничего особенно сложного с ним. выложу, когда начнем говорить про тестирование. Про подключение я действительно забыл. Дополню позднее.
      Про Lazy Loading будет через одну статью. Уже начал писать)


      1. bromzh
        15.04.2015 15:27
        +1

        Ну просто новичок не сможет по такой статье создать приложение и потестить его, хотя бы потому, что половины нужного кода нет.

        Кстати, есть целая куча статей по хибернейту, правда на английском. Хотя примеры там крайне простые и понятные. И привязки к Spring нет (хотя тут её тоже нет), так что их можно использовать и в «SE»-приложениях тоже.


    1. bromzh
      15.04.2015 15:13

      Вдогонку: обычно то, что аннотированно Entity зовётся сущностью, а не SessionBean'ом, и его название схоже с именем таблицы (в данном случае, просто Session). Session-бином обычно обзывают класс, содержащий бизнес-логику. А чтобы быть бином, классу достаточно просто иметь конструктор по-умолчанию, откуда взялись ещё 2 условия, непонятно.
      Для DAO обычно создаётся абстрактный generic-класс, в котором реализована большая часть кода (потому что для разных сущностей он фактически одинаковый). Судя по отсутствию в классе NEXEntityDAO слова extend, и наличию аннотаций Override, смею предположить, что код вставился криво и этот класс где-то есть, но не тут.
      Ну и использовать нетипизированные запросы — плохо. Есть же TypedQuery


      1. grossws
        16.04.2015 02:46

        Извините, промазал с комментарием. См. habrahabr.ru/post/255829/#comment_8377963


  1. grossws
    15.04.2015 19:03

    А чтобы быть бином, классу достаточно просто иметь конструктор по-умолчанию, откуда взялись ещё 2 условия, непонятно.
    Условие про конструктор со всеми параметрами, очевидно, бредовое. Обычно под бинами в яве понимают то, что описано спеке JavaBeans (http://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html).

    Минимально для этого нужно следующее:
    — no-arg public constructor
    — implements Serializable
    — отсутствуют публичные поля, свойства доступные через геттеры и сеттеры.


  1. poxu
    15.04.2015 20:33

    А скажите пожалуйста где в статье про Spring?


  1. norguhtar
    16.04.2015 10:06

    Ну зачем? В Spring есть SpringData по этой причине закат солнца в ручную с утомительным написанием своих DAO уже не требуется.


  1. elw00d
    16.04.2015 18:41

    В последнее время для персистенси предпочитаю использовать MyBatis 3 на аннотациях. Прикрутил к нему плагин для FreeMarker, и всё вообще идеально. Никаких сложных statefull фреймворков, всё на кончиках пальцев, и не многословно )