Hibernate — библиотека для языка программирования Java, предназначенная для решения задач объектно-реляционного проецирования (object-relational mapping — ORM). Она представляет собой свободное программное обеспечение с открытым исходным кодом (open source), распространяемое на условиях GNU Lesser General Public License. Данная библиотека предоставляет лёгкий в использовании каркас (фреймворк) для отображения объектно-ориентированной модели данных в традиционные реляционные базы данных.
В этой статье будут раскрыты основы использования Java ORM-библиотеки Hibernate 3-й версии на небольшом примере.
В качестве среды разработки использовался NetBeans версии 7.1. Для начала создайте Maven-проект в NetBeans:
File -> New Project... -> Maven -> Java Application
Добавим зависимости в проект. Полный файл pom.xml показан ниже:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.seostella.hibernate</groupId>
<artifactId>hibernate_basics</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>hibernate_basics</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>jboss</id>
<url>http://repository.jboss.org/maven2</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>3.6.9.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-annotations</artifactId>
<version>3.5.6-Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>3.1.0.GA</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-tools</artifactId>
<version>3.2.4.GA</version>
</dependency>
<dependency>
<groupId>javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.18</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Также нам понадобится конфигурационный файл hibernate:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate_basics</property>
<property name="hibernate.connection.username">hb_user</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="connection.password">hb_password</property>
<!-- <property name="show_sql">true</property>-->
<!-- <mapping jar="hibernate-mappings.jar"/> -->
<mapping class="com.seostella.hibernate.basics.entity.Article"/>
<mapping class="com.seostella.hibernate.basics.entity.Category"/>
<mapping class="com.seostella.hibernate.basics.entity.User"/>
</session-factory>
</hibernate-configuration>
В данном примере для доступа к базе данных hibernate_basics используется пользователь hb_user с паролем hb_password. Измените эти параметры на используемые Вами.
Дамп используемой базы представлен ниже:
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
CREATE TABLE IF NOT EXISTS `article` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8 NOT NULL,
`message` varchar(255) CHARACTER SET utf8 NOT NULL,
`user_id` bigint(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `article` (`id`, `title`, `message`, `user_id`) VALUES
(1, '"Законы" экологии Коммонера', 'К. обращает внимание на всеобщуюсвязь процессов и явлений в природе и близок по смыслу к закону внутреннегодинамического равновесия изменение одного из показателей системы вызываетфункционально-структурные количественные и качественные перемены, при этом ', 3),
(2, '"Прикладная" экология', 'Смысл изучения даннойнауки заключается в получении знания, как сохранить наш дом чистым и пригоднымдля обитания в течение долгих лет. Поскольку целью образования являются не знания, а действия Герберт Спенсер , то анализсуществующего экологического положе', 3);
CREATE TABLE IF NOT EXISTS `category` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`title` varchar(255) CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `title` (`title`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
INSERT INTO `category` (`id`, `title`) VALUES
(1, 'Биология');
CREATE TABLE IF NOT EXISTS `category_article` (
`categories_id` bigint(20) NOT NULL,
`articles_id` bigint(20) NOT NULL,
PRIMARY KEY (`categories_id`,`articles_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO `category_article` (`categories_id`, `articles_id`) VALUES
(1, 1),
(1, 2);
CREATE TABLE IF NOT EXISTS `hb_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `hb_user` (`id`, `name`, `password`) VALUES
(3, 'John Connor', '1');
Структура проекта, в том числе, где располагается конфигурационный файл hibernate.cfg.xml показана на Рис.1.
Рис 1. Структура проекта
Приступим к реализации взаимодействия с базой. Начнем с создания базового класса для работы с Hibernate и, соответственно, с базой данных. Создадим в пакете com.seostella.hibernate.basics.dao класс DAO:
package com.seostella.hibernate.basics.dao;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
/**
*
* @author seostella.com
*/
public class DAO {
private static final Logger log = Logger.getAnonymousLogger();
private static final ThreadLocal session = new ThreadLocal();
private static final SessionFactory sessionFactory =
new AnnotationConfiguration().configure().buildSessionFactory();
protected DAO() {
}
public static Session getSession() {
Session session = (Session) DAO.session.get();
if (session == null) {
session = sessionFactory.openSession();
DAO.session.set(session);
}
return session;
}
protected void begin() {
getSession().beginTransaction();
}
protected void commit() {
getSession().getTransaction().commit();
}
protected void rollback() {
try {
getSession().getTransaction().rollback();
} catch (HibernateException e) {
log.log(Level.WARNING, "Cannot rollback", e);
}
try {
getSession().close();
} catch (HibernateException e) {
log.log(Level.WARNING, "Cannot close", e);
}
DAO.session.set(null);
}
public static void close() {
getSession().close();
DAO.session.set(null);
}
}
Этот класс содержит базовые методы для работы с Hibernate: getSession() - получение сессии, begin(), commit(), rollback() - начало, комит и откат транзакции соответственно, close() - закрытие сессии.
Внимание! Объект SessionFactory является "тяжеловесным" - на его создание уходит много времени. Поэтому старайтесь этот объект инициализировать только раз, либо как можно реже.
Приступим к созданию объектов. Начнем с класса для работы с объектом User:
package com.seostella.hibernate.basics.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
/**
*
* @author seostella.com
*/
@Entity
@Table(name="hb_user")
public class User {
private long id;
private String name;
private String password;
public User() {
}
public User(String name, String password) {
this.name = name;
this.password = password;
}
@Column(unique=true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Id
@GeneratedValue
protected long getId() {
return id;
}
protected void setId(long id) {
this.id = id;
}
}
Аннотация @Entity используется для того, чтобы сообщить Hibernate, что класс взаимодействует с Hibernate. Также необходимо сообщить о том, что добавился маппинг-файл, добавив в конфигурационный файл hibernate.cfg.xml строку "mapping class=...". Пример:
<hibernate-configuration>
<session-factory>
...
<mapping class="com.seostella.hibernate.basics.entity.Article"/>
...
</session-factory>
</hibernate-configuration>
Если у Вас есть jar-файл, состоящих из классов, помеченных как @Entity, Вы можете добавить его одной строкой:
<hibernate-configuration>
<session-factory>
...
<mapping jar="hibernate-mappings.jar"/>
...
</session-factory>
</hibernate-configuration>
Рассмотрим остальные аннотации, которые использовались в этом классе, а именно: @Table, @Column, @Id, @GeneratedValue.
@Table - аннотация, используемая для явного указания названия таблицы. Так как база данных содержит таблицу "hb_user", а не просто "user", была использована аннотация @Table(name="hb_user") чтобы явно указать название таблицы.
@Column - использовалась в примере для указания уникальности имени пользователя следующим образом:
@Column(unique=true)
public String getName() {
return name;
}
@Id - аннотация используется для указания Primary-ключа;
@GeneratedValue - сообщает Hibernate, что значение должно генерироваться автоматически при добавлении нового объекта в базу.
Последние две аннотации используются в примере вместе с полем id:
@Id
@GeneratedValue
protected long getId() {
return id;
}
Теперь создаем DAO-класс для работы с объектами User:
package com.seostella.hibernate.basics.dao;
import com.seostella.hibernate.basics.entity.User;
import org.hibernate.HibernateException;
import org.hibernate.Query;
/**
*
* @author seostella.com
*/
public class UserDAO extends DAO {
public User createUser(String username, String password)
throws Exception {
try {
begin();
User user = new User(username, password);
getSession().save(user);
commit();
return user;
} catch (HibernateException e) {
rollback();
throw new Exception("Could not create user " + username, e);
}
}
public User retrieveUser(String username) throws Exception {
try {
begin();
Query q = getSession().createQuery("from User where name = :username");
q.setString("username", username);
User user = (User) q.uniqueResult();
commit();
return user;
} catch (HibernateException e) {
rollback();
throw new Exception("Could not get user " + username, e);
}
}
public void deleteUser( User user ) throws Exception {
try {
begin();
getSession().delete(user);
commit();
} catch (HibernateException e) {
rollback();
throw new Exception("Could not delete user " + user.getName(), e);
}
}
}
Как видно из примера, всё достаточно просто: для сохранения, поиска и удаления пользователя используются следующие соответствующие строки кода:
getSession().save(user); // сохранение
getSession().createQuery("...").uniqueResult(); // поиск
getSession().delete(user); // удаление
Для обеспечения целостности базы и для защиты от ошибок код обрамляется следующим образом:
try {
begin();
// какой-то код для работы с базой данных
commit();
} catch (HibernateException e) {
rollback();
throw new Exception("Could not create user " + username, e);
}
Создадим главный класс UserApp для демонстрации выше приведенного кода:
package com.seostella.hibernate.basics;
import com.seostella.hibernate.basics.dao.UserDAO;
import com.seostella.hibernate.basics.entity.User;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.logging.Logger;
/**
*
* @author seostella.com
*/
public class UserApp {
private static Logger logger = Logger.getLogger(UserApp.class.getName());
public static void main(String[] args) {
String userInput = ""; // Line read from standard in
InputStreamReader converter = new InputStreamReader(System.in);
BufferedReader in = new BufferedReader(converter);
String username = "";
try {
while (!(userInput.equals("0"))) {
System.out.println("1. Создать пользователя");
System.out.println("2. Найти пользователя");
System.out.println("3. Удалить пользователя");
System.out.println("0. Выход");
userInput = in.readLine();
if ("1".equals(userInput)) {
try {
System.out.print(" Введите имя пользователя: ");
username = in.readLine();
UserDAO userDAO = new UserDAO();
User user = userDAO.createUser(username, "1");
System.out.println("Пользователь создан. Имя: "
+ user.getName() + " пароль: " + user.getPassword());
} catch (Exception e) {
System.out.println("Пользователь " + username + " уже существует.");
}
} else if ("2".equals(userInput)) {
try {
System.out.print(" Введите имя пользователя: ");
username = in.readLine();
UserDAO userDAO = new UserDAO();
User user = userDAO.retrieveUser( username );
System.out.println( "Пользователь получен из базы данных. Имя: "
+ user.getName() + " пароль: " + user.getPassword());
} catch (Exception e) {
System.out.println("Пользователь " + username + " не существует.");
}
} else if( "3".equals( userInput ) ){
try {
System.out.print(" Введите имя пользователя: ");
username = in.readLine();
UserDAO userDAO = new UserDAO();
User user = userDAO.retrieveUser( username );
userDAO.deleteUser( user );
System.out.println( "Пользователь " + username + " удален из базы данных.");
} catch (Exception e) {
System.out.println("Пользователь " + username + " не существует.");
}
}
}
} catch (Exception e) {
}
}
}
Вы можете запустить этот класс на выполнение и, используя клавиши "1", "2" и "3", добавить, найти и удалить пользователя соответственно. Пример работы программы:
1. Создать пользователя
2. Найти пользователя
3. Удалить пользователя
0. Выход
1
Введите имя пользователя: Homer
Пользователь создан. Имя: Homer пароль: 1
1. Создать пользователя
2. Найти пользователя
3. Удалить пользователя
0. Выход
2
Введите имя пользователя: Homer
Пользователь получен из базы данных. Имя: Homer пароль: 1
1. Создать пользователя
2. Найти пользователя
3. Удалить пользователя
0. Выход
3
Введите имя пользователя: Homer
Пользователь Homer удален из базы данных.
1. Создать пользователя
2. Найти пользователя
3. Удалить пользователя
0. Выход
2
Введите имя пользователя: Homer
Пользователь Homer не существует.
1. Создать пользователя
2. Найти пользователя
3. Удалить пользователя
0. Выход
0
Аналогичным способом (по отношению к классу User) создаем классы Article и Category:
package com.seostella.hibernate.basics.entity;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
/**
*
* @author seostella.com
*/
@Entity
public class Article {
private long id;
private String title;
private String message;
private User user;
private Set<Category> categories;
public Article(String title, String message, User user) {
this.title = title;
this.message = message;
this.user = user;
this.categories = new HashSet<Category>();
}
public Article() {
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@ManyToOne
@JoinColumn(name = "user_id")
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
@ManyToMany(mappedBy = "articles")
public Set<Category> getCategories() {
return categories;
}
public void setCategories(Set<Category> categories) {
this.categories = categories;
}
@Id
@GeneratedValue
protected long getId() {
return id;
}
protected void setId(long id) {
this.id = id;
}
}
package com.seostella.hibernate.basics.entity;
import java.util.HashSet;
import java.util.Set;
import javax.persistence.*;
/**
*
* @author seostella.com
*/
@Entity
public class Category {
private long id;
private String title;
private Set<Article> articles = new HashSet<Article>();
public Category() {
}
public Category(String title) {
this.title = title;
this.articles = new HashSet<Article>();
}
@ManyToMany
@JoinTable(name="category_article")
public Set<Article> getArticles() {
return articles;
}
void setArticles(Set<Article> adverts) {
this.articles = adverts;
}
public void addArticles(Article advert) {
getArticles().add(advert);
}
@Column(unique=true)
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Id
@GeneratedValue
protected long getId() {
return id;
}
protected void setId(long id) {
this.id = id;
}
}
Рассмотрим новые аннотации @ManyToOne, @JoinColumn, @ManyToMany и @JoinTable:
@ManyToOne - аннотация применяется к полю если таблица связана с другой таблицей типом один-ко-многим;
@JoinColumn - с помощью этой аннотации указывается название поля таблицы, по которому происходит связь один-ко-многим с другой таблицей;
В нашем случае таблицы user и article связаны между собой связью один-ко-многим по полю user_id таблицы article. Поэтому в классе Article поле user_id описано следующим способом:
@ManyToOne
@JoinColumn(name = "user_id")
public User getUser() {
return user;
}
Как видим, Hibernate упрощает жизнь, сразу же возвращая объект User, вместо лишь одного идентификатора пользователя.
В нашем примере также есть связь многие-ко-многим между таблицами article и category через промежуточную таблицу category_article. Чтобы указать эту связь в Hibernate необходимо воспользоваться аннотациями @ManyToMany и @JoinTable. В одном из классов Article или Category необходимо прописать инструкцию вида @ManyToMany(mappedBy = "..."), а в другом:
@ManyToMany
@JoinTable(name="...")
В примере использовались следующие методы для этой связи. Класс Article, метод getCategories():
@ManyToMany(mappedBy = "articles")
public Set<Category> getCategories() {
return categories;
}
И соответствующий код в классе Category:
@ManyToMany
@JoinTable(name="category_article")
public Set<Article> getArticles() {
return articles;
}
Грубо говоря, в одном из классов должна быть привязка к промежуточной таблице category_article, а во втором - привязка к первому классу с помощью выражения mappedBy.
Обратите внимание, что названия методов getArticles() и getCateories() соответствуют названиям полей articles_id и categories_id в таблице category_article. То есть, название поля = название метода без стартового get.
Добавляем эти классы в конфигурационный файл hibernate.cfg.xml:
<hibernate-configuration>
<session-factory>
...
<mapping class="com.seostella.hibernate.basics.entity.Article"/>
<mapping class="com.seostella.hibernate.basics.entity.Category"/>
....
</session-factory>
</hibernate-configuration>
Рассмотрим DAO-классы: ArticleDAO и CategoryDAO. Начнем с CategoryDAO:
package com.seostella.hibernate.basics.dao;
import com.seostella.hibernate.basics.entity.Category;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.Query;
/**
*
* @author seostella.com
*/
public class CategoryDAO extends DAO {
public Category createCategory(String title)
throws Exception {
try {
begin();
Category category = new Category(title);
getSession().save(category);
commit();
return category;
} catch (HibernateException e) {
throw new Exception("Could not create category " + title, e);
}
}
public Category retrieveCategory(String categoryTitle) throws Exception {
try {
begin();
Query categoryQuery = getSession().createQuery(
" from Category where title = :categoryTitle");
categoryQuery.setString("categoryTitle", categoryTitle);
Category category = (Category) categoryQuery.uniqueResult();
return category;
} catch (HibernateException e) {
rollback();
throw new Exception("Could not get category " + categoryTitle, e);
}
}
public List<Category> list() throws Exception{
try {
begin();
List<Category> categories = getSession().createQuery("from Category").list();
return categories;
} catch (HibernateException e) {
rollback();
throw new Exception("Could not get category list", e);
}
}
}
Как видим, класс CategoryDAO существенно отличается от UserDAO лишь одним методом list(), который возвращает список всех категорий в базе данных. Аналогичным способом мы могли бы получить список всех пользователей в базе данных:
List<User> users = getSession().createQuery("from User").list();
Класс ArticleDAO представлен ниже:
package com.seostella.hibernate.basics.dao;
import com.seostella.hibernate.basics.entity.Article;
import com.seostella.hibernate.basics.entity.Category;
import com.seostella.hibernate.basics.entity.User;
import org.hibernate.HibernateException;
/**
*
* @author seostella.com
*/
public class ArticleDAO extends DAO {
public Article createArticle(String username, String categoryTitle,
String title, String message)
throws Exception {
try {
begin();
UserDAO userDAO = new UserDAO();
User user = userDAO.retrieveUser(username);
CategoryDAO categoryDAO = new CategoryDAO();
Category category = categoryDAO.retrieveCategory( categoryTitle );
Article article = new Article(title, message, user);
getSession().save(article);
category.addArticles(article);
getSession().save(category);
commit();
return article;
} catch (HibernateException e) {
throw new Exception("Could not create article " + title, e);
}
}
}
Один существующий метод createArticle() хоть и кажется сложным, на самом деле очень простой. Сначала по имени пользователи находится пользователь, а по имени категории - категория. Используя эти два объекта создается и сохраняется в базу данных новый объект Article. Ключевой момент в этом методе строки:
category.addArticles(article);
getSession().save(category);
Обязательно необходимо добавить в категорию новый объект Article, иначе связки многие-ко-многим не будет.
Покажем на примере вывод всех категорий и связанных с ними статей и пользователей. Создадим класс CategoryApp:
package com.seostella.hibernate.basics;
import com.seostella.hibernate.basics.dao.CategoryDAO;
import com.seostella.hibernate.basics.entity.Article;
import com.seostella.hibernate.basics.entity.Category;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author seostella.com
*/
public class CategoryApp {
private static Logger logger = Logger.getLogger(CategoryApp.class.getName());
public static void main(String[] args) {
try {
List categories = new CategoryDAO().list();
Iterator ci = categories.iterator();
while (ci.hasNext()) {
Category category = (Category) ci.next();
System.out.println("Категория: " + category.getTitle());
Iterator ai = category.getArticles().iterator();
while (ai.hasNext()) {
Article advert = (Article) ai.next();
System.out.println(" Название: " + advert.getTitle());
System.out.println(" Сообщение: " + advert.getMessage());
System.out.println(" Автор: " + advert.getUser().getName());
System.out.println();
}
}
} catch (Exception ex) {
logger.log(Level.SEVERE, null, ex);
}
}
}
При выполнении этой программы выводится следующее:
Категория: Биология
Название: "Прикладная" экология
Сообщение: Смысл изучения даннойнауки заключается в получении знания, как сохранить наш дом чистым и пригоднымдля обитания в течение долгих лет. Поскольку целью образования являются не знания, а действия Герберт Спенсер , то анализсуществующего экологического положе
Автор: John Connor
Название: "Законы" экологии Коммонера
Сообщение: К. обращает внимание на всеобщуюсвязь процессов и явлений в природе и близок по смыслу к закону внутреннегодинамического равновесия изменение одного из показателей системы вызываетфункционально-структурные количественные и качественные перемены, при этом
Автор: John Connor
Для демонстрации создания объекта Article создадим класс ArticleApp:
package com.seostella.hibernate.basics;
import com.seostella.hibernate.basics.dao.ArticleDAO;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author seostella.com
*/
public class ArticleApp
{
private static Logger logger = Logger.getLogger(ArticleApp.class.getName());
public static void main(String[] args) {
try {
ArticleDAO articleDAO = new ArticleDAO();
articleDAO.createArticle( "John Connor", "Test category", "test title", "test message");
} catch (Exception ex) {
logger.log(Level.SEVERE, null, ex);
}
}
}
Не забудьте добавить в базу данных категорию с именем "Test category".
Исходный текст программы можно скачать по следующей ссылке - Основы Hibernate
5 января 2016 г. 11:26
|
Вроде понятный пример для новичка.
Не могу понять куда конкретно добавить в базу данных категорию с именем "Test category". |