Pull to refresh

Некоторая практика применения Hibernate

Reading time 16 min
Views 15K
Поскольку в процессе продолжения обучения решил разобраться с Hibernate для организации работы с базами данных посредством максимального ухода от использования SQL-запросов, то было решено сформировать некий абстрактный пример. Он должен быть с одной стороны простым, а с другой дать возможность проверить работоспособность всех типовых операций, которые при обращении к БД возникают.

Ниже представляю на суд свое творение. Изначально я опирался на следующий материал. Как по мне он довольно интересен, но всегда интересно сформировать полное приложение, пусть и простое. Итак, что же получилось…

Исходная база осталась неизменной, как в указанной выше методике. image

Перечень операций, которые необходимо будет реализовать, будет выглядеть так:
  • Добавить новый отдел;
  • Править данные отдела;
  • Удалить отдел;
  • Вывести данные отделов;
  • Найти отдел по коду;
  • Добавить нового сотрудника;
  • Просмотр сотрудников по отделу;
  • Удалить сотрудника.


Первый шаг — было формирование маппингов к базе. Из того материала, что рассмотрел сам, понял что есть два пути: использования xml — файла или использование аннотаций. Для упрощения восприятия, отдельный xml- файл для моего примера для отдела сотрудников выглядит так:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>

    <class name="logic.Department" table="department" schema="joblist">
        <id name="departmentId">
            <column name="department_id" sql-type="int(11)"/>
            <generator class="increment"/>
        </id>
        <property name="caption">
            <column name="caption" sql-type="text" not-null="true"/>
        </property>

        <set name="employies" lazy="false" cascade="all" inverse="true">
            <key column="fk_department_id"/>
            <one-to-many class="logic.Employee"/>
        </set>
    </class>
</hibernate-mapping>


Сразу первая оговорка, в файле должен присутствовать заголовок… <?xml version='1.0' encoding='utf-8'?> и т.д.

Пытался реализовать это же самое через аннотации, которые выглядят так:
@Entity
@Table(name="department")
public class Departmant {

    private long id;
    private String name;

    public Department() {
    }


Строки, начинающиеся с @ — и есть аннотации. Что именно использовать, решать авторам самостоятельно. Обе версии имеют право на жизнь.

Второй маппинг для сотрудников выглядит так:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>

    <class name="logic.Employee" table="employee" schema="joblist">
        <id name="employeeId">
            <column name="employee_id" sql-type="int(11)"/>
            <generator class="increment"/>
        </id>
        <property name="fio">
            <column name="fio" sql-type="text" not-null="true"/>
        </property>

        <many-to-one name="department" class="logic.Department" column="fk_department_id" not-null="true" cascade="save-update"/>
    </class>
</hibernate-mapping>


Сразу внесу замечания по формированию связей. Поскольку используется связь 1-М (один ко многим), то со стороны базовой сущности (отдел) указываем:
<set name="employies" lazy="false" cascade="all" inverse="true">

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

Со стороны подчиненной сущности укажем:
 <many-to-one name="department" class="logic.Department" column="fk_department_id" not-null="true"
                     cascade="save-update"/>

То есть, ключевое поле (внешний ключ) не может быть нулевым (обеспечение целостности на уровне БД) и выполнение каскадных операций допустимо для сохранения или обновления (иначе при удалении сотрудника слетит и его родной отдел).

Классы «Отдел» и «Сотрудники» — это типичные POJO, как показано во многих примерах.
package logic;

import java.util.HashSet;
import java.util.Set;

public class Department {
    private int departmentId;
    private String caption;


    public Department() {
    }

    public Department(String caption) {
        this.caption = caption;
    }

    public int getDepartmentId() {
        return departmentId;
    }

    public void setDepartmentId(int departmentId) {
        this.departmentId = departmentId;
    }

    public String getCaption() {
        return caption;
    }

    public void setCaption(String caption) {
        this.caption = caption;
    }



    Set<Employee> employies = new HashSet<Employee>();

    public Set<Employee> getEmployies(){
        return employies;
    }

    public void setEmployies(Set<Employee> employies) {
        this.employies = employies;
    }

    @Override
    public String toString() {
        String string = this.departmentId + " " + this.caption;
        return string;
    }
}


package logic;

public class Employee {
    private int employeeId;
    private String fio;
    private Department department;

