Введение в Hibernate

30.01.2013
Говоря о Hibernate следует ввести ряд понятий:
  • ORM (Object-relational mapping) — технология программирования, которая связывает базы данных с концепциями объектно-ориентированных языков программирования,создавая «виртуальную объектную базу данных».
  • JPA – это технология, обеспечивающая объектно-реляционное отображение простых JAVA объектов и предоставляющая API для сохранения, получения и управления такими объектами.JPA – это спецификация (документ, утвержденный как стандарт, описывающий все аспекты технологии), часть EJB3 спецификации.
  • Реализации JPA:
    Hibernate
    Oracle TopLink
    Apache OpenJPA
Таким образом Hibernate — популярная библиотека для языка программирования Java с открытм исходным кодом, предназначенная для решения ORM задач.

В качестве предметной области рассмотрим автомобильную промышленность.Суть автопрома в следующем: имеются марки, у марки есть модели (отношение один ко многим).У модели имеется модификация (отношение один ко многим). У каждой взятой модификации имеется комплектация(многие ко многим),а также другие характеристики, в данном случае рассмотрим характеристику "Коробка переключения передач".Итак, предметная область определена. Для начала работы необходимо продумать структуру классов, а также осуществить процедуру маппинга.
Mapping-проецирование (сопоставление) Java классов с таблицами базы данных. Реализация маппинга возможна через использование конфигурационных файлов XML, либо через аннотации.

Реализация маппинга

В качестве реализации маппинга рассмотрим подход с использованием аннотаций.

Сущность марка

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "MARK")
public class MarkEntity {
    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "NAME",length = 40,nullable = false)
    private String name;

    @Column(name = "FOUNDER",length = 40,nullable = false)
    private String founder;

    @OneToMany(mappedBy = "markEntity",fetch = FetchType.LAZY,
               cascade = CascadeType.ALL,orphanRemoval = true)
    private List models=new ArrayList();
    //getters&setters
 }

Сущность модель

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "MODEL")
public class ModelEntity {
    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "NAME", length = 40, nullable = false)
    private String name;

    @Column(name = "YEAR_START")
    private int yearStart;

    @Column(name = "YEAR_END")
    private int yearEnd;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "MARK_ID", referencedColumnName = "ID")
    private MarkEntity markEntity;

    @OneToMany(mappedBy = "modelEntity", fetch = FetchType.LAZY, 
              cascade = CascadeType.ALL)
    private List modifs=new ArrayList();
    //getters&setters
}

Сущность модификация

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name="MODIFICATION")
public class ModificationEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private long id;

    @Column(name = "NAME",nullable = false,length = 40)
    private String name;

    @ManyToOne(targetEntity = ModelEntity.class)
    @JoinColumn(name = "MODEL_ID",referencedColumnName = "ID")
    private ModelEntity modelEntity;

    @OneToOne
    private GearBoxEntity gearBoxEntity;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "MODIF_COMPL",
     joinColumns = {@JoinColumn(name = "MODIF_ID")},
     inverseJoinColumns = {@JoinColumn(name = "COMPL_ID")})
    private List complectations=new   ArrayList();
//getters&setters
}

Сущность комплектация

import javax.persistence.*;

@Entity
@Table(name = "COMPL")
public class ComplectationEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ID")
    private long id;

    @Column(name = "NAME")
    private String name;
}
//getters&setters

Сущность коробка передач

import javax.persistence.*;

@Entity
@Table(name = "GEARBOX")
public class GearBoxEntity {
    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "NAME")
    private String name;
   //getters&setters
}
Несколько слов об используемых аннотациях:
@Entity Указывает, что данный класс является сущностью бизнес модели.
@Table Указывает на имя таблицы в базе данных. Если имя таблицы совпадает с именем класса, аннотацию можно не использовать.
@Id Определяет первичный ключ. Поле над которым стоит аннотация должно быть либо примитивом, либо оберткой над жтим примитивом:String; java.util.Date; java.sql.Date; java.math.BigDecimal; java.math.BigInteger.
GeneratedValue Определяет стратегию, по которой будет генерироваться первичный ключ.
@ColumУказывает имя столбца, с которым связано поле класса

