Започнете с Hibernate

Добре е да разберете необходимостта от обектно / релационно картографиране (ORM) в Java приложения, но вероятно сте нетърпеливи да видите Hibernate в действие. Ще започнем, като ви покажем един прост пример, който демонстрира част от неговата сила.

Както вероятно сте наясно, традиционно е книгата по програмиране да започва с пример „Hello World“. В тази глава следваме тази традиция, въвеждайки Hibernate с относително проста програма "Hello World". Само отпечатването на съобщение в прозореца на конзолата няма да е достатъчно, за да се демонстрира наистина Hibernate. Вместо това нашата програма ще съхранява новосъздадени обекти в базата данни, ще ги актуализира и ще изпълнява заявки за извличането им от базата данни.

В допълнение към каноничния пример "Hello World", ние представяме основните API на Hibernate и даваме подробности за основната конфигурация.

"Hello World" с Hibernate

Приложенията за хибернация дефинират постоянни класове, които се "картографират" в таблици на база данни. Нашият пример "Hello World" се състои от един клас и един файл за картографиране. Нека да видим как изглежда един прост устойчив клас, как е посочено картографирането и някои от нещата, които можем да направим с екземпляри на постоянния клас, използващ Hibernate.

Целта на нашето примерно приложение е да съхранява съобщения в база данни и да ги извлича за показване. Приложението има прост постоянен клас Message, който представлява тези съобщения за печат. Нашият Messageклас е показан в Листинг 1.

Листинг 1. Message.java: Прост постоянен клас

пакет здравей; публичен клас Съобщение {private Long id; частен текстов низ; лично съобщение nextMessage; частно съобщение () {} публично съобщение (текстов низ) {this.text = text; } public Long getId () {return id; } private void setId (Long id) {this.id = id; } public String getText () {return text; } public void setText (Текст на низа) {this.text = text; } публично съобщение getNextMessage () {return nextMessage; } public void setNextMessage (Съобщение nextMessage) {this.nextMessage = nextMessage; }}

Нашият Messageклас има три атрибута: атрибут идентификатор, текст на съобщението и препратка към друг Message. Атрибутът идентификатор позволява на приложението достъп до идентичността на базата данни - стойността на първичния ключ - на постоянен обект. Ако два екземпляра Messageимат една и съща стойност на идентификатора, те представляват един и същ ред в базата данни. Избрахме Longза типа на нашия идентификатор атрибут, но това не е изискване. Hibernate позволява почти всичко за типа идентификатор, както ще видите по-късно.

Може би сте забелязали, че всички атрибути на Messageкласа имат методи за достъп до свойства на JavaBean. Класът също има конструктор без параметри. Постоянните класове, които използваме в нашите примери, почти винаги ще изглеждат така.

Екземплярите на Messageкласа могат да се управляват (правят постоянни) от Hibernate, но не е задължително . Тъй като Messageобектът не реализира никакви специфични за Hibernate класове или интерфейси, можем да го използваме като всеки друг Java клас:

Съобщение на съобщението = ново съобщение ("Hello World"); System.out.println (message.getText ());

Този фрагмент на кода прави точно това, което сме очаквали от приложенията "Hello World": Той печата "Hello World"на конзолата. Може да изглежда, че се опитваме да бъдем сладки тук; всъщност ние демонстрираме важна характеристика, която отличава Hibernate от някои други решения за устойчивост, като EJB (Enterprise JavaBean) обектни обекти. Нашият постоянен клас може да се използва във всеки контекст на изпълнение - не е необходим специален контейнер. Разбира се, вие дойдохте тук, за да видите самия хибернация, така че нека запазим нов Messageв базата данни:

Сесия на сесията = getSessionFactory (). OpenSession (); Транзакция tx = session.beginTransaction (); Съобщение на съобщението = ново съобщение ("Hello World"); session.save (съобщение); tx.commit (); session.close ();

Този код извиква хибернация Sessionи Transactionинтерфейси. (Скоро ще стигнем до това getSessionFactory()обаждане.) Резултатът е изпълнението на нещо подобно на следния SQL:

вмъкване в стойности на MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID) (1, „Hello World“, null) 