    public Employee() {
    }

    public Employee(String fio) {
        this.fio = fio;
    }


    public int getEmployeeId() {
        return employeeId;
    }

    public void setEmployeeId(int employeeId) {
        this.employeeId = employeeId;
    }


    public String getFio() {
        return fio;
    }

    public void setFio(String fio) {
        this.fio = fio;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department){
        this.department = department;
    }

    @Override
    public String toString() {
        String string = this.employeeId+ " " + this.fio;
        return string;
    }
}


Продолжаем далее… Необходимо сформировать класс — фабрику, которая будет выполнять однократный запуск конфигурации и списка методов обработки (интерфейсов). Это реализовано в виде синглетонов.

package factory;

import DAO.DepartmentDAO;
import DAO.EmployeeDAO;
import implement.ImplDepartment;
import implement.ImplEmployee;

public class Factory {

    private static DepartmentDAO departmentDAO = null;
    private static EmployeeDAO employeeDAO = null;
    private static Factory instance = null;

    public static synchronized Factory getInstance() {
        if (instance == null) {
            instance = new Factory();
        }
        return instance;
    }

    public DepartmentDAO getDepartmentDAO() {
        if (departmentDAO == null) {
            departmentDAO = new ImplDepartment();
        }
        return departmentDAO;
    }

    public EmployeeDAO getEmployeeDAO() {
        if (employeeDAO == null) {
            employeeDAO = new ImplEmployee();
        }
        return employeeDAO;
    }
}


Конфигурирования hibernate — файл hibernate.cfg.xml имеет структуру:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
    <property name="connection.url">jdbc:mysql://localhost:3306/Joblist</property>
    <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
    <property name="connection.username">root</property>
    <property name="connection.password">admin</property>
    <property name="connection.pool_size">1</property>
    <property name="show_sql">true</property>
    <property name="current_session_context_class">thread</property>
    <property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>

    <mapping resource="logic/Employee.hbm.xml"/>
    <mapping resource="logic/Department.hbm.xml"/>
    <mapping class="logic.Department"/>
    <mapping class="logic.Employee"/>


  </session-factory>
</hibernate-configuration>


Следующий шаг — создание класса для формирования сессии работы. Обычный вариант, рассматриваемый во множестве примеров, хотя и работает, но имеет статус «Deprecated» — не рекомендовано к применению. В результате, утилита для организации соединения имеет несколько иной вид:

package utils;

import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;


public class HibernateUtil {

    private static SessionFactory sessionFactory;
    private static ServiceRegistry serviceRegistry;

    public static final SessionFactory createSessionFactory() {
        Configuration configuration = new Configuration();
        configuration.configure();
        serviceRegistry = new ServiceRegistryBuilder().applySettings(
                configuration.getProperties()).buildServiceRegistry();
        sessionFactory = configuration.buildSessionFactory(serviceRegistry);
        return sessionFactory;
    }
}


Классы — интерфейсы для организации операций с таблицами, выглядят так:
package DAO;

import logic.Department;
import logic.Employee;

import java.io.IOException;
import java.sql.SQLException;
import java.util.Collection;

public interface DepartmentDAO {

    public void addDepartment(Department department) throws SQLException;

    public void editDepartment(Department department) throws SQLException;

    public void deleteDepartment(Integer department) throws SQLException, IOException;

    public Collection getDepartmentById(Integer department_id) throws SQLException;

    public Collection getAllDepartments() throws SQLException;

    public void addEmployee(Department department, Employee employee) throws SQLException;

}


Во втором случае, оговорюсь сразу, методы реализовал не все…

package DAO;

import logic.Employee;

import java.sql.SQLException;
import java.util.Collection;

public interface EmployeeDAO {

    public void editEmployee (Employee employee) throws SQLException;

    public void deleteEmployee (Employee employee) throws SQLException;

    public Collection getDepartmentByEmployee (Employee employee) throws SQLException;

    public Collection getAllEmployies() throws SQLException;

    public void deleteEmployiesByName(String name) throws SQLException;

