В этой части мы рассморим готовый пример с реализацией раличных видов отношений между сущностями. Советую еще раз посмотреть на структуру базы данных — Часть 15 — Новая структура данных
Как видите, мы будем использовать разные виды отношений, несмотря на невзрачность нашего проекта. Для начала мы приведем код классов, в которых будет хранится информация из таблиц. Они все имеют достаточно одинаковое строение — у них есть поля и методы set/get для обращения к этим полям.
Applicant.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
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; } } |
В этом классе нам надо обратить внимание на два поля:
- Первое поле Profession profession показывает нам, что у класса Applicant существует связь/отношение с классом Profession. И это отношение много-к одному. Т.е. существует много абитуриентов, у которых будет одна и та же специальность. Также такое отношение показывает, что абитуриент может поступать только на одну специальность. Я понимаю, что в реальности может быть и не так, но не забывайте, что это учебная задача.
- Второе поле List<ApplicantResult> applicantResultList показывает другое отношение — один-ко-многим. Т.е. у одного абитуриента существует много сданных предметов. Точнее может быть 0 и больше.
Конечно же просто такая запись не дает нам информации, из какой таблицы надо брать данные для самого класса и как формируются отношения между объектами разных классов. Для выяснения этого вопроса нам надо посмотреть на файл Applicant.hbm.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?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 нам уже знакомы. Но появились новые.
Давайте сначала посмотрим на тэг <many-to-one name=»profession» column=»profession_id»>. Как видите, он содержит несколько атрибутов, которые дают информацию о том, как осуществляется отношение класса Applicant с классом Profession. Атрибут name говорит о том, что за поле использутеся для ссылки на класс Profession. Атрибут column показывает поле в таблице APPLICANT, которое служит ссылкой на таблицу PROFESSION. Ну и наконец поле class говорит о том, объект какого класса будет присоеденен к нашему объекту. Как видите, это классProfession. Думаю, что пока вопросов должно возникать не очень много.
Теперь мы можем посмотреть на более сложную запись — <bag name=»applicantResultList» inverse=»true» cascade=»all-delete-orphan»>. Как видите, здесь запись содержит несколько тэгов. Рассмотрим их подробнее:
- name — имя поля в классе, которое содержит список сданных экзаменов
- inverse — достаточно хитрый атрибут. В данном случае у нас связь двунаправленная — «родитель» знает о детях и каждый «ребенок» знает своего родителя. Чтобы эта свзяь была в одном экземпляре и существует атрибут inverse
- cascade — данный атрибут показывает, что при удалении «родителя» надо удалять и всех «детей». т.е. если мы удалим абитуриента, то удалятся и все данные об его экзаменах
- <key column=»APPLICANT_ID»> — думаю, что вы уже догадались о назначении этого тэга. Он показывает, какое поле в таблицеAPPLICANT_RESULT используется для ссылки на «родителя» — таблицу APPLICANT
- <one-to-many> — данная запись говорит о том, какой класс используется для списка.
Перейдем к следующему нашему классу — ApplicantResult. Думаю, что его текст даже разбирать не придется — все необходимая информация была рассмотрена для класса Applicant.
ApplcantResult.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
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; } } |
Как видите, в классе указано два отношения:
- Applicant applicant — информация о том, кто сдавал данный экзамен
- Subject subject — по какому предмету был экзамен
Посмотрим на описание нашего класса в виде файла XML — ApplicantResult.hbm.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?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 — Новая структура данных.
Называется она SPECIALITY_SUBJECT.
Сначала я приведу код классов, в которых будет прописано наше отношение, а потом мы посмотрим как это описывается с помощью XML.
Profession.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
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 это будет более умное решение.
Но давайте посмотрим на XML-файлы.
Profession.hbm.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<?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
1 2 3 4 |
<set name="subjectList" table="speciality_subject"> <key column="profession_id"></key> <many-to-many column="subject_id" class="students.entity.Subject"/> </set> |
Давайте подробно рассмотрим все составляющие данного XML.
- <set — этот момент мы уже обсуждали
- name — имя свойства, в котором храниться список предметов (Subject), которые надо сдать для поступления на данную специальность
- table — это таблица, которая является связующей для наших двух классов. Мы о ней говорили выше
- <key column=»profession_id»> — это поле в таблице SPECIALITY_SUBJECT, которое указывает на класс Profession (таблицуPROFESSION)
- <many-to-many column=»subject_id»/> — этот тэг содержит информацию о связи с классом Subject. Атрибут column указывает на поле в таблице SPECIALITY_SUBJECT к которому привязывается класс Subject(таблица SUBJECT). Ну а атрибут class показывает какого рода объекты находятся в списке subjectList
Вот мы и написали все необходимые XML-файлы и JAVA-файлы для работы с базой данных. Осталось совсем немного. Во-первых давайте посмотрим на обновленный файл hibernate.cfg.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
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, который нам позволит посмотреть на наш код в действии.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
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
1 2 3 4 5 6 7 |
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.
При работе с базой данных создается объект, который реализует интерфейс CurrentSessionContext. Hibernate включает три реализации этого интерфеса — JTASessionContext, ManagedSessionContext, ThreadLocalSessionContext. Никто не мешает написать свой :). Задача контекста — описать как и когда сессия открывается и закрывается
Первый контекст управляется транзакциями, которыми в свою очередь управляет AplpicationServer (J2EE). Второй позволяет управлять транзакциями внешним пакетам. И в этом случае разработчик отвечает за открытие и закрытие (уничтожение) контекста. И наконец третий — локальный контекст. Так вот особенность вызова getCurrentSession в том, что он позволяет создать контекст автоматически при начале транзакции (обратите внимание на вызов session.beginTransaction() — если его убрать, то получим ошибку — createQuery is not valid without active transaction). И что достаточно удобно — при закрытии транзакции контекст «закрывается» тоже автоматически. Если же мы воспользуемся методомopenSesion, то все будет хорошо. Вы можете даже убрать вызовы для транзакций. Но возникает другая проблема — в случае такого открытия сессии ее надо закрыть самому. И делать это придется в нашем случае там, где мы ее создали — доступна она нам только внутри метода. И если мы ее там закроем, то получим исеключение LazyInitializationException. Т.е. надо вытаскивать session наружу метода, что не хотелось бы.
Думаю, что для понимания основ работы с Hibernate этого материала будет достаточно. Опять же — призываю вас экспериментировать, искать, пробовать и читать документацию. Это здорово помогает понять многие тонкости использования Hibernate.
Если вы подумали о том, что количество файлов увеличивается в два раза (для каждого Entity необходимо написать свой XML), то вы «верной дорогой идете, товарищи». Ибо нам предстоит узнать более простой вариант описания — аннотации. Добро пожаловать в Часть 18 — Hibernate. Аннотации вместо XML.
Архив с исходными кодами: Исходный код