Задръжте - MESSAGE_IDколоната се инициализира до странна стойност. Не сме задавали idсобствеността messageникъде, така че бихме очаквали, че е така null, нали? Всъщност idсвойството е специално: Това е свойство на идентификатора - съдържа генерирана уникална стойност. (Ще обсъдим как по-късно се генерира стойността.) Стойността се присвоява на Messageекземпляра от Hibernate, когато save()бъде извикана.

За този пример приемаме, че MESSAGESтаблицата вече съществува. Разбира се, искаме нашата програма "Hello World" да отпечата съобщението на конзолата. Сега, когато имаме съобщение в базата данни, сме готови да демонстрираме това. Следващият пример извлича всички съобщения от базата данни по азбучен ред и ги отпечатва:

Сесия newSession = getSessionFactory (). OpenSession (); Транзакция newTransaction = newSession.beginTransaction (); Списък на съобщенията = newSession.find ("от Съобщение като m ред по m.text asc"); System.out.println (messages.size () + "намерено съобщение (я):"); за (Итератор iter = messages.iterator (); iter.hasNext ();) {Съобщение съобщение = (Съобщение) iter.next (); System.out.println (message.getText ()); } newTransaction.commit (); newSession.close ();

Буквалният низ "from Message as m order by m.text asc"е Hibernate заявка, изразена в собствения обектно-ориентиран Hibernate Query Language (HQL) на Hibernate. Тази заявка се превежда вътрешно в следния SQL при find()извикване:

изберете m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID от MESSAGES m ред по m.MESSAGE_TEXT възходящо 

Кодовият фрагмент се отпечатва:

Намерени са 1 съобщение (я): Hello World 

Ако никога преди не сте използвали ORM инструмент като Hibernate, вероятно сте очаквали да видите SQL изразите някъде в кода или метаданните. Те не са там. Всички SQL се генерират по време на изпълнение (всъщност при стартиране, за всички SQL инструкции за многократна употреба).

За да позволи тази магия да се случи, Hibernate се нуждае от повече информация за това как Messageкласът трябва да бъде устойчив. Тази информация обикновено се предоставя в документ за XML картографиране . Документът за картографиране дефинира, наред с други неща, как свойствата на Messageкласа се преобразуват в колони на MESSAGESтаблицата. Нека да разгледаме картографиращия документ в Листинг 2.

Листинг 2. Проста хибернация на XML картографиране


  

Документът за картографиране казва Hibernate, че Messageкласът трябва да се поддържа в MESSAGESтаблицата, че свойството на идентификатора се преобразува в колона с име MESSAGE_ID, че свойството на текста се преобразува в колона с име MESSAGE_TEXTи че свойството с име nextMessageе асоциация с много към едно множественост, която се преобразува в колона с име NEXT_MESSAGE_ID. (Засега не се притеснявайте за другите подробности.)

Както можете да видите, XML документът не е труден за разбиране. Можете лесно да го пишете и поддържате на ръка. Който и метод да изберете, Hibernate има достатъчно информация, за да генерира напълно всички SQL изрази, които биха били необходими за вмъкване, актуализиране, изтриване и извличане на екземпляри от Messageкласа. Вече не е необходимо да пишете тези SQL изрази на ръка.

Забележка
Много разработчици на Java се оплакват от „ада на метаданните“, който съпътства разработката на J2EE. Някои предполагат преместване от XML метаданни обратно към обикновен Java код. Въпреки че приветстваме това предложение за някои проблеми, ORM представлява случай, когато метаданните, базирани на текст, наистина са необходими. Hibernate има разумни настройки по подразбиране, които свеждат до минимум въвеждането и дефиниция на зрял тип документ, която може да се използва за автоматично попълване или валидиране в редакторите. Можете дори да генерирате автоматично метаданни с различни инструменти.

Сега, нека променим първото си съобщение и докато сме готови, създайте ново съобщение, свързано с първото, както е показано в Листинг 3.

Листинг 3. Актуализиране на съобщение