    public Collection getEmployiesByDepartment(Integer department) throws SQLException;
}


Далее, раз есть методы, то их необходимо реализовать:

package implement;


import DAO.DepartmentDAO;
import logic.Department;
import logic.Employee;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import utils.HibernateUtil;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.util.*;

public class ImplDepartment implements DepartmentDAO {
    @Override
    public void addDepartment(Department department) throws SQLException {
        Session session = null;
        try {
            session = HibernateUtil.createSessionFactory().openSession();
            session.beginTransaction();
            session.save(department);
            session.getTransaction().commit();
        } catch (HibernateException exception) {
            System.out.println("Ошибка добавления отдела");
            exception.printStackTrace();
        } finally {
            if (session != null && session.isOpen()) {
                session.close();
            }
        }
    }

    @Override
    public void editDepartment(Department department) throws SQLException {
        Session session = null;
        try {
            session = HibernateUtil.createSessionFactory().openSession();
            session.beginTransaction();
            session.update(department);
            session.getTransaction().commit();
        } catch (HibernateException exception) {
            System.out.println("Ошибка правки отдела");
            exception.printStackTrace();
        } finally {
            if (session != null && session.isOpen()) {
                session.close();
            }
        }
    }

    @Override
    public void deleteDepartment(Integer id) throws SQLException, IOException {
        Session session = null;
        try {
            session = HibernateUtil.createSessionFactory().openSession();
            session.beginTransaction();
            Department department = (Department) session.load(Department.class, id);
            Employee[] employies = department.getEmployies().toArray(new Employee[]{});
            if (employies.length > 0) {
                System.out.println("В отделе есть сотрудники, удалить?");
                BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
                String ch = reader.readLine();
                if (ch.equals("Y") || (ch.equals("y"))) {
                    session.delete(department);
                    session.getTransaction().commit();
                }
            } else {
                session.delete(department);
                session.getTransaction().commit();
            }
        }
         catch (HibernateException exception) {
            System.out.println("Ошибка удаления отдела");
            exception.printStackTrace();
        } finally {
            if (session != null && session.isOpen()) {
                session.close();
            }
        }
    }

    @Override
    public Collection getDepartmentById(Integer department_id) throws SQLException {
        Session session = null;
        List departments = new ArrayList<Department>();
        try {

            session = HibernateUtil.createSessionFactory().openSession();
            departments = session.createCriteria(Department.class).add(Restrictions.idEq(department_id)).list();
        } catch (HibernateException exception) {
            System.out.println("Ошибка отбора отдела");
            exception.printStackTrace();
        } finally {
            if (session != null && session.isOpen()) {
                session.close();
            }
        }
        return departments;
    }

    @Override
    public Collection getAllDepartments() throws SQLException {
        Session session = null;
        List departments = new ArrayList<Department>();
        try {
            session = HibernateUtil.createSessionFactory().openSession();
            departments = session.createCriteria(Department.class).list();
        } catch (Exception e) {
            System.out.println("Ошибка чтения списка отделов");
        } finally {
            if (session != null && session.isOpen()) {
                session.close();
            }
        }
        return departments;
    }

    @Override
    public void addEmployee(Department department, Employee employee) throws SQLException {
        Session session = null;
        try {
            session = HibernateUtil.createSessionFactory().openSession();
            session.beginTransaction();
            department.getEmployies().add(employee);
            employee.setDepartment(department);
            session.saveOrUpdate(department);
            session.saveOrUpdate(employee);
            session.getTransaction().commit();
        } catch (HibernateException exception) {
            exception.printStackTrace();
            System.out.println("Ошибка добавления сотрудника");
        } finally {
            if (session != null && session.isOpen()) {
                session.close();
            }
        }
    }
}



package implement;


import DAO.EmployeeDAO;
import logic.Department;
import logic.Employee;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.Restrictions;
import utils.HibernateUtil;
import java.sql.SQLException;
import java.util.*;

public class ImplEmployee implements EmployeeDAO {


    @Override
    public void editEmployee(Employee employee) throws SQLException {
        Session session = null;
        try {
            session = HibernateUtil.createSessionFactory().openSession();
            session.beginTransaction();
            session.update(employee);
            session.getTransaction().commit();
        } catch (HibernateException exception) {
            System.out.println("Ошибка правки сотрудника");
            exception.printStackTrace();
        } finally {
            if (session != null && session.isOpen()) {
                session.close();
            }
        }
    }