В результате наши классы будут соответствовать схеме БД:
После того как все классы замаплены, необходимо создать специальный файл конфигурации, который Hibernate требует для своей работы. В данном примере используется контекст Spring



    
    

        
            
            
            
            
        
   

    
    
        
        
            
                formain.entities.mark.MarkEntity
                formain.entities.model.ModelEntity
                formain.entities.modification.ModificationEntity
                formain.entities.gearbox.GearBoxEntity
                formain.entities.complectation.ComplectationEntity
            
        
        
            
                org.hibernate.dialect.MySQL5Dialect
                create
                
            
        
    


    
    
        
    

    
    
        
    

    
        
    

    
    
        
    

    
        
    


    
    
        
    

    
        
    

    
    
        
    

    
        
    

    
    
        
    

    
        
    

Как мы видим, в контексте объявлены классы которые помечены как @Entity, а также определены некоторые из свойств hibernate.properties:
  • hibernate.dialect Определяет СУБД
  • hibernate.hbm2ddl.auto свойство, которое указывается что нужно сделать со схемой БД при инициализации.
    Может принимать значения
    • create При запуске приложения будет создавать схему заново. Все данные которые были в старой схеме потеряются.
    • update При запуске приложения будет осуществляться сверка имеющейся в БД схемы с классами приложения. Если были изменения,то схема обновится, а данные останутся.
    • create-drop Аналогично create,только после завершения схема с данными будет удаляться.
    • validate Будет проверять соответствие схемы и классов,если соответствия нет ,то выбросится исключение.
  • hibernate.show_sql Позволяет выводить в консоль запросы,генерируемые hibernate

Реализация слоев DAO и сервисов

Марка DAO
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

import java.util.List;

public class MarkDaoImpl implements MarkDao{
    private SessionFactory sessionFactory;

    @Override
    public void appendModel(long markId, ModelEntity modelEntity) {
        MarkEntity markEntity= (MarkEntity)  getCurrentSession().load(MarkEntity.class,markId);
        if(markEntity!=null){
            markEntity.getModels().add(modelEntity);
        }
    }

    @Override
    public MarkEntity getEntityByName(String name) {
        String q="FROM MarkEntity m WHERE m.name=:markName";
        Query query=getCurrentSession().createQuery(q);
        query.setParameter("markName",name);
        List marks=query.list();
        if(marks!=null &&!marks.isEmpty()){
            return marks.get(0);
        }else{
            return null;
        }
    }

    @Override
    public void save(MarkEntity markEntity) {
        getCurrentSession().save(markEntity);
    }
    @Override
    public void delete(long id) {
        getCurrentSession().delete(getEntityById(id));
    }

    @Override
    public void update(MarkEntity markEntity) {
        getCurrentSession().update(markEntity);
    }

    @Override
    public MarkEntity getEntityById(long id) {
        return (MarkEntity) getCurrentSession().get(MarkEntity.class, id);
    }

    private Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
}
Как мы видим, основной объект через который ведется работа с базой данных является объект org.hibernate.Session;
Марка Сервис
import org.springframework.transaction.annotation.Transactional;

@Transactional
public class MarkService {
    private MarkDao markDao;

    public void save(MarkEntity markEntity) {
        markDao.save(markEntity);
    }

    public void delete(long id) {
        markDao.delete(id);
    }

    public void update(MarkEntity markEntity) {
        markDao.update(markEntity);
    }

    public MarkEntity getEntityById(long id) {
        return markDao.getEntityById(id);
    }

    public MarkEntity getEntityByName(String name){
        return markDao.getEntityByName(name);
    }

    public void appendModel(long markId, ModelEntity modelEntity) {
       markDao.appendModel(markId,modelEntity);
    }

    public void setMarkDao(MarkDao markDao) {
        this.markDao = markDao;
    }
}
Модель DAO
import formain.entities.modification.ModificationEntity;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

public class ModelDaoImpl implements ModelDao {
    private SessionFactory sessionFactory;

    @Override
    public void appendModification(long modelId, ModificationEntity modificationEntity) {
        ModelEntity modelEntity= (ModelEntity) getCurrentSession().load(ModelEntity.class,modelId);
        modelEntity.getModifs().add(modificationEntity);
    }
Модификация DAO
import org.hibernate.Session;
import org.hibernate.SessionFactory;

import java.util.List;

public class ModificationDaoImpl implements ModificationDao {
    private SessionFactory sessionFactory;

    @Override
    public void save(ModificationEntity modificationEntity) {
        getCurrentSession().save(modificationEntity);
    }

    @Override
    public void delete(long id) {
        getCurrentSession().delete(id);
    }

    @Override
    public void update(ModificationEntity modificationEntity) {
        getCurrentSession().update(modificationEntity);
    }

    @Override
    public ModificationEntity getEntityById(long id) {
        return (ModificationEntity) getCurrentSession().get(ModificationEntity.class, id);
    }

    @Override
    public String getModifInfoAsString(long id) {
        StringBuilder result=new StringBuilder();
        ModificationEntity modificationEntity= (ModificationEntity) getCurrentSession().load(ModificationEntity.class,id);
        ModelEntity modelEntity=modificationEntity.getModelEntity();
        MarkEntity markEntity=modelEntity.getMarkEntity();
        result.append("MARK:").append(markEntity.getName()).append(";\n").
               append("MODEL:").append(modelEntity.getName()).append(";\n").
               append("MODIF:").append(modificationEntity.getName()).append("\n");
        List compls=modificationEntity.getComplectations();
        if(compls!=null && !compls.isEmpty()){
           result.append("Complectation:\n");
            for(ComplectationEntity complectationEntity:compls){
                result.append(complectationEntity.getName()).append(",");
            }
            result.append("\n");
        }
        result.append("GearBox:").append(modificationEntity.getGearBoxEntity().getName());
        return result.toString();
    }

    private Session getCurrentSession() {
        return sessionFactory.getCurrentSession();
    }

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }
}

Пример использования

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ForMain {
    public static void main(String args[]) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("/spring-config.xml");
        MarkService markService = (MarkService) applicationContext.getBean("markService");
        ModelService modelService = (ModelService) applicationContext.getBean("modelService");
        ModificationService modificationService = (ModificationService) applicationContext.getBean("modifiactionService");
        GearBoxService gearBoxService = (GearBoxService) applicationContext.getBean("gearBoxService");

        MarkEntity markEntity = new MarkEntity();
        markEntity.setName("FORD");
        markEntity.setFounder("Henry Ford");
        markService.save(markEntity);
        System.out.println("Ford was saved,it's id=" + markEntity.getId());

        markEntity = new MarkEntity();
        markEntity.setName("Mersedes-Benz");
        try {
            markService.save(markEntity);
        } catch (Exception e) {
            System.out.println("Look at your entity class, field founder should not be null");
        }
        markEntity.setFounder("Karl Benz");
        markService.save(markEntity);
        System.out.println("Mersedes-Benz was saved,it's id=" + markEntity.getId());


        markEntity = markService.getEntityByName("FORD");
        if (markEntity != null) {
            ModelEntity modelEntity = new ModelEntity();
            modelEntity.setName("Focus");
            modelEntity.setYearEnd(1900);
            modelEntity.setMarkEntity(markEntity);
            try {
                markEntity.getModels().add(modelEntity);
            } catch (Exception e) {
                System.out.println("Can't fetch. Models are too lazy!");
            }
            markService.appendModel(markEntity.getId(), modelEntity);
            System.out.println("Focus model was added to ford!");
        }


        GearBoxEntity gearBoxEntity = new GearBoxEntity();
        gearBoxEntity.setName("AUTO");
        gearBoxService.save(gearBoxEntity);

        gearBoxEntity = new GearBoxEntity();
        gearBoxEntity.setName("MECHANICAL");
        gearBoxService.save(gearBoxEntity);

        ModelEntity focusModel=modelService.getEntityById(1);
        if(focusModel!=null){
            ModificationEntity modificationEntity=new ModificationEntity();
            modificationEntity.setName("RS");
            modificationEntity.setGearBoxEntity(gearBoxService.getEntityById(2));
            modificationEntity.setModelEntity(focusModel);
            System.out.println("Complectation is EAGER, lets use it directly");
            ComplectationEntity compl=new ComplectationEntity();
            compl.setName("ABS");
            modificationEntity.getComplectations().add(compl);

            compl=new ComplectationEntity();
            compl.setName("ESP");
            modificationEntity.getComplectations().add(compl);

            modelService.appendModification(focusModel.getId(),modificationEntity);
            System.out.println("RS Modification was successfully added");
        }
        System.out.println();
        System.out.println(modificationService.getModifInfoAsString(1));
    }
}
В результате запуска в консоль выведется следующая информация

Ford was saved,it's id=1
Look at your entity class, field founder should not be null
Mersedes-Benz was saved,it's id=2
Can't fetch. Models are too lazy!
Focus model was added to ford!
Complectation is EAGER, lets use it directly
RS Modification was successfully added
MARK:FORD;
MODEL:Focus;
MODIF:RS
Complectation:
ABS,ESP,
GearBox:MECHANICAL