Книга 1 - Начальные сведения
Книга 2 - Более профессиональный подход |
Студенческий отдел кадров Hibernate. Запись в виде XML-файлов
В этой части мы рассморим готовый пример с реализацией раличных видов отношений между сущностями.
Советую еще раз посмотреть на структуру базы данных -
Часть 15 - Новая структура данных Applicant.java package students.entity; import java.util.List; public class Applicant { private Long applicantId; private Profession profession; private List<ApplicantResult> applicantResultList; private String firstName; private String lastName; private String middleName; private Integer entranceYear; public Long getApplicantId() { return applicantId; } public void setApplicantId(Long applicantId) { this.applicantId = applicantId; } public List<ApplicantResult> getApplicantResultList() { return applicantResultList; } public void setApplicantResultList(List<ApplicantResult> applicantResultList) { this.applicantResultList = applicantResultList; } public Integer getEntranceYear() { return entranceYear; } public void setEntranceYear(Integer entranceYear) { this.entranceYear = entranceYear; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getMiddleName() { return middleName; } public void setMiddleName(String middleName) { this.middleName = middleName; } public Profession getProfession() { return profession; } public void setProfession(Profession profession) { this.profession = profession; } } В этом классе нам надо обратить внимание на два поля:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="students.entity"> <class name="Applicant" table="applicant"> <id name="applicantId" column="applicant_id"> <generator class="native"/> </id> <many-to-one name="profession" column="profession_id" class="students.entity.Profession"/> <bag name="applicantResultList" inverse="true" cascade="all-delete-orphan"> <key column="APPLICANT_ID"></key> <one-to-many class="ApplicantResult"/> </bag> <property name="firstName" column="first_name"/> <property name="lastName" column="last_name"/> <property name="middleName" column="middle_name"/> <property name="entranceYear" column="entrance_year"/> </class> </hibernate-mapping>
Здесь уже есть на что посмотреть. Понятно, что простые тэги property
нам уже знакомы. Но появились новые. Теперь мы можем посмотреть на более сложную запись - <bag name="applicantResultList" inverse="true" cascade="all-delete-orphan">. Как видите, здесь запись содержит несколько тэгов. Рассмотрим их подробнее:
Перейдем к следующему нашему классу - ApplicantResult. Думаю, что его текст даже разбирать не придется - все необходимая информация была рассмотрена для класса Applicant. ApplcantResult.java package students.entity; public class ApplicantResult { private Long applicantResultId; private Applicant applicant; private Subject subject; private Integer mark; public Applicant getApplicant() { return applicant; } public void setApplicant(Applicant applicant) { this.applicant = applicant; } public Long getApplicantResultId() { return applicantResultId; } public void setApplicantResultId(Long applicantResultId) { this.applicantResultId = applicantResultId; } public Integer getMark() { return mark; } public void setMark(Integer mark) { this.mark = mark; } public Subject getSubject() { return subject; } public void setSubject(Subject subject) { this.subject = subject; } } Как видите, в классе указано два отношения:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="students.entity"> <class name="ApplicantResult" table="applicant_result"> <id name="applicantResultId" column="applicant_result_id"> <generator class="native"/> </id> <many-to-one name="applicant" column="applicant_id" class="students.entity.Applicant"/> <many-to-one name="subject" column="subject_id" class="students.entity.Subject"/> <property name="mark" column="mark"/> </class> </hibernate-mapping> Думаю, что данный файл уже не нуждается в комментариях - можете разобраться сами.
Теперь нам осталось посмотреть на организацию классов Profession и
Subject. Между ними тоже существует связь. Причем еще более сложная -
много-ко-многим. Обычно такая связь организуется в виде дополнительной таблицы (мы ее рассматривали
в Часть 15 - Новая структура данных. Profession.java package students.entity; import java.util.HashSet; import java.util.Set; public class Profession { private Long professionId; private String professionName; private Set<Subject> subjectList = new HashSet<Subject>(); public Long getProfessionId() { return professionId; } public void setProfessionId(Long professionId) { this.professionId = professionId; } public String getProfessionName() { return professionName; } public void setProfessionName(String professionName) { this.professionName = professionName; } public Set<Subject> getSubjectList() { return subjectList; } public void setSubjectList(Set<Subject> subjectList) { this.subjectList = subjectList; } } Subject.java package students.entity; import java.util.Set; public class Subject { private Long subjectId; private String subjectName; private Set<Profession> professionList; public Set<Profession> getProfessionList() { return professionList; } public void setProfessionList(Set<Profession> professionList) { this.professionList = professionList; } public Long getSubjectId() { return subjectId; } public void setSubjectId(Long subjectId) { this.subjectId = subjectId; } public String getSubjectName() { return subjectName; } public void setSubjectName(String subjectName) { this.subjectName = subjectName; } }
Как видите отношение записывается как множество (Set). Если вы
внимательно посмотрите на запись отношения один-ко-многим для класса Applicant,
то увидите, что я использовал тэг bag и в классах исползовался
List. В отношении много-ко-многим мы будем использовать другой тэг -
set. Hibernate в этом случае более оптимально удаляет и вставляет
записи об отношениях. Если хотите поэкспериментировать - поменяйте set на
bag (соответственно Set на
List - для инициализации можно использовать
ArrayList. Помните, что Set и
List всего лишь интерфейсы). И посмотрите какие запросы формирует
Hibernate при обращении к базе данных. В случае List он просто
удалит все записи из таблицы SPECIALITY-SUBJECT и заново вставит.
При использовании Set это будет более умное решение. Profession.hbm.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="students.entity"> <class name="Profession" table="profession"> <id name="professionId" column="profession_id"> <generator class="native"/> </id> <property name="professionName" column="profession_name"/> <set name="subjectList" table="speciality_subject"> <key column="profession_id"></key> <many-to-many column="subject_id" class="students.entity.Subject"/> </set> </class> </hibernate-mapping> Subject.hbm.xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="students.entity"> <class name="Subject" table="subject"> <id name="subjectId" column="subject_id"> <generator class="native"/> </id> <property name="subjectName" column="subject_name"/> <set name="professionList" table="speciality_subject"> <key column="subject_id"></key> <many-to-many column="profession_id" class="students.entity.Profession"/> </set> </class> </hibernate-mapping> Рассмотрим самое важное на примере кусочка XML для Profession
<set name="subjectList" table="speciality_subject">
<key column="profession_id"></key>
<many-to-many column="subject_id" class="students.entity.Subject"/>
</set>
Давайте подробно рассмотрим все составляющие данного XML.
Вот мы и написали все необходимые XML-файлы и JAVA-файлы для работы с базой данных. Осталось совсем немного. Во-первых давайте посмотрим на обновленный файл hibernate.cfg.xml <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="connection.url">jdbc:mysql://127.0.0.1:3306/db_applicant</property> <property name="connection.username">root</property> <property name="connection.password">root</property> <!-- SQL dialect --> <property name="dialect">org.hibernate.dialect.MySQLInnoDBDialect</property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">1</property> <!-- Enable Hibernate's automatic session context management --> <property name="current_session_context_class">thread</property> <!-- Disable the second-level cache --> <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <!-- Mapping files --> <mapping resource="students/entity/Profession.hbm.xml"/> <mapping resource="students/entity/Applicant.hbm.xml"/> <mapping resource="students/entity/Subject.hbm.xml"/> <mapping resource="students/entity/ApplicantResult.hbm.xml"/> </session-factory> </hibernate-configuration> Как видите он не сильно изменился - мы только добавили новые описания классов в самом конце. Что нам еще надо сделать ? Наверно пора написать какой-то класс для реализации DAO. Он не будет у нас пока выполнять все функции, но кое-что он уже сможет сделать. Так что встречайте StudentDAO.java package students.dao; import java.util.List; import org.hibernate.Hibernate; import org.hibernate.Session; import students.entity.Applicant; import students.entity.Profession; import students.entity.Subject; import students.utils.HibernateUtil; public class StudentDAO { public Long addApplicant(Applicant applicant) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Long result = (Long) session.save(applicant); session.getTransaction().commit(); return result; } public void updateApplicant(Applicant applicant) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.saveOrUpdate(applicant); session.getTransaction().commit(); } public Applicant getApplicant(Long applicantId) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Applicant result = (Applicant) session.load(Applicant.class, applicantId); // Насильная инициализация списка. Не очень хорошая практика так делать Hibernate.initialize(result.getApplicantResultList()); session.getTransaction().commit(); return result; } public List<Applicant> findApplicant() { // Если поменять первую строку на вторую, то исключение о неинициализированной коллекции // в классе Main уйдет. Session session = HibernateUtil.getSessionFactory().getCurrentSession(); //Session session = HibernateUtil.getSessionFactory().openSession(); session.beginTransaction(); List<Applicant> result = session.createQuery("from Applicant order by lastName, firstName").list(); // Насильная инициализация списка. Не очень хорошая практика так делать for (Applicant a : result) { Hibernate.initialize(a.getProfession()); } session.getTransaction().commit(); return result; } public Long addProfession(Profession profession) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Long result = (Long) session.save(profession); session.getTransaction().commit(); return result; } public void updateProfession(Profession profession) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.saveOrUpdate(profession); session.getTransaction().commit(); } public Profession getProfession(Long professionId) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Profession result = (Profession) session.load(Profession.class, professionId); // Насильная инициализация списка. Не очень хорошая практика так делать Hibernate.initialize(result.getSubjectList()); session.getTransaction().commit(); return result; } public List<Profession> findProfession() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); List<Profession> result = session.createQuery("from Profession order by professionName").list(); // Насильная инициализация списка. Не очень хорошая практика так делать for (Profession a : result) { Hibernate.initialize(a.getSubjectList()); } session.getTransaction().commit(); return result; } public Long addSubject(Subject subject) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Long result = (Long) session.save(subject); session.getTransaction().commit(); return result; } public void updateSubject(Subject subject) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); session.saveOrUpdate(subject); session.getTransaction().commit(); } public Subject getSubject(Long subjectId) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Subject result = (Subject) session.load(Subject.class, subjectId); // Насильная инициализация списка. Не очень хорошая практика так делать Hibernate.initialize(result.getProfessionList()); session.getTransaction().commit(); return result; } public List<Subject> findSubject() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); List<Subject> result = session.createQuery("from Subject order by subjectName").list(); // Насильная инициализация списка. Не очень хорошая практика так делать for (Subject a : result) { Hibernate.initialize(a.getProfessionList()); } session.getTransaction().commit(); return result; } } Я настоятельно рекомендую прочитать комментарии к данному файлу. Здесь рассматрвиается одна важная особенность Hibernate - "ленивая" инициализация (lazy). Смысл ее в том, что когда вы загружаете объект с помощью того же вызова session.load() это сосвсем не означает, что Hibernate сразу же загрузит все данные. Можно сказать, что Hibernate "зарегистрирует" Ваше желание загрузить данные, но реальное обращение к базе данных может произойти только при попытке получить значение какого-то поля. еще более заметно это становится при работе с объектами, которые связаны с основным какими-либо отношениями. Здесь приводится код класса Main.java, который нам позволит посмотреть на наш код в действии. package students; import students.entity.Profession; import java.util.List; import students.dao.StudentDAO; import students.entity.Applicant; import students.entity.Subject; public class Main { public static void main(String[] args) { StudentDAO dao = new StudentDAO(); // Добавление новых предметов Subject subject = new Subject(); subject.setSubjectName("Mathematics"); dao.addSubject(subject); subject = new Subject(); subject.setSubjectName("Chemistry"); dao.addSubject(subject); subject = new Subject(); subject.setSubjectName("Logic"); dao.addSubject(subject); System.out.println("List of SUBJECTS"); System.out.println("----------------"); List<Subject> sbList = dao.findSubject(); // В списке вы увидите, что предметы пока не привязаны к профессиям - количество = 0 for (Subject a : sbList) { System.out.println(a.getSubjectId() + ":" + a.getSubjectName() + ". Number of profession:" + a.getProfessionList().size()); } // Теперь добавим профессии Profession profession = new Profession(); profession.setProfessionName("Programmer"); // Список предметов, которые надо сдавать для этой профессии // Обратите внимание, что в классе Profession мы создаем пустой список // чтобы не было NullPointerException profession.getSubjectList().add(sbList.get(0)); profession.getSubjectList().add(sbList.get(2)); dao.addProfession(profession); profession = new Profession(); profession.setProfessionName("Biologist"); profession.getSubjectList().add(sbList.get(1)); profession.getSubjectList().add(sbList.get(2)); // Получим профессию по ID и добавим еще один предмет для сдачи Long id = dao.addProfession(profession); profession = dao.getProfession(id); profession.getSubjectList().add(sbList.get(0)); dao.updateProfession(profession); // Смотрим список профессий System.out.println(); System.out.println("List of PROFESSIONS"); System.out.println("-------------------"); List<Profession> prList = dao.findProfession(); for (Profession a : prList) { System.out.println(a.getProfessionId() + ":" + a.getProfessionName()); } System.out.println(); System.out.println("List of SUBJECTS"); System.out.println("----------------"); sbList = dao.findSubject(); // В списке вы увидите, что предметы теперь привязаны к профессиям - количество > 0 for (Subject a : sbList) { System.out.println(a.getSubjectId() + ":" + a.getSubjectName() + ". Number of profession:" + a.getProfessionList().size()); } // А теперь создадим новых абитуриентов Applicant applicant = new Applicant(); applicant.setFirstName("John"); applicant.setMiddleName("M"); applicant.setLastName("Danny"); // Задаем профессию applicant.setProfession(prList.get(0)); applicant.setEntranceYear(2009); dao.addApplicant(applicant); applicant = new Applicant(); applicant.setFirstName("Poul"); applicant.setMiddleName("H"); applicant.setLastName("Tride"); // Задаем профессию applicant.setProfession(prList.get(1)); applicant.setEntranceYear(2009); dao.addApplicant(applicant); System.out.println(); System.out.println("List of APPLICANTS"); System.out.println("------------------"); List<Applicant> apList = dao.findApplicant(); for (Applicant a : apList) { System.out.println(a.getFirstName() + ":" + a.getLastName() + " - " + a.getProfession().getProfessionName()); // Если убрать комментарий, то получим сообщене об ошибке - коллекция не инициализирована // Но еще можно посмотреть комментарий в StudentDAO (метод findApplicant()). //System.out.println(a.getProfession().getSubjectList().size()); } } } Еще раз призываю вас внимательно почитать код и комментарии к нему. В самом конце приводится очень характерный код для Hibernate
List<Applicant> apList = dao.findApplicant();
for (Applicant a : apList) {
System.out.println(a.getFirstName() + ":" + a.getLastName() + " - " + a.getProfession().getProfessionName());
// Если убрать комментарий, то получим сообщене об ошибке - коллекция не инициализирована
// Но еще можно посмотреть комментарий в StudentDAO (метод findApplicant()).
//System.out.println(a.getProfession().getSubjectList().size());
}
Посмотрите, как мы обращаемся к профессии, которую выбрал абитуриент. Мы нигде не пишем специальный SQL, который получал бы данные о специальности. Мы просто просим дать нам поле profession. Hibernate сам все сделает за вас. Это очень удобно. Думаю, что именно эта простота сделала Hibernate (да и вооще ORM) столь популярным инструментом. Ведь мы можем сделать очень длинные цепочки. Можно начать с какой-нибудь оценки на экзамене и протащить полную цепочку начиная с абитуриента и заканчивая полным списком предметов для специальности. Если предположить, что ar указывает на какую-то оценку, то выглядит вот так:
ar.getApplicant().getProfession().getSubjectList().size() Мы получили количество предметов, которые надо здать абитуриенту, который получил данную оценку. И для этого нам не потребовалось писать ни одной строчки SQL. Такое не может не радовать. Особенно когда вам необходимо делать много запросов для реализации какой-то достаточно сложной логики. Еще раз возвращаясь в понятию lazy (ленивый). Посмотрите, что я в некоторых случаях делаю вызов Hibernate.initialize(). Этот вызов насильно загружает данные для поля. Особенно это бывает актуально для отношений один-ко-многим, многие-ко-многим. Такие коллекции конечно же делаются "ленивыми" и не инициализируются. Попробуйте закомментировать где-нибудь этот вызов - получите ошибку. Например в методе StudentDAO.findSubject. В классе Main нам хочется узнать размер коллекции специальностей, для которых надо сдавать этот предмет. И вы увидите последствия.
Если углубляться в данную проблему, то в коде я оставил вариант вызова openSession
вместо getCurrentSession. Думаю, что для понимания основ работы с Hibernate этого материала будет достаточно. Опять же - призываю вас экспериментировать, искать, пробовать и читать документацию. Это здорово помогает понять многие тонкости использования Hibernate. Если вы подумали о том, что количество файлов увеличивается в два раза (для каждого Entity необходимо написать свой XML), то вы "верной дорогой идете, товарищи". Ибо нам предстоит узнать более простой вариант описания - аннотации. Добро пожаловать в Часть 18 - Hibernate. Аннотации вместо XML. Архив с исходными кодами: Исходный код |