    @Override
    public void deleteEmployee(Employee employee) throws SQLException {

    }

    @Override
    public Collection getDepartmentByEmployee(Employee employee) throws SQLException {
        return null;
    }

    @Override
    public Collection getAllEmployies() throws SQLException {
        return null;
    }


    @Override
    public void deleteEmployiesByName(String name) throws SQLException {
        Session session = null;
        List employies = new ArrayList<Employee>();
        try {
            session = HibernateUtil.createSessionFactory().openSession();
            session.beginTransaction();
            employies = session.createCriteria(Employee.class).add(Restrictions.like("fio", name)).list();
            Iterator iterator = employies.iterator();
            while (iterator.hasNext()) {
                Employee data = (Employee) iterator.next();
                String nameTemp = data.getFio();
                if (nameTemp.equals(name)){
                    int id = data.getEmployeeId();
                    Object person =  session.load(Employee.class, id);
                    session.delete(person);
                }
            }
            session.getTransaction().commit();
        } catch (HibernateException exception) {
            System.out.println("Ошибка удаления сотрудника");
            exception.printStackTrace();
        } finally {
            if (session != null && session.isOpen()) {
                session.close();
            }
        }
    }

    @Override
    public Collection getEmployiesByDepartment(Integer department) throws SQLException {
        Session session = null;
        List departments = new ArrayList<Department>();
        Collection employies = null;
        try {
            session = HibernateUtil.createSessionFactory().openSession();
            departments = session.createCriteria(Department.class).add(Restrictions.idEq(department)).list();
            Iterator iterator = departments.iterator();
            while (iterator.hasNext()) {
                Department data = (Department) iterator.next();
                employies = data.getEmployies();
            }
        } catch (Exception e) {
            System.out.println("Ошибка чтения списка сотрудников");
        } finally {
            if (session != null && session.isOpen()) {
                session.close();
            }
        }
        return employies;
    }
}


Пустышки с результатом Null и есть пока не реализованные методы. Но они не испрашиваются из основного класса, поэтому проблем с этим нет, доделать можно в любое время. Да и в принципе, состав методов может меняться по обстановке.

Ну и наконец — основной модуль, чтобы проверить всю правильность реализованных идей.
package main;

import factory.Factory;
import logic.Department;
import logic.Employee;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class Main {

    public static void addDepartment() throws IOException, SQLException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("Введите наименование отдела: ");
        String data = reader.readLine();
        Department department = new Department();
        department.setCaption(data);
        Factory.getInstance().getDepartmentDAO().addDepartment(department);
    }

