Доброго всем времени суток! При написании программы, которая так или иначе будет взаимодействовать с базой данных, пользуются разными средствами. Это и старый добрый jdbc, также применяют: EclipseLink,TopLink, iBatis (уже MyBatis), Spring Framework и конечно же герой нашей статьи — Hibernate. Конечно я здесь перечислил не все средства работы с базой данных, но постарался указать самые распространенные. В данной статье будет показано, как при помощи Hibernate вызывать хранимые процедуры, маппить как таблицы, так и запросы к классам. В качестве подопытной базы данных возьмем Oracle.
Подготовим все необходимое для экспериментов. Начнем с базы данных. Для начала создадим 3 таблички, над которыми мы и будем практиковаться.
Теперь создадим функцию. которая будет нам возвращать название книги по ее id, пример глупый, но зато будет показывать принцип вызова функции с входным числовым параметром и выходным — строковым.
И также в качестве эксперимента создадим простую процедуру сохранения в БД, у которой будут как входные параметры, так и выходной.
Теперь перейдем к классам на Java.
Отобразим наши 3 таблицы базы данных в классах следующим образом:
Немного пояснений по коду:
Маппинг следующих 2 таблиц БД будет выглядеть так:
Ограничения для полей можно задавать прямо в аннотации, это делается следующей строкой @Column(name=«NAME»,unique = true, nullable = false, length = 100). Аннотациями @ManyToOne и @JoinTable(name = «CATALOG») мы говорим, что таблица Book и таблица Student имеют связь «многие-ко-многим» через таблицу Catalog, следовательно и все изменения в Book и в Student, автоматом будут применяться и в таблице Catalog.
Ну вот, мы закончили с маппингом таблиц, и теперь перейдем к непосредственной работе с базой данных.
Извлечение данных с одновременным заполнением коллекции можно производить разными способами:
Перейдем к работе с хранимыми процедурами. Для того, чтобы вызвать функцию, которая по id вернет нам название книги, выполним следующие действия:
Ну, а чтобы вызвать нашу процедуру save_book проделаем следующие манипуляции:
Вроде бы все. Конечно я не все смог осветить, но думаю для быстрого старта этого хватит.
Подготовим все необходимое для экспериментов. Начнем с базы данных. Для начала создадим 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.
Ну вот, мы закончили с маппингом таблиц, и теперь перейдем к непосредственной работе с базой данных.
Извлечение данных с одновременным заполнением коллекции можно производить разными способами:
- При помощи HQL (Hibernate Query Language) запросов
List<Book> book = (List<Book>)session.createQuery("from Book order by name").list();
- при помощи 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();
- при помощи 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();
Вроде бы все. Конечно я не все смог осветить, но думаю для быстрого старта этого хватит.