В этой части мы рассморим готовый пример с реализацией раличных видов отношений между сущностями. Советую еще раз посмотреть на структуру базы данных — Часть 15 — Новая структура данных
Как видите, мы будем использовать разные виды отношений, несмотря на невзрачность нашего проекта. Для начала мы приведем код классов, в которых будет хранится информация из таблиц. Они все имеют достаточно одинаковое строение — у них есть поля и методы set/get для обращения к этим полям.

Applicant.java

В этом классе нам надо обратить внимание на два поля:

  • Первое поле Profession profession показывает нам, что у класса Applicant существует связь/отношение с классом Profession. И это отношение много-к одному. Т.е. существует много абитуриентов, у которых будет одна и та же специальность. Также такое отношение показывает, что абитуриент может поступать только на одну специальность. Я понимаю, что в реальности может быть и не так, но не забывайте, что это учебная задача.
  • Второе поле List<ApplicantResult> applicantResultList показывает другое отношение — один-ко-многим. Т.е. у одного абитуриента существует много сданных предметов. Точнее может быть 0 и больше.

Конечно же просто такая запись не дает нам информации, из какой таблицы надо брать данные для самого класса и как формируются отношения между объектами разных классов. Для выяснения этого вопроса нам надо посмотреть на файл Applicant.hbm.xml

 

Здесь уже есть на что посмотреть. Понятно, что простые тэги 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

Как видите, в классе указано два отношения:

  • Applicant applicant — информация о том, кто сдавал данный экзамен
  • Subject subject — по какому предмету был экзамен

Посмотрим на описание нашего класса в виде файла XML — ApplicantResult.hbm.xml

 

Думаю, что данный файл уже не нуждается в комментариях — можете разобраться сами.

Теперь нам осталось посмотреть на организацию классов Profession и Subject. Между ними тоже существует связь. Причем еще более сложная — много-ко-многим. Обычно такая связь организуется в виде дополнительной таблицы (мы ее рассматривали в Часть 15 — Новая структура данных.
Называется она SPECIALITY_SUBJECT.
Сначала я приведу код классов, в которых будет прописано наше отношение, а потом мы посмотрим как это описывается с помощью XML.

Profession.java

Subject.java

Как видите отношение записывается как множество (Set). Если вы внимательно посмотрите на запись отношения один-ко-многим для классаApplicant, то увидите, что я использовал тэг bag и в классах исползовался List. В отношении много-ко-многим мы будем использовать другой тэг — set. Hibernate в этом случае более оптимально удаляет и вставляет записи об отношениях. Если хотите поэкспериментировать — поменяйте setна bag (соответственно Set на List — для инициализации можно использовать ArrayList. Помните, что Set и List всего лишь интерфейсы). И посмотрите какие запросы формирует Hibernate при обращении к базе данных. В случае List он просто удалит все записи из таблицы SPECIALITY-SUBJECT и заново вставит. При использовании Set это будет более умное решение.
Но давайте посмотрим на XML-файлы.

Profession.hbm.xml

Subject.hbm.xml

Рассмотрим самое важное на примере кусочка XML для Profession

Давайте подробно рассмотрим все составляющие данного 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

Как видите он не сильно изменился — мы только добавили новые описания классов в самом конце.

Что нам еще надо сделать ? Наверно пора написать какой-то класс для реализации DAO. Он не будет у нас пока выполнять все функции, но кое-что он уже сможет сделать. Так что встречайте

StudentDAO.java

Я настоятельно рекомендую прочитать комментарии к данному файлу. Здесь рассматрвиается одна важная особенность Hibernate — «ленивая» инициализация (lazy). Смысл ее в том, что когда вы загружаете объект с помощью того же вызова session.load() это сосвсем не означает, что Hibernate сразу же загрузит все данные. Можно сказать, что Hibernate «зарегистрирует» Ваше желание загрузить данные, но реальное обращение к базе данных может произойти только при попытке получить значение какого-то поля. еще более заметно это становится при работе с объектами, которые связаны с основным какими-либо отношениями.

Здесь приводится код класса Main.java, который нам позволит посмотреть на наш код в действии.

Еще раз призываю вас внимательно почитать код и комментарии к нему. В самом конце приводится очень характерный код для Hibernate

Посмотрите, как мы обращаемся к профессии, которую выбрал абитуриент. Мы нигде не пишем специальный 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.

Архив с исходными кодами: Исходный код

5 comments to Hibernate. Запись в виде XML-файлов

  • Владимир  says:

    Антон, спасибо за полезный курс.
    Подскажите пожалуйста как написать тест JUnit для метода dao.deleteEntry(Entry entry)?

    • admin  says:

      Не очень понял вопрос, но наверно так:
      Entry en = new Entry();
      en.setEntryId(10);
      dao.deeteEntry(en);

      • Владимир  says:

        Спасибо!

  • Сергей  says:

    Здравствуйте, немного непонятно каким образом у предметов в их полях появились профессии, по коду видно что добавляем только к профессиям — предметы profession.getSubjectList().add(sbList.get(0));

    • admin  says:

      Так в описании есть связи один-ко-многим и обратное описание — многое-к-одному. Посмотрите внимательно.

Leave a reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Лимит времени истёк. Пожалуйста, перезагрузите CAPTCHA.