    public static void editDepartment() throws IOException, SQLException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("Введите шифр отдела: ");
        int num = 0;
        String data = reader.readLine();
        try {
            num = Integer.parseInt(data);
            Collection collection;
            collection = Factory.getInstance().getDepartmentDAO().getDepartmentById(num);
            Set<Department> set = new HashSet<>(collection);
            Iterator iterator = set.iterator();
            while (iterator.hasNext()) {
                Department department = (Department) iterator.next();
                Integer id = department.getDepartmentId();
                String name;
                System.out.println("Введите новое название");
                name = reader.readLine();
                Department temporal = new Department();
                temporal.setDepartmentId(id);
                temporal.setCaption(name);
                Factory.getInstance().getDepartmentDAO().editDepartment(temporal);
            }
        } catch (NumberFormatException e) {
            System.out.println("Введено не число");
        }
    }

    public static void getDepartments() throws SQLException {
        Collection collection;
        collection = Factory.getInstance().getDepartmentDAO().getAllDepartments();
        showDepartments(collection);
    }

    public static void findDepartments() throws SQLException, IOException {
        System.out.println("Введите код отдела");
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        String data = reader.readLine();
        try {
            int num = Integer.parseInt(data);
            Collection collection;
            collection = Factory.getInstance().getDepartmentDAO().getDepartmentById(num);
            showDepartments(collection);
        } catch (NumberFormatException e) {
            System.out.println("Введено не число");
        }
    }

    public static void findEmployies() throws SQLException, IOException {
        System.out.println("Введите код отдела для выборки");
        int num = 0;
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        try {
            String data = reader.readLine();
            num = Integer.parseInt(data);
            Collection collection = Factory.getInstance().getDepartmentDAO().getDepartmentById(num);
            showDepartments(collection);
            if (collection.size() != 0) {
                Collection employies = Factory.getInstance().getEmployeeDAO().getEmployiesByDepartment(num);
                showEmployers(employies);
            } else System.out.println("Такого отдела нет");
        } catch (NumberFormatException e) {
            System.out.println("Введено не число");
        }
    }


    public static void showDepartments(Collection collection) throws SQLException {
        Set<Department> set = new HashSet<Department>(collection);
        Iterator iterator = set.iterator();
        System.out.println("====================================");
        System.out.printf("%-20s%-20s%n", "Шифр отдела", "Название отдела");
        System.out.println("====================================");
        while (iterator.hasNext()) {
            Department data = (Department) iterator.next();
            System.out.printf("%-20s%-20s%n", data.getDepartmentId(), data.getCaption());
        }
    }

    public static void showEmployers(Collection collection) throws SQLException {
        Set<Employee> set = new HashSet<Employee>(collection);
        Iterator iterator = set.iterator();
        System.out.println("====================================");
        System.out.printf("%-20s%n", "ФИО сотрудника отдела");
        System.out.println("====================================");
        while (iterator.hasNext()) {
            Employee data = (Employee) iterator.next();
            System.out.printf("%-20s%n", data.getFio());
        }
    }

    public static void deleteDepartment() throws SQLException, IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        int num = 0;
        System.out.println("Введите код отдела");
        try {
            String data = reader.readLine();
            num = Integer.parseInt(data);
            Factory.getInstance().getDepartmentDAO().deleteDepartment(num);
        } catch (NumberFormatException e) {
            System.out.println("Введено не число");
        }
    }


    public static void deleteEmployies() throws SQLException, IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("Введите ФИО сотрудника");
        String name = reader.readLine();
        Collection collection;
        Factory.getInstance().getEmployeeDAO().deleteEmployiesByName(name);
    }


    public static void addEmployee() throws SQLException, IOException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("Введите шифр отдела");
        String id = reader.readLine();
        try {
            int num = Integer.parseInt(id);
            Collection collection;
            collection = Factory.getInstance().getDepartmentDAO().getDepartmentById(num);
            showDepartments(collection);
            Set<Department> set = new HashSet<>(collection);
            if (set.size() != 0) {
                System.out.println("Введите ФИО сотрудника");
                String data = reader.readLine();
                Employee employee = new Employee(data);
                Iterator iterator = set.iterator();
                while (iterator.hasNext()) {
                    Department department = (Department) iterator.next();
                    Factory.getInstance().getDepartmentDAO().addEmployee(department, employee);
                }
            } else System.out.println("Такого отдела нет");
        } catch (NumberFormatException e) {
            System.out.println("Введено не число");
        }

    }


    static public void main(String[] args) throws IOException, SQLException {
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            System.out.println("====================================");
            System.out.println("Выберите режим работы с базой");
            System.out.println("====================================");
            System.out.println("1 - Добавить новый отдел");
            System.out.println("2 - Править данные отдела");
            System.out.println("3 - Удалить отдел");
            System.out.println("4 - Вывести данные отделов");
            System.out.println("5 - Найти отдел по коду");
            System.out.println("====================================");
            System.out.println("6 - Добавить нового сотрудника");
            System.out.println("7 - Просмотр сотрудников по отделу");
            System.out.println("8 - Удалить сотрудника");
            System.out.println("====================================");
            System.out.println("0 - Завершить работу");
            System.out.println("====================================");
            String num = reader.readLine();
            switch (num) {
                case "1":
                    addDepartment();
                    break;
                case "2":
                    editDepartment();
                    break;
                case "3":
                    deleteDepartment();
                    break;
                case "4":
                    getDepartments();
                    break;
                case "5":
                    findDepartments();
                    break;
                case "6":
                    addEmployee();
                    break;
                case "7":
                    findEmployies();
                    break;
                case "8":
                    deleteEmployies();
                    break;
                case "0":
                    System.exit(0);
                    break;
            }
        }
    }
}


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

Надеюсь, что пример хотя и простой, но пригодится для получения первых впечатлений.
Tags:
Hubs:
+2
Comments 64
Comments Comments 64

Articles