Сесия на сесията = getSessionFactory (). OpenSession (); Транзакция tx = session.beginTransaction (); // 1 е генерираният идентификатор на първото съобщение Съобщение на съобщението = (Съобщение) session.load (Съобщение.клас, ново Long (1)); message.setText ("Поздрави землянин"); Съобщение nextMessage = ново съобщение ("Заведете ме при вашия лидер (моля)"); message.setNextMessage (nextMessage); tx.commit (); session.close ();

Този код извиква три SQL израза в рамките на една и съща транзакция:

изберете m.MESSAGE_ID, m.MESSAGE_TEXT, m.NEXT_MESSAGE_ID от MESSAGES m, където m.MESSAGE_ID = 1 вмъкнете в MESSAGES (MESSAGE_ID, MESSAGE_TEXT, NEXT_MESSAGE_ID) стойности (2, „Заведете ме при вашия лидер (моля)“, нула) актуализира MESSES задайте MESSAGE_TEXT = 'Поздрави землянин', NEXT_MESSAGE_ID = 2 където MESSAGE_ID = 1 

Notice how Hibernate detected the modification to the text and nextMessage properties of the first message and automatically updated the database. We've taken advantage of a Hibernate feature called automatic dirty checking: this feature saves us the effort of explicitly asking Hibernate to update the database when we modify the state of an object inside a transaction. Similarly, you can see that the new message was made persistent when a reference was created from the first message. This feature is called cascading save: it saves us the effort of explicitly making the new object persistent by calling save(), as long as it's reachable by an already persistent instance. Also notice that the ordering of the SQL statements isn't the same as the order in which we set property values. Hibernate uses a sophisticated algorithm to determine an efficient ordering that avoids database foreign key constraint violations but is still sufficiently predictable to the user. This feature is called transactional write-behind.

If we run "Hello World" again, it prints:

2 message(s) found: Greetings Earthling Take me to your leader (please) 

This is as far as we'll take the "Hello World" application. Now that we finally have some code under our belt, we'll take a step back and present an overview of Hibernate's main APIs.

Understanding the architecture

The programming interfaces are the first thing you have to learn about Hibernate in order to use it in the persistence layer of your application. A major objective of API design is to keep the interfaces between software components as narrow as possible. In practice, however, ORM APIs aren't especially small. Don't worry, though; you don't have to understand all the Hibernate interfaces at once. The figure below illustrates the roles of the most important Hibernate interfaces in the business and persistence layers.

We show the business layer above the persistence layer, since the business layer acts as a client of the persistence layer in a traditionally layered application. Note that some simple applications might not cleanly separate business logic from persistence logic; that's okay—it merely simplifies the diagram.

The Hibernate interfaces shown in the figure above may be approximately classified as follows:

  • Interfaces called by applications to perform basic CRUD (create/read/update/delete) and querying operations. These interfaces are the main point of dependency of application business/control logic on Hibernate. They include Session, Transaction, and Query.
  • Interfaces called by application infrastructure code to configure Hibernate, most importantly, the Configuration class.
  • Callback interfaces that allow the application to react to events occurring inside Hibernate, such as Interceptor, Lifecycle, and Validatable.
  • Interfaces that allow extension of Hibernate's powerful mapping functionality, such as UserType, CompositeUserType, and IdentifierGenerator. These interfaces are implemented by application infrastructure code (if necessary).

Hibernate makes use of existing Java APIs, including JDBC (Java Database Connectivity), Java Transaction API (JTA), and Java Naming and Directory Interface (JNDI). JDBC provides a rudimentary level of abstraction of functionality common to relational databases, allowing almost any database with a JDBC driver to be supported by Hibernate. JNDI and JTA allow Hibernate to be integrated with J2EE application servers.

В този раздел ние не обхващаме подробната семантика на Hibernate API методите, а само ролята на всеки от основните интерфейси. Можете да намерите повечето от тези интерфейси в пакета net.sf.hibernate. Нека да разгледаме накратко всеки интерфейс на свой ред.

Основните интерфейси

Петте основни интерфейса се използват почти във всяко приложение за хибернация. Използвайки тези интерфейси, можете да съхранявате и извличате постоянни обекти и да контролирате транзакции.

Интерфейс на сесията