Pull to refresh

Hibernate для самых маленьких и не только

Reading time 6 min
Views 203K
Доброго всем времени суток! При написании программы, которая так или иначе будет взаимодействовать с базой данных, пользуются разными средствами. Это и старый добрый jdbc, также применяют: EclipseLink,TopLink, iBatis (уже MyBatis), Spring Framework и конечно же герой нашей статьи — Hibernate. Конечно я здесь перечислил не все средства работы с базой данных, но постарался указать самые распространенные. В данной статье будет показано, как при помощи Hibernate вызывать хранимые процедуры, маппить как таблицы, так и запросы к классам. В качестве подопытной базы данных возьмем Oracle.
Подготовим все необходимое для экспериментов. Начнем с базы данных. Для начала создадим 3 таблички, над которыми мы и будем практиковаться.
CREATE TABLE book
(
    id             NUMBER NOT NULL,
    name           VARCHAR2 (100 BYTE) NOT NULL,
    description    VARCHAR2 (1000 BYTE) NOT NULL,
    CONSTRAINT pk$book_id PRIMARY KEY (id)
)
CREATE TABLE student
(
    id         NUMBER NOT NULL,
    name       VARCHAR2 (100 BYTE) NOT NULL,
    CONSTRAINT pk$student_id PRIMARY KEY (id)
)
CREATE TABLE catalog
(
    id_student   NUMBER NOT NULL,
    id_book      NUMBER NOT NULL
)

Теперь создадим функцию. которая будет нам возвращать название книги по ее id, пример глупый, но зато будет показывать принцип вызова функции с входным числовым параметром и выходным — строковым.
CREATE OR REPLACE
FUNCTION get_book_name_by_id (p_id IN NUMBER)
    RETURN VARCHAR2
IS
    v_name   VARCHAR2 (100);
BEGIN
    SELECT name
      INTO v_name
      FROM book
     WHERE id = p_id;
    RETURN v_name;
EXCEPTION
    WHEN NO_DATA_FOUND
    THEN
        RETURN 'ну нет такой книги!!';
END;

И также в качестве эксперимента создадим простую процедуру сохранения в БД, у которой будут как входные параметры, так и выходной.
CREATE OR REPLACE 
PROCEDURE save_book (p_id      IN OUT NUMBER,
                         p_name    IN     VARCHAR2,
                         p_descr   IN     VARCHAR2)
IS
BEGIN
    IF p_id > 0
    THEN
        UPDATE book
           SET name = p_name, description = p_descr
         WHERE id = p_id;
    ELSE
        SELECT catalog_seq.NEXTVAL INTO p_id FROM DUAL;
        INSERT INTO book
        VALUES (p_id, p_name, p_descr);
    END IF;
END;

Теперь перейдем к классам на Java.
Отобразим наши 3 таблицы базы данных в классах следующим образом:
@Entity
@Table
public class Student implements Serializable {
	private static final long serialVersionUID = -5170875020617735653L;
	@Id  
	@GeneratedValue(strategy = GenerationType.AUTO, generator = "my_entity_seq_gen")
	@SequenceGenerator(name = "my_entity_seq_gen", sequenceName = "catalog_seq")
	private long id;
	@Column
	private String name;
	@OneToMany(mappedBy = "student", fetch = FetchType.LAZY)
	private Set<Book> bookList;
		// здесь идет реализация методов  getter, setter, hashCode(), equals(), toString()
}

Немного пояснений по коду:
  • если у вас название класса не совпадает с названием таблицы, то например пишем так: Table(name = «STUDENT»);
  • одно из требований фреймворка — у каждой таблицы должен быть id, также если название поля id таблицы и название нашей переменной не совпадают, тогда аннотация будет выглядить так: Id @Column(name=«id таблицы в базе»);
  • аннотациями @GeneratedValue и @SequenceGenerator мы определяем стратегию генерации уникального идентификатора, в данном случае мы говорим, что при сохранении информации в базу данных, мы берем число из sequence с названием «catalog_seq»;
  • так как у нас один студент может иметь несколько книг, то отобразим это при помощи аннотации таким образом @OneToMany(mappedBy = «student», fetch = FetchType.LAZY), где mappedBy = «student» — это имя поля в классе Book(смотри ниже), а FetchType.LAZY — говорит нам о том, что коллекцию
    Set<Book> bookList
    мы будем загружать данными только по требованию.

Маппинг следующих 2 таблиц БД будет выглядеть так:
@Entity
@Table
public class Book implements Serializable {
	private static final long serialVersionUID = 1L;
	@Id
	@Column(name="ID")
	@GeneratedValue(strategy = GenerationType.AUTO, generator = "my_entity_seq_gen")
	@SequenceGenerator(name = "my_entity_seq_gen", sequenceName = "catalog_seq")
	private long id;
	@Column(name="NAME",unique = true, nullable = false, length = 100)
	private String name;
	@Column(name="DESCRIPTION",unique = true, nullable = false, length = 100)
	private String description;
	@ManyToOne(fetch = FetchType.LAZY,optional=true)
	@JoinTable(name = "CATALOG", joinColumns = @JoinColumn(name = "ID_BOOK"), inverseJoinColumns = @JoinColumn(name = "ID_STUDENT"))
	private Student student;
	// здесь идет реализация методов  getter, setter, hashCode(), equals(), toString() 
}

Ограничения для полей можно задавать прямо в аннотации, это делается следующей строкой @Column(name=«NAME»,unique = true, nullable = false, length = 100). Аннотациями @ManyToOne и @JoinTable(name = «CATALOG») мы говорим, что таблица Book и таблица Student имеют связь «многие-ко-многим» через таблицу Catalog, следовательно и все изменения в Book и в Student, автоматом будут применяться и в таблице Catalog.
Ну вот, мы закончили с маппингом таблиц, и теперь перейдем к непосредственной работе с базой данных.
Извлечение данных с одновременным заполнением коллекции можно производить разными способами:
  1. При помощи HQL (Hibernate Query Language) запросов
    	List<Book> book = (List<Book>)session.createQuery("from Book order by name").list();
    	
  2. при помощи SQL-запросов
    	List<Book> book = (List<Book>)session.createSQLQuery("select ID, DESCRIPTION, NAME from book order by NAME")
    	.addScalar("id",Hibernate.LONG).addScalar("name").addScalar("description")
    	.setResultTransformer(Transformers.aliasToBean(Book.class)).list();
    	
  3. при помощи Criteria
    	List<Book> book=(List<Book>)session.createCriteria(Book.class).createAlias("student", "st").add(Restrictions.eq("st.name", "Maxim")).list();
    	

Перейдем к работе с хранимыми процедурами. Для того, чтобы вызвать функцию, которая по id вернет нам название книги, выполним следующие действия:
String bookName = (String)session.createSQLQuery("{? = call get_book_name_by_id (:id)}").setLong("id",1).uniqueResult();
Допустим у нас на сервере нет таблицы с названием Student, а есть только функция, которая возвращает курсор:
FUNCTION get_all_students
    RETURN SYS_REFCURSOR
IS
    l_cur   SYS_REFCURSOR;
BEGIN
    OPEN l_cur FOR
        SELECT *
          FROM student
       ORDER BY 1;
    RETURN l_cur;
END;
тогда маппинг будем проходить следующим образом, вместо аннотации @Table(name="STUDENT") напишем
@NamedNativeQuery(name="getAllStudent",query="{? = call get_all_students}", callable=true, resultClass=Student.class)
А вызов этой функции будет следующим:
 List<Student> student = (List<Student>) session.getNamedQuery("entity").list();

Ну, а чтобы вызвать нашу процедуру save_book проделаем следующие манипуляции:
CallableStatement st = session.connection().prepareCall("{call save_book(?,?,?)}");
			st.setLong(1,0);
			st.setString(2, "Золотой ключик, или Приключения Буратино");
			st.setString(3,"повесть-сказка Алексея Николаевича Толстого");
			st.registerOutParameter(1, java.sql.Types.NUMERIC);
			st.execute();
System.out.println(st.getLong(1));
Как Вы наверно успели заметить, при написании команд для обращения к базе данных и заполнением коллекции, использовалось слово session. В нашем случае это слово указывает на главный интерфейс между нашим Java-приложением и фреймворком Hibernate, то есть org.hibernate.Session session. Но для начала нам необходимо воспользоваться еще одним основополагающим и важным интерфейсом — SessionFactory. SessionFactory — это глобальная фабрика, ответственная за конкретную базу данных. Чтобы получить эту фабрику нам необходимо получить экземпляр класса org.hibernate.cfg.Configuration. Делается это так:
 SessionFactory sessions = new Configuration().configure().buildSessionFactory();
Где Configuration().configure().buildSessionFactory() парсит файлик с названием hibernate.cfg.xml, который находится рядом с вызываемой программой, конечно если не указан путь. Приведем файл конфигурации, в котором показана настройка соединения к базе данных и отображение наших таблиц:
 <?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
         <property name="hibernate.connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
        <property name="hibernate.connection.url">jdbc:oracle:thin:@localhost:port:baseName</property>
        <property name="hibernate.connection.username">username</property>
        <property name="hibernate.connection.password">password</property>
        <property name="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</property>
        <property name="show_sql">true</property>
        
        <mapping class="com.sample.javaHibernate.data.Book" />
        <mapping class="com.sample.javaHibernate.data.Student" />        
    </session-factory>
</hibernate-configuration>
И теперь, когда мы имеем фабрику, в которой есть вся необходимая конфигурация (коннект к базе через пул, маппинг/отображение таблиц и др.) мы можем работать с Session
Session session = sessions.openSession();
Работать с транзакциями можно так: session.beginTransaction(); и соответственно session.getTransaction().commit();
Вроде бы все. Конечно я не все смог осветить, но думаю для быстрого старта этого хватит.
Tags:
Hubs:
+37
Comments 26
Comments Comments 26

Articles