Hibernate教程– ULTIMATE指南(PDF下载)
編者注:在本文中,我們提供了全面的Hibernate教程。 Hibernate ORM(簡(jiǎn)稱Hibernate)是一個(gè)對(duì)象關(guān)系映射框架,它有助于將面向?qū)ο蟮挠蚰P娃D(zhuǎn)換為傳統(tǒng)的關(guān)系數(shù)據(jù)庫(kù)。 Hibernate通過(guò)用高級(jí)對(duì)象處理功能代替直接與持久性相關(guān)的數(shù)據(jù)庫(kù)訪問(wèn),解決了對(duì)象關(guān)系阻抗不匹配的問(wèn)題。
Hibernate是目前最流行的Java框架之一。 由于這個(gè)原因,我們?cè)贘ava Code Geeks上提供了很多教程,其中大多數(shù)可以在此處找到。
現(xiàn)在,我們希望創(chuàng)建一個(gè)獨(dú)立的參考文章,以提供有關(guān)如何使用Hibernate的框架,并幫助您快速啟動(dòng)Hibernate應(yīng)用程序。 請(qǐng)享用!
目錄
1.簡(jiǎn)介 2.項(xiàng)目設(shè)置 3.基礎(chǔ)介紹
Hibernate是Java世界中最流行的對(duì)象/關(guān)系映射(ORM)框架之一。 它允許開(kāi)發(fā)人員將普通Java類的對(duì)象結(jié)構(gòu)映射到數(shù)據(jù)庫(kù)的關(guān)系結(jié)構(gòu)。 借助ORM框架,將對(duì)象實(shí)例中的數(shù)據(jù)從內(nèi)存中存儲(chǔ)到持久數(shù)據(jù)存儲(chǔ)并將它們加載回同一對(duì)象結(jié)構(gòu)的工作變得非常容易。
同時(shí),像Hibernate這樣的ORM解決方案旨在從用于存儲(chǔ)數(shù)據(jù)的特定產(chǎn)品中抽象出來(lái)。 這允許將相同的Java代碼與不同的數(shù)據(jù)庫(kù)產(chǎn)品一起使用,而無(wú)需編寫(xiě)處理受支持產(chǎn)品之間細(xì)微差異的代碼。
Hibernate還是JPA提供者,這意味著它實(shí)現(xiàn)了Java Persistence API(JPA) 。 JPA是用于將Java對(duì)象映射到關(guān)系數(shù)據(jù)庫(kù)表的獨(dú)立于供應(yīng)商的規(guī)范。 由于Ultimate系列的另一篇文章已經(jīng)介紹了JPA,因此本文重點(diǎn)介紹Hibernate,因此不使用JPA批注,而是使用Hibernate特定的配置文件。
Hibernate由三個(gè)不同的組件組成:
- 實(shí)體 :Hibernate映射到關(guān)系數(shù)據(jù)庫(kù)系統(tǒng)的表的類是簡(jiǎn)單的Java類(普通的舊Java對(duì)象)。
- 對(duì)象關(guān)系元數(shù)據(jù) :如何將實(shí)體映射到關(guān)系數(shù)據(jù)庫(kù)的信息由注釋(自Java 1.5開(kāi)始)或基于XML的舊式配置文件提供。 這些文件中的信息在運(yùn)行時(shí)用于執(zhí)行到數(shù)據(jù)存儲(chǔ)并返回到Java對(duì)象的映射。
- Hibernate查詢語(yǔ)言(HQL) :使用Hibernate時(shí),不必使用本機(jī)SQL編寫(xiě)發(fā)送到數(shù)據(jù)庫(kù)的查詢,而是可以使用Hibernate的查詢語(yǔ)言進(jìn)行指定。 由于這些查詢?cè)谶\(yùn)行時(shí)轉(zhuǎn)換為所選產(chǎn)品的當(dāng)前使用的方言,因此以HQL編寫(xiě)的查詢獨(dú)立于特定供應(yīng)商的SQL方言。
在本教程中,我們將研究框架的各個(gè)方面,并將開(kāi)發(fā)一個(gè)簡(jiǎn)單的Java SE應(yīng)用程序,該應(yīng)用程序可以在關(guān)系數(shù)據(jù)庫(kù)中存儲(chǔ)和檢索數(shù)據(jù)。 我們將使用以下庫(kù)/環(huán)境:
- Maven> = 3.0作為構(gòu)建環(huán)境
- 休眠(4.3.8.Final)
- H2作為關(guān)系數(shù)據(jù)庫(kù)(1.3.176)
項(xiàng)目設(shè)置
第一步,我們將在命令行上創(chuàng)建一個(gè)簡(jiǎn)單的maven項(xiàng)目:
mvn archetype:create -DgroupId=com.javacodegeeks.ultimate -DartifactId=hibernate此命令將在文件系統(tǒng)中創(chuàng)建以下結(jié)構(gòu):
|-- src | |-- main | | `-- java | | `-- com | | `-- javacodegeeks | | `-- ultimate | `-- test | | `-- java | | `-- com | | `-- javacodegeeks | | `-- ultimate `-- pom.xml我們的實(shí)現(xiàn)所依賴的庫(kù)以以下方式添加到pom.xml文件的dependencies部分:
1.3.1764.3.8.Finalcom.h2databaseh2${h2.version}org.hibernatehibernate-core${hibernate.version}為了更好地了解各個(gè)版本,我們將每個(gè)版本定義為一個(gè)maven屬性,稍后在“依賴項(xiàng)”部分中對(duì)其進(jìn)行引用。
3.基礎(chǔ)
SessionFactory和Session
現(xiàn)在,我們開(kāi)始實(shí)施第一個(gè)O / R映射。 讓我們從一個(gè)簡(jiǎn)單的類開(kāi)始,該類提供在應(yīng)用程序的main方法中調(diào)用的run()方法:
public class Main {private static final Logger LOGGER = Logger.getLogger("Hibernate-Tutorial");public static void main(String[] args) {Main main = new Main();main.run();}public void run() {SessionFactory sessionFactory = null;Session session = null;try {Configuration configuration = new Configuration();configuration.configure("hibernate.cfg.xml");ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();sessionFactory = configuration.buildSessionFactory(serviceRegistry);session = sessionFactory.openSession();persistPerson(session);} catch (Exception e) {LOGGER.log(Level.SEVERE, e.getMessage(), e);} finally {if (session != null) {session.close();}if (sessionFactory != null) {sessionFactory.close();}}}...run()方法創(chuàng)建org.hibernate.cfg.Configuration類的新實(shí)例,該實(shí)例隨后使用XML文件hibernate.cfg.xml進(jìn)行配置。 將配置文件放置在我們項(xiàng)目的src/main/resources文件夾中,使maven可以將其放置到創(chuàng)建的jar文件的根目錄中。 這樣,可以在運(yùn)行時(shí)在類路徑上找到文件。
第二步, run()方法構(gòu)造一個(gè)使用先前加載的配置的ServiceRegistry 。 現(xiàn)在可以將此ServiceRegistry的實(shí)例作為參數(shù)傳遞給Configuration buildSessionFactroy()方法。 現(xiàn)在,可以使用該SessionFactory獲取將實(shí)體存儲(chǔ)和加載到基礎(chǔ)數(shù)據(jù)存儲(chǔ)所需的會(huì)話。
配置文件hibernate.cfg.xml具有以下內(nèi)容:
org.h2.Driverjdbc:h2:~/hibernate;AUTOCOMMIT=OFF1org.hibernate.dialect.H2Dialectthreadorg.hibernate.cache.internal.NoCacheProvidertruetruecreate從上面的示例中可以看到,配置文件為會(huì)話工廠定義了一組屬性。 第一個(gè)屬性connection.driver_class指定應(yīng)使用的數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序。 在我們的示例中,這是H2數(shù)據(jù)庫(kù)的驅(qū)動(dòng)程序。 通過(guò)屬性connection.url ,可以指定JDBC-URL。 在我們的案例中,定義了我們要使用h2,并且H2存儲(chǔ)其數(shù)據(jù)的單個(gè)數(shù)據(jù)庫(kù)文件應(yīng)位于用戶的主目錄中,并應(yīng)命名為hibernate ( ~/hibernate )。 因?yàn)槲覀円约涸谑纠a中提交事務(wù),所以我們還定義了特定于H2的配置選項(xiàng)AUTOCOMMIT=OFF 。
接下來(lái),配置文件定義數(shù)據(jù)庫(kù)連接的用戶名和密碼,以及連接池的大小。 我們的示例應(yīng)用程序僅在一個(gè)線程中執(zhí)行代碼,因此我們可以將池大小設(shè)置為1。 如果應(yīng)用程序必須處理多個(gè)線程和多個(gè)用戶,則必須選擇適當(dāng)?shù)某卮笮 ?
屬性dialect指定一個(gè)Java類,該類執(zhí)行轉(zhuǎn)換為數(shù)據(jù)庫(kù)特定的SQL方言。
從3.1版開(kāi)始,Hibernate提供了一個(gè)名為SessionFactory.getCurrentSession()的方法,該方法使開(kāi)發(fā)人員可以獲得對(duì)當(dāng)前會(huì)話的引用。 使用配置屬性current_session_context_class可以配置Hibernate從中獲取此會(huì)話的位置。 此屬性的默認(rèn)值為jta ,表示Hibernate從基礎(chǔ)Java事務(wù)API(JTA)獲取會(huì)話。 由于本示例中未使用JTA,因此我們指示Hibernate使用配置值thread來(lái)存儲(chǔ)與當(dāng)前線程之間的會(huì)話,以及從當(dāng)前線程中檢索會(huì)話。
為了簡(jiǎn)單起見(jiàn),我們不想使用實(shí)體緩存。 因此,我們將屬性cache.provider_class為org.hibernate.cache.internal.NoCacheProvider 。
以下兩個(gè)選項(xiàng)告訴Hibernate將每個(gè)SQL語(yǔ)句輸出到控制臺(tái)并對(duì)其進(jìn)行格式化,以提高可讀性。 為了減輕開(kāi)發(fā)人員的負(fù)擔(dān),可以手動(dòng)創(chuàng)建模式,我們指示Hibernate使用選項(xiàng)hbm2ddl.auto設(shè)置為create以在啟動(dòng)期間創(chuàng)建所有表。
最后但并非最不重要的一點(diǎn)是,我們定義了一個(gè)映射資源文件,其中包含我們應(yīng)用程序的所有映射信息。 該文件的內(nèi)容將在以下各節(jié)中說(shuō)明。
如上所述,會(huì)話用于與數(shù)據(jù)存儲(chǔ)進(jìn)行通信,實(shí)際上代表了JDBC連接。 這意味著與連接的所有交互都通過(guò)會(huì)話完成。 它是單線程的,并為其迄今為止使用的所有對(duì)象提供緩存。 因此,應(yīng)用程序中的每個(gè)線程都應(yīng)使用自己從會(huì)話工廠獲取的會(huì)話。
與會(huì)話相反,會(huì)話工廠是線程安全的,并為定義映射提供了不可變的緩存。 對(duì)于每個(gè)數(shù)據(jù)庫(kù),只有一個(gè)會(huì)話工廠。 可選地,會(huì)話工廠除了可以提供會(huì)話的第一級(jí)緩存外,還可以提供應(yīng)用程序范圍的第二級(jí)緩存。
交易次數(shù)
在hibernate.cfg.xml配置文件中,我們已配置為自行管理事務(wù)。 因此,我們必須手動(dòng)啟動(dòng)和提交或回滾每個(gè)事務(wù)。 以下代碼演示了如何從會(huì)話中獲取新事務(wù)以及如何啟動(dòng)和提交該事務(wù):
try {Transaction transaction = session.getTransaction();transaction.begin();...transaction.commit(); } catch (Exception e) {if (session.getTransaction().isActive()) {session.getTransaction().rollback();}throw e; }第一步,我們調(diào)用getTransaction()來(lái)檢索新事務(wù)的引用。 通過(guò)在其上調(diào)用方法begin()立即開(kāi)始此事務(wù)。 如果以下代碼毫無(wú)例外地繼續(xù)進(jìn)行,則事務(wù)將被提交。 如果發(fā)生異常并且當(dāng)前事務(wù)處于活動(dòng)狀態(tài),則將回滾該事務(wù)。
由于上面顯示的代碼對(duì)于所有即將出現(xiàn)的示例都是相同的,因此不會(huì)一再重復(fù)以確切的形式進(jìn)行重復(fù)。 讀者可以將使用例如模板模式將代碼重構(gòu)為可重用形式的步驟留給讀者。
桌子
既然我們已經(jīng)了解了會(huì)話工廠,會(huì)話和事務(wù),那么現(xiàn)在就該開(kāi)始進(jìn)行一流的映射了。 為了簡(jiǎn)單起見(jiàn),我們選擇一個(gè)只有幾個(gè)簡(jiǎn)單屬性的簡(jiǎn)單類:
public class Person {private Long id;private String firstName;private String lastName;public Long getId() {return id;}public void setId(Long id) {this.id = id;}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;} }Person類帶有兩個(gè)用于存儲(chǔ)人名的屬性( firstName和lastName )。 字段id用于將對(duì)象的唯一標(biāo)識(shí)符存儲(chǔ)為長(zhǎng)值。 在本教程中,我們將使用映射文件而不是注釋,因此,我們將此類映射到表T_PERSON指定如下:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping package="hibernate.entity"><class name="Person" table="T_PERSON"><id name="id" column="ID"><generator class="native"/></id><property name="firstName" column="FIRST_NAME"/><property name="lastName" column="LAST_NAME"/></class> </hibernate-mapping>XML元素hibernate-mapping用于定義實(shí)體所在的包(此處為hibernate.entity )。 在此元素內(nèi),為每個(gè)類提供了一個(gè)class元素, class元素應(yīng)映射到數(shù)據(jù)庫(kù)中的表。
id元素指定包含唯一標(biāo)識(shí)符的類字段的名稱( name )以及該值存儲(chǔ)在( ID )中的列的名稱。 Hibernate通過(guò)其子元素generator了解如何為每個(gè)實(shí)體創(chuàng)建唯一標(biāo)識(shí)符。 在上面顯示的native值旁邊,Hibernate支持一長(zhǎng)串不同的策略。
本native策略只是為所使用的數(shù)據(jù)庫(kù)產(chǎn)品選擇最佳策略。 因此,該策略可以應(yīng)用于不同的產(chǎn)品。 其他可能的值例如是: sequence (使用數(shù)據(jù)庫(kù)中的序列), uuid (生成128位UUID)并進(jìn)行assigned (讓?xiě)?yīng)用程序自行分配值)。 除了預(yù)定義的策略之外,還可以通過(guò)實(shí)現(xiàn)org.hibernate.id.IdentifierGenerator接口來(lái)實(shí)現(xiàn)自定義策略。
使用XML元素property ,將firstName和lastName字段映射到FIRST_NAME和LAST_NAME列。 屬性name和column定義了類和列中的字段名稱。
以下代碼顯示了如何在數(shù)據(jù)庫(kù)中存儲(chǔ)人員的示例:
private void persistPerson(Session session) throws Exception {try {Transaction transaction = session.getTransaction();transaction.begin();Person person = new Person();person.setFirstName("Homer");person.setLastName("Simpson");session.save(person);transaction.commit();} catch (Exception e) {if (session.getTransaction().isActive()) {session.getTransaction().rollback();}throw e;} }在處理事務(wù)的代碼旁邊,它創(chuàng)建了Person類的新實(shí)例,并將兩個(gè)值分配給firstName和lastName字段。 最后,它通過(guò)調(diào)用會(huì)話的方法save()將人員存儲(chǔ)在數(shù)據(jù)庫(kù)中。
當(dāng)我們執(zhí)行上述代碼時(shí),控制臺(tái)上將打印以下SQL語(yǔ)句:
Hibernate: drop table T_PERSON if exists Hibernate: create table T_PERSON (ID bigint generated by default as identity,FIRST_NAME varchar(255),LAST_NAME varchar(255),primary key (ID)) Hibernate: insert intoT_PERSON(ID, firstName, lastName, ID_ID_CARD) values(null, ?, ?, ?)正如我們選擇讓Hibernate在啟動(dòng)時(shí)刪除并創(chuàng)建表一樣,打印出來(lái)的第一條語(yǔ)句是drop table和create table語(yǔ)句。 我們還可以看到表T_PERSON的三列ID , FIRST_NAME和LAST_NAME以及主鍵的定義(此處為ID )。
創(chuàng)建表之后,對(duì)session.save()的調(diào)用將向數(shù)據(jù)庫(kù)發(fā)出一條insert語(yǔ)句。 由于Hibernate在內(nèi)部使用PreparedStatement ,因此在控制臺(tái)上看不到這些值。 如果您還想查看綁定到PreparedStatement參數(shù)的值,則可以將記錄器org.hibernate.type的記錄級(jí)別設(shè)置為FINEST 。 這在具有以下內(nèi)容的名為logging.properties的文件中完成(例如,文件路徑可以作為系統(tǒng)屬性-Djava.util.logging.config.file=src/main/resources/logging.properties ) :
.handlers = java.util.logging.ConsoleHandler .level = INFOjava.util.logging.ConsoleHandler.level = ALL java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatterorg.hibernate.SQL.level = FINEST org.hibernate.type.level = FINEST將記錄器org.hibernate.SQL設(shè)置為與將Hibernate配置文件中的屬性show_sql設(shè)置為true具有相同的效果。
現(xiàn)在,您可以在控制臺(tái)上看到以下輸出以及實(shí)際值:
DEBUG: insert intoT_PERSON(ID, FIRST_NAME, LAST_NAME, ID_ID_CARD) values(null, ?, ?, ?) TRACE: binding parameter [1] as [VARCHAR] - [Homer] TRACE: binding parameter [2] as [VARCHAR] - [Simpson] TRACE: binding parameter [3] as [BIGINT] - [null]4.繼承
像Hibernate這樣的O / R映射解決方案的一個(gè)有趣的功能是繼承的使用。 用戶可以選擇如何將超類和子類映射到關(guān)系數(shù)據(jù)庫(kù)的表。 Hibernate支持以下映射策略:
- 每個(gè)類一個(gè)表:超類和子類都映射到同一張表。 附加列標(biāo)記該行是超類還是子類的實(shí)例,并且超類中不存在的字段保留為空。
- 聯(lián)接的子類 :此策略為每個(gè)類使用單獨(dú)的表,而子類的表僅存儲(chǔ)超類中不存在的字段。 要檢索子類實(shí)例的所有值,必須在兩個(gè)表之間執(zhí)行聯(lián)接。
- 每個(gè)類的表 :該策略還為每個(gè)類使用一個(gè)單獨(dú)的表,但在子類的表中還存儲(chǔ)了超類的字段。 通過(guò)這種策略,子類表中的一行包含所有值,并且為了檢索所有值,不需要join語(yǔ)句。
我們將要研究的方法是“每班單一表”方法。 作為人員的子類,我們選擇Geek類:
public class Geek extends Person {private String favouriteProgrammingLanguage;public String getFavouriteProgrammingLanguage() {return favouriteProgrammingLanguage;}public void setFavouriteProgrammingLanguage(String favouriteProgrammingLanguage) {this.favouriteProgrammingLanguage = favouriteProgrammingLanguage;} }該類擴(kuò)展了已知的Person類,并添加了一個(gè)名為favouriteProgrammingLanguage的附加字段。 此用例的映射文件如下所示:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping package="hibernate.entity"><class name="Person" table="T_PERSON"><id name="id" column="ID"><generator class="native"/></id><discriminator column="PERSON_TYPE" type="string"/><property name="firstName" column="FIRST_NAME"/><property name="lastName" column="LAST_NAME"/><subclass name="Geek" extends="Person"><property name="favouriteProgrammingLanguage" column="FAV_PROG_LANG"/></subclass></class> </hibernate-mapping>第一個(gè)區(qū)別是引入了discriminator列。 如上所述,此列存儲(chǔ)當(dāng)前實(shí)例屬于哪種類型的信息。 在我們的例子中,我們將其PERSON_TYPE ,為了更好的可讀性,字符串表示實(shí)際類型。 在這種情況下,默認(rèn)情況下,Hibernate僅采用類名。 為了節(jié)省存儲(chǔ)空間,也可以使用整數(shù)類型的列。
除了區(qū)分FAV_PROG_LANG之外,我們還添加了subclass元素,該元素告知Hibernate新的Java類Geek及其字段favouriteProgrammingLanguage ,該字段應(yīng)映射到FAV_PROG_LANG列。
以下示例代碼顯示了如何在數(shù)據(jù)庫(kù)中存儲(chǔ)Geek類型的實(shí)例:
session.getTransaction().begin(); Geek geek = new Geek(); geek.setFirstName("Gavin"); geek.setLastName("Coffee"); geek.setFavouriteProgrammingLanguage("Java"); session.save(geek); geek = new Geek(); geek.setFirstName("Thomas"); geek.setLastName("Micro"); geek.setFavouriteProgrammingLanguage("C#"); session.save(geek); geek = new Geek(); geek.setFirstName("Christian"); geek.setLastName("Cup"); geek.setFavouriteProgrammingLanguage("Java"); session.save(geek); session.getTransaction().commit();執(zhí)行上面顯示的代碼,將導(dǎo)致以下輸出:
Hibernate: drop table T_PERSON if exists Hibernate: create table T_PERSON (ID bigint generated by default as identity,PERSON_TYPE varchar(255) not null,FIRST_NAME varchar(255),LAST_NAME varchar(255),FAV_PROG_LANG varchar(255),primary key (ID)) Hibernate: insert intoT_PERSON(ID, FIRST_NAME, LAST_NAME, FAV_PROG_LANG, PERSON_TYPE) values(null, ?, ?, ?, 'hibernate.entity.Geek')與前面的示例相比,表T_PERSON現(xiàn)在包含兩個(gè)新列PERSON_TYPE和FAV_PROG_LANG 。 PERSON_TYPE列包含用于怪胎的值hibernate.entity.Geek 。
為了調(diào)查T_PERSON表的內(nèi)容,我們可以利用H2 jar文件中附帶的Shell應(yīng)用程序:
> java -cp h2-1.3.176.jar org.h2.tools.Shell -url jdbc:h2:~/hibernate ... sql> select * from t_person; ID | PERSON_TYPE | FIRST_NAME | LAST_NAME | FAV_PROG_LANG 1 | hibernate.entity.Person | Homer | Simpson | null 2 | hibernate.entity.Geek | Gavin | Coffee | Java 3 | hibernate.entity.Geek | Thomas | Micro | C# 4 | hibernate.entity.Geek | Christian | Cup | Java如上所述,列PERSON_TYPE存儲(chǔ)實(shí)例的類型,而列FAV_PROG_LANG包含超類Person實(shí)例的值null 。
以類似于下圖的方式更改映射定義,Hibernate將為超類和子類創(chuàng)建一個(gè)單獨(dú)的表:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping package="hibernate.entity"><class name="Person" table="T_PERSON"><id name="id" column="ID"><generator class="native"/></id><property name="firstName" column="FIRST_NAME"/><property name="lastName" column="LAST_NAME"/><joined-subclass name="Geek" table="T_GEEK"><key column="ID_PERSON"/><property name="favouriteProgrammingLanguage" column="FAV_PROG_LANG"/></joined-subclass></class> </hibernate-mapping>XML元素joined-subclass告訴Hibernate創(chuàng)建表T_GEEK的子類Geek與其他列ID_PERSON 。 這種額外的鍵列存儲(chǔ)外鍵表T_PERSON要想在每一行分配T_GEEK在其父行T_PERSON 。
使用上面顯示的Java代碼在數(shù)據(jù)庫(kù)中存儲(chǔ)一些怪胎,會(huì)在控制臺(tái)上產(chǎn)生以下輸出:
Hibernate: drop table T_GEEK if exists Hibernate: drop table T_PERSON if exists Hibernate: create table T_GEEK (ID_PERSON bigint not null,FAV_PROG_LANG varchar(255),primary key (ID_PERSON)) Hibernate: create table T_PERSON (ID bigint generated by default as identity,FIRST_NAME varchar(255),LAST_NAME varchar(255),primary key (ID)) Hibernate: alter table T_GEEK add constraint FK_p2ile8qooftvytnxnqtjkrbsa foreign key (ID_PERSON) references T_PERSON現(xiàn)在,Hibernate創(chuàng)建了兩個(gè)表而不是一個(gè)表,并為表T_GEEK定義了引用表T_PERSON的外鍵。 表T_GEEK由兩列組成: ID_PERSON用于引用相應(yīng)的人員)和FAV_PROG_LANG用于存儲(chǔ)喜歡的編程語(yǔ)言)。
現(xiàn)在,將極客存儲(chǔ)在數(shù)據(jù)庫(kù)中包括兩個(gè)插入語(yǔ)句:
Hibernate: insert intoT_PERSON(ID, FIRST_NAME, LAST_NAME, ID_ID_CARD) values(null, ?, ?, ?) Hibernate: insert intoT_GEEK(FAV_PROG_LANG, ID_PERSON) values(?, ?)第一條語(yǔ)句將新行插入到表T_PERSON ,而第二條語(yǔ)句將新行插入到表T_GEEK 。 這兩個(gè)表的內(nèi)容如下所示:
sql> select * from t_person; ID | FIRST_NAME | LAST_NAME 1 | Homer | Simpson 2 | Gavin | Coffee 3 | Thomas | Micro 4 | Christian | Cup sql> select * from t_geek; ID_PERSON | FAV_PROG_LANG 2 | Java 3 | C# 4 | Java顯然,表T_PERSON僅存儲(chǔ)超類的屬性,而表T_GEEK僅存儲(chǔ)子類的字段值。 列ID_PERSON引用父表中的相應(yīng)行。
正在研究的下一個(gè)策略是“每個(gè)班級(jí)分配表”。 與最后一種策略類似,該策略也為每個(gè)類創(chuàng)建了一個(gè)單獨(dú)的表,但是與之相反,子類的表也包含超類的所有列。 因此,此類表中的一行包含所有值,以構(gòu)造此類型的實(shí)例,而無(wú)需聯(lián)接父表中的其他數(shù)據(jù)。 在龐大的數(shù)據(jù)集上,這可以提高查詢的性能,因?yàn)槁?lián)接需要在父表中另外查找對(duì)應(yīng)的行。 這種額外的查找花費(fèi)了這種方法所避免的時(shí)間。
為了在上述用例中使用此策略,可以像下面這樣重寫(xiě)映射文件:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC"-//Hibernate/Hibernate Mapping DTD 3.0//EN""http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"><hibernate-mapping package="hibernate.entity"><class name="Person" table="T_PERSON"><id name="id" column="ID"><generator class="sequence"/></id><property name="firstName" column="FIRST_NAME"/><property name="lastName" column="LAST_NAME"/><union-subclass name="Geek" table="T_GEEK"><property name="favouriteProgrammingLanguage" column="FAV_PROG_LANG"/></union-subclass></class> </hibernate-mapping>XML元素union-subclass提供實(shí)體的名稱( Geek )以及單獨(dú)的表的名稱( T_GEEK )作為屬性。 與其他方法一樣,將字段favouriteProgrammingLanguage聲明為子類的屬性。
關(guān)于其他方法的另一個(gè)重要更改包含在定義id生成器的行中。 由于其他方法使用的是native生成器,該生成器在H2上退回到一個(gè)身份列,因此此方法需要一個(gè)ID生成器,該生成器創(chuàng)建兩個(gè)表( T_PERSON和T_GEEK )唯一的身份。
標(biāo)識(shí)列只是一種特殊的列,它會(huì)自動(dòng)為每一行創(chuàng)建一個(gè)新的ID。 但是,兩個(gè)表,我們也有兩個(gè)標(biāo)識(shí)列以及與其在該IDS T_PERSON表可以是相同的T_GEEK表。 這與僅通過(guò)讀取表T_GEEK一行就可以創(chuàng)建Geek類型的實(shí)體以及所有人和Geek的標(biāo)識(shí)符都是唯一的要求相矛盾。 因此,我們使用的是序列,而不是通過(guò)切換對(duì)所述值標(biāo)識(shí)列class從屬性native到sequence 。
現(xiàn)在,Hibernate創(chuàng)建的DDL語(yǔ)句如下所示:
Hibernate: drop table T_GEEK if exists Hibernate: drop table T_PERSON if exists Hibernate: drop sequence if exists hibernate_sequence Hibernate: create table T_GEEK (ID bigint not null,FIRST_NAME varchar(255),LAST_NAME varchar(255),FAV_PROG_LANG varchar(255),primary key (ID)) Hibernate: create table T_PERSON (ID bigint not null,FIRST_NAME varchar(255),LAST_NAME varchar(255),primary key (ID)) Hibernate: create sequence hibernate_sequence上面的輸出清楚地表明,表T_GEEK現(xiàn)在在FAV_PROG_LANG旁邊還包含超類的列( FIRST_NAME和LAST_NAME )。 該語(yǔ)句不會(huì)在兩個(gè)表之間創(chuàng)建外鍵。 還請(qǐng)注意,現(xiàn)在列ID不再是標(biāo)識(shí)列,而是創(chuàng)建了一個(gè)序列。
一個(gè)人和一個(gè)怪胎的插入向數(shù)據(jù)庫(kù)發(fā)出以下語(yǔ)句:
Hibernate: call next value for hibernate_sequence Hibernate: insert intoT_PERSON(FIRST_NAME, LAST_NAME, ID) values(?, ?, ?, ?) Hibernate: call next value for hibernate_sequence Hibernate: insert intoT_GEEK(FIRST_NAME, LAST_NAME, FAV_PROG_LANG, ID) values(?, ?, ?, ?, ?)對(duì)于一個(gè)人和一個(gè)極客,我們顯然只有兩個(gè)insert語(yǔ)句。 T_GEEK表完全由一次插入填充,并且包含Geek實(shí)例的所有值:
sql> select * from t_person; ID | FIRST_NAME | LAST_NAME 1 | Homer | Simpson sql> select * from t_geek; ID | FIRST_NAME | LAST_NAME | FAV_PROG_LANG 3 | Gavin | Coffee | Java 4 | Thomas | Micro | C# 5 | Christian | Cup | Java5.關(guān)系
到目前為止,我們看到的兩個(gè)表之間的唯一關(guān)系是“擴(kuò)展”表。 除了單純的繼承,Hibernate還可以映射基于列表的關(guān)系,其中一個(gè)實(shí)體具有另一個(gè)實(shí)體的實(shí)例列表。 區(qū)分以下類型的關(guān)系:
- 一對(duì)一 :這表示一種簡(jiǎn)單的關(guān)系,其中類型A的一個(gè)實(shí)體恰好屬于類型B的一個(gè)實(shí)體。
- 多對(duì)一 :顧名思義,這種關(guān)系涵蓋了類型A的實(shí)體具有許多類型B的子實(shí)體的情況。
- 多對(duì)多 :在這種情況下,可以有許多類型A的實(shí)體屬于許多類型B的實(shí)體。
為了更好地理解這些不同類型的關(guān)系,我們將在下面進(jìn)行研究。
一對(duì)一
作為“一對(duì)一”案例的示例,我們將以下類添加到我們的實(shí)體模型中:
public class IdCard {private Long id;private String idNumber;private Date issueDate;private boolean valid;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getIdNumber() {return idNumber;}public void setIdNumber(String idNumber) {this.idNumber = idNumber;}public Date getIssueDate() {return issueDate;}public void setIssueDate(Date issueDate) {this.issueDate = issueDate;}public boolean isValid() {return valid;}public void setValid(boolean valid) {this.valid = valid;} }身份卡作為內(nèi)部唯一標(biāo)識(shí)符以及外部idNumber,發(fā)行日期和布爾值標(biāo)志(指示該卡是否有效)。
在關(guān)系的另一側(cè),此人獲得一個(gè)名為idCard的新字段,該字段引用此人的卡:
public class Person {...private IdCard idCard;...public IdCard getIdCard() {return idCard;}public void setIdCard(IdCard idCard) {this.idCard = idCard;}要使用Hibernate特定的映射文件映射此關(guān)系,我們可以通過(guò)以下方式對(duì)其進(jìn)行更改:
<hibernate-mapping package="hibernate.entity"><class name="IdCard" table="T_ID_CARD"><id name="id" column="ID"><generator class="sequence"/></id></class><class name="Person" table="T_PERSON"><id name="id" column="ID"><generator class="sequence"/></id><property name="firstName" column="FIRST_NAME"/><property name="lastName" column="LAST_NAME"/><many-to-one name="idCard" column="ID_ID_CARD" unique="true"/><union-subclass name="Geek" table="T_GEEK"><property name="favouriteProgrammingLanguage" column="FAV_PROG_LANG"/></union-subclass></class> </hibernate-mapping>首先,我們?yōu)樾耤lass添加一個(gè)新的class元素,指定類的名稱及其對(duì)應(yīng)的表名(此處為T_ID_CARD )。 字段id成為唯一標(biāo)識(shí)符,應(yīng)使用序列值填充。
另一方面, Person映射現(xiàn)在包含新的XML元素many-to-one并且其屬性name引用存儲(chǔ)了對(duì)IdCard的引用的Person類的字段。 可選的屬性column使我們可以在表T_PERSON中指定鏈接到該人的身份證的外鍵列的確切名稱。 由于此關(guān)系應(yīng)為“一對(duì)一”類型,因此我們必須將unique屬性設(shè)置為true 。
執(zhí)行此配置將導(dǎo)致以下DDL語(yǔ)句(請(qǐng)注意,為了減少表的數(shù)量,我們已切換回“每個(gè)類只有一個(gè)表”的方法,在該方法中,對(duì)于超類和子類只有一個(gè)表):
Hibernate: drop table T_ID_CARD if exists Hibernate: drop table T_PERSON if exists Hibernate: drop sequence if exists hibernate_sequence Hibernate: create table T_ID_CARD (ID bigint not null,ID_NUMBER varchar(255),ISSUE_DATE timestamp,VALID boolean,primary key (ID)) Hibernate: create table T_PERSON (ID bigint not null,PERSON_TYPE varchar(255) not null,FIRST_NAME varchar(255),LAST_NAME varchar(255),ID_ID_CARD bigint,FAV_PROG_LANG varchar(255),primary key (ID)) Hibernate: alter table T_PERSON add constraint UK_96axqtck4kc0be4ancejxtu0p unique (ID_ID_CARD) Hibernate: alter table T_PERSON add constraint FK_96axqtck4kc0be4ancejxtu0p foreign key (ID_ID_CARD) references T_ID_CARD Hibernate: create sequence hibernate_sequence與前面的示例有關(guān)的變化是,表T_PERSON現(xiàn)在包含一個(gè)附加列ID_ID_CARD ,該列被定義為表T_ID_CARD外鍵。 表T_ID_CARD本身包含預(yù)期的三列ID_NUMBER , ISSUE_DATE和VALID 。
用于將人及其身份證插入的Java代碼如下所示:
Person person = new Person(); person.setFirstName("Homer"); person.setLastName("Simpson"); session.save(person); IdCard idCard = new IdCard(); idCard.setIdNumber("4711"); idCard.setIssueDate(new Date()); person.setIdCard(idCard); session.save(idCard);創(chuàng)建IdCard的實(shí)例很簡(jiǎn)單,也請(qǐng)注意,從Person到IdCard的引用設(shè)置在最后一行,但只有一行。 這兩個(gè)實(shí)例都傳遞給Hibernate的save()方法。
仔細(xì)看一下上面的代碼,可能會(huì)爭(zhēng)論為什么我們必須將兩個(gè)實(shí)例都傳遞給會(huì)話的save()方法。 這一點(diǎn)是合理的,因?yàn)镠ibernate允許定義在處理完整實(shí)體圖時(shí)應(yīng)“級(jí)聯(lián)”某些操作。 要啟用與IdCard的關(guān)系的級(jí)聯(lián),我們可以簡(jiǎn)單地將屬性cascade添加到映射文件中的many-to-one元素:
<many-to-one name="idCard" column="ID_ID_CARD" unique="true" cascade="all"/>使用值all告訴Hibernate級(jí)聯(lián)所有類型的操作。 由于這并非始終是處理實(shí)體之間關(guān)系的首選方法,因此也只能選擇特定的操作:
<many-to-one name="idCard" column="ID_ID_CARD" unique="true" cascade="save-update,refresh"/ ?- >上面的示例演示了如何配置映射,以便僅級(jí)聯(lián)對(duì)save() , saveOrUpdate()和refresh調(diào)用save()從數(shù)據(jù)庫(kù)中重新讀取給定對(duì)象的狀態(tài))。 例如,不會(huì)轉(zhuǎn)發(fā)對(duì)Hibernate方法delete()或lock()調(diào)用。
使用上述兩種配置中的一種,可以將存儲(chǔ)人員及其身份證的代碼重寫(xiě)為以下代碼:
Person person = new Person(); person.setFirstName("Homer"); person.setLastName("Simpson"); IdCard idCard = new IdCard(); idCard.setIdNumber("4711"); idCard.setIssueDate(new Date()); person.setIdCard(idCard); session.save(person);除了使用方法save() ,還可以在此用例中使用方法saveOrUpdate() 。 方法saveOrUpdate()的目的在于,它還可用于更新現(xiàn)有實(shí)體。 兩種實(shí)現(xiàn)之間的細(xì)微差別是, save()方法返回創(chuàng)建的新實(shí)體標(biāo)識(shí)符的事實(shí):
Long personId = (Long) session.save(person);例如,在編寫(xiě)服務(wù)器端代碼時(shí)這將很有幫助,該代碼應(yīng)將此標(biāo)識(shí)符返回給方法的調(diào)用者。 另一方面,方法update()不會(huì)返回標(biāo)識(shí)符,因?yàn)樗俣▽?shí)體已經(jīng)存儲(chǔ)到數(shù)據(jù)存儲(chǔ)中,因此必須具有標(biāo)識(shí)符。 嘗試更新沒(méi)有標(biāo)識(shí)符的實(shí)體將引發(fā)異常:
org.hibernate.TransientObjectException: The given object has a null identifier: ...因此,在需要省略確定實(shí)體是否已存儲(chǔ)的代碼的情況下, saveOrUpdate()有所幫助。
一對(duì)多
在O / R映射期間經(jīng)常出現(xiàn)的另一個(gè)關(guān)系是“一對(duì)多”關(guān)系。 在這種情況下,一組實(shí)體屬于另一種類型的一個(gè)實(shí)體。 為了對(duì)這種關(guān)系進(jìn)行建模,我們?cè)谀P椭刑砑恿薖hone類:
public class Phone {private Long id;private String number;private Person person;public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getNumber() {return number;}public void setNumber(String number) {this.number = number;}public Person getPerson() {return person;}public void setPerson(Person person) {this.person = person;} }通常,實(shí)體Phone具有一個(gè)內(nèi)部標(biāo)識(shí)符( id )和一個(gè)用于存儲(chǔ)實(shí)際電話號(hào)碼的字段。 現(xiàn)場(chǎng)person將參考保存回?fù)碛写穗娫挼娜藛T。 由于一個(gè)人可以擁有多個(gè)電話,因此我們向“ Person類添加一個(gè)“ Set以收集一個(gè)人的所有電話:
public class Person {...private Set phones = new HashSet();...public Set getPhones() {return phones;}public void setPhones(Set phones) {this.phones = phones;} }映射文件必須相應(yīng)地更新:
<hibernate-mapping package="hibernate.entity">...<class name="Phone" table="T_PHONE"><id name="id" column="ID"><generator class="sequence"/></id><property name="number" column="NUMBER"/><many-to-one name="person" column="ID_PERSON" unique="false" cascade="all"/></class><class name="Person" table="T_PERSON"><id name="id" column="ID"><generator class="sequence"/></id><discriminator column="PERSON_TYPE" type="string"/><property name="firstName" column="FIRST_NAME"/><property name="lastName" column="LAST_NAME"/><many-to-one name="idCard" column="ID_ID_CARD" unique="true" cascade="all"/><subclass name="Geek" extends="Person"><property name="favouriteProgrammingLanguage" column="FAV_PROG_LANG"/></subclass></class> </hibernate-mapping>上面的清單顯示了Phone類的映射的定義。 在使用序列和字段number生成的常規(guī)標(biāo)識(shí)符( id )旁邊,此定義還包含many-to-one元素。 與我們之前看到的“一對(duì)一”關(guān)系相反,將該屬性u(píng)nique設(shè)置為false 。 除此之外,在屬性column定義了外鍵列的名稱和屬性的值cascade休眠應(yīng)該如何級(jí)聯(lián)此關(guān)系操作。
執(zhí)行上述配置后,將打印出以下DDL語(yǔ)句:
... Hibernate: drop table T_PERSON if exists Hibernate: drop table T_PHONE if exists ... Hibernate: create table T_PERSON (ID bigint not null,PERSON_TYPE varchar(255) not null,FIRST_NAME varchar(255),LAST_NAME varchar(255),ID_ID_CARD bigint,FAV_PROG_LANG varchar(255),primary key (ID)) Hibernate: create table T_PHONE (ID bigint not null,NUMBER varchar(255),ID_PERSON bigint,primary key (ID)) ... Hibernate: alter table T_PHONE add constraint FK_dvxwd55q1bax99ibyw4oxa8iy foreign key (ID_PERSON) references T_PERSON ...接下來(lái)表T_PERSON休眠現(xiàn)在還創(chuàng)建新表T_PHONE其三個(gè)ID , NUMBER和ID_PERSON 。 因?yàn)楹笳吡写鎯?chǔ)的參考Person ,Hibernate也增加了一個(gè)外鍵約束表T_PHONE指向列ID表的T_PERSON 。
為了向現(xiàn)有人員之一添加電話號(hào)碼,我們首先加載特定人員,然后添加電話:
session.getTransaction().begin(); List resultList = session.createQuery("from Person as person where person.firstName = ?").setString(0, "Homer").list(); for (Person person : resultList) {Phone phone = new Phone();phone.setNumber("+49 1234 456789");session.persist(phone);person.getPhones().add(phone);phone.setPerson(person); } session.getTransaction().commit();此示例說(shuō)明如何使用Hibernate的查詢語(yǔ)言(HQL)從數(shù)據(jù)存儲(chǔ)中加載人員。 與SQL相似,此查詢由from和where子句組成。 未使用其SQL名稱引用FIRST_NAME列。 而是使用Java字段/屬性的名稱。 可以使用setString()方法將諸如名字之類的參數(shù)傳遞到查詢中。
在下面的代碼中,對(duì)找到的人(應(yīng)該是一個(gè))進(jìn)行迭代,并創(chuàng)建一個(gè)新的Phone實(shí)例,該實(shí)例將添加到找到的人的電話集中。 從電話返回到該人的鏈接也將在進(jìn)行交易之前設(shè)置。 執(zhí)行完此代碼后,數(shù)據(jù)庫(kù)如下所示:
sql> select * from t_person where first_name = 'Homer'; ID | PERSON_TYPE | FIRST_NAME | LAST_NAME | ID_ID_CARD | FAV_PROG_LANG 1 | hibernate.entity.Person | Homer | Simpson | 2 | nullsql> select * from t_phone; ID | NUMBER | ID_PERSON 6 | +49 1234 456789 | 1結(jié)果集的兩個(gè)選擇語(yǔ)句以上表明,在該行的T_PHONE被連接到在所選擇的行T_PERSON因?yàn)樗谒牡谝涣忻昂神R”的人的ID ID_ID_PERSON 。
多對(duì)多
下一個(gè)有趣的關(guān)系是“多對(duì)多”關(guān)系。 在這種情況下,許多類型A的實(shí)體可以屬于許多類型B的實(shí)體,反之亦然。 在實(shí)踐中,例如極客和項(xiàng)目就是這種情況。 一個(gè)極客可以在多個(gè)項(xiàng)目中(同時(shí)或順序)工作,而一個(gè)項(xiàng)目可以包含一個(gè)以上的極客。 因此,引入了新的實(shí)體Project :
public class Project {private Long id;private String title;private Set geeks = new HashSet();public Long getId() {return id;}public void setId(Long id) {this.id = id;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public Set getGeeks() {return geeks;}public void setGeeks(Set geeks) {this.geeks = geeks;} }它旁邊是標(biāo)題的標(biāo)識(shí)符( id )和一組怪胎。 在關(guān)系的另一側(cè), Geek類具有一組項(xiàng)目:
public class Geek extends Person {private String favouriteProgrammingLanguage;private Set projects = new HashSet();public String getFavouriteProgrammingLanguage() {return favouriteProgrammingLanguage;}public void setFavouriteProgrammingLanguage(String favouriteProgrammingLanguage) {this.favouriteProgrammingLanguage = favouriteProgrammingLanguage;}public Set getProjects() {return projects;}public void setProjects(Set projects) {this.projects = projects;} }為了支持這種關(guān)系,必須以以下方式更改映射文件:
<hibernate-mapping package="hibernate.entity">...<class name="Project" table="T_PROJECT"><id name="id" column="ID"><generator class="sequence"/></id><property name="title" column="TITLE"/><set name="geeks" table="T_GEEKS_PROJECTS"><key column="ID_PROJECT"/><many-to-many column="ID_GEEK" class="Geek"/></set></class><class name="Person" table="T_PERSON"><id name="id" column="ID"><generator class="sequence"/></id><discriminator column="PERSON_TYPE" type="string"/><property name="firstName" column="FIRST_NAME"/><property name="lastName" column="LAST_NAME"/><many-to-one name="idCard" column="ID_ID_CARD" unique="true" cascade="all"/><subclass name="Geek" extends="Person"><property name="favouriteProgrammingLanguage" column="FAV_PROG_LANG"/><set name="projects" inverse="true"><key column="ID_GEEK"/><many-to-many column="ID_PROJECT" class="Project"/></set></subclass></class> </hibernate-mapping>首先,我們看到映射到表T_PROJECT的新類Project 。 其唯一標(biāo)識(shí)符存儲(chǔ)在字段id ,其字段title存儲(chǔ)在TITLE列中。 XML元素set定義了映射的一側(cè): geeks集內(nèi)的項(xiàng)目應(yīng)存儲(chǔ)在名為T_GEEKS_PROJECTS的單獨(dú)表中,該表的列ID_PROJECT和ID_GEEK 。 在關(guān)系的另一側(cè),在Geek的subclass內(nèi)set的XML元素定義了逆關(guān)系( inverse="true" )。 在這方面, Geek類中的字段稱為projects ,而引用類中的字段為Project 。
創(chuàng)建表的結(jié)果語(yǔ)句如下所示:
... Hibernate: drop table T_GEEKS_PROJECTS if exists Hibernate: drop table T_PROJECT if exists ... Hibernate: create table T_GEEKS_PROJECTS (ID_PROJECT bigint not null,ID_GEEK bigint not null,primary key (ID_PROJECT, ID_GEEK)) Hibernate: create table T_PROJECT (ID bigint not null,TITLE varchar(255),primary key (ID)) ... Hibernate: alter table T_GEEKS_PROJECTS add constraint FK_2kp3f3tq46ckky02pshvjngaq foreign key (ID_GEEK) references T_PERSON Hibernate: alter table T_GEEKS_PROJECTS add constraint FK_36tafu1nw9j5o51d21xm5rqne foreign key (ID_PROJECT) references T_PROJECT ...這些語(yǔ)句創(chuàng)建新表T_PROJECT以及T_GEEKS_PROJECTS 。 表T_PROJECT由列的ID和TITLE由此在列中的值ID的新的表被稱為T_GEEKS_PROJECTS在其列ID_PROJECT 。 該表上的第二個(gè)外鍵指向T_PERSON的主鍵。
為了將可以使用Java編程的極客項(xiàng)目插入數(shù)據(jù)存儲(chǔ)區(qū),可以使用以下代碼:
session.getTransaction().begin(); List resultList = session.createQuery("from Geek as geek where geek.favouriteProgrammingLanguage = ?").setString(0, "Java").list(); Project project = new Project(); project.setTitle("Java Project"); for (Geek geek : resultList) {project.getGeeks().add(geek);geek.getProjects().add(project); } session.save(project); session.getTransaction().commit();初始查詢將選擇所有將“ Java”作為其最喜歡的編程語(yǔ)言的怪胎。 然后,將創(chuàng)建一個(gè)新的Project實(shí)例,并將查詢結(jié)果集中的所有怪胎添加到該項(xiàng)目的怪胎集中。 在關(guān)系的另一側(cè),將項(xiàng)目添加到極客項(xiàng)目集。 最后,項(xiàng)目被存儲(chǔ),事務(wù)被提交。
執(zhí)行此代碼后,數(shù)據(jù)庫(kù)如下所示:
sql> select * from t_person; ID | PERSON_TYPE | FIRST_NAME | LAST_NAME | ID_ID_CARD | FAV_PROG_LANG 1 | hibernate.entity.Person | Homer | Simpson | 2 | null 3 | hibernate.entity.Geek | Gavin | Coffee | null | Java 4 | hibernate.entity.Geek | Thomas | Micro | null | C# 5 | hibernate.entity.Geek | Christian | Cup | null | Javasql> select * from t_project; ID | TITLE 7 | Java Projectsql> select * from t_geeks_projects; ID_PROJECT | ID_GEEK 7 | 5 7 | 3第一個(gè)選擇表明,只有id為3和5的兩個(gè)極客表示Java是他們最喜歡的編程語(yǔ)言。 因此,標(biāo)題為“ Java Project”(ID:7)的項(xiàng)目由ID為3和5(最后選擇語(yǔ)句)的兩個(gè)怪胎組成。
零件
面向?qū)ο蟮脑O(shè)計(jì)規(guī)則建議將常用字段提取到單獨(dú)的類中。 例如,上面的Project類仍然錯(cuò)過(guò)開(kāi)始和結(jié)束日期。 但是由于這樣的時(shí)間段也可以用于其他實(shí)體,因此我們可以創(chuàng)建一個(gè)名為Period的新類,該類封裝了兩個(gè)字段startDate和endDate :
public class Period {private Date startDate;private Date endDate;public Date getStartDate() {return startDate;}public void setStartDate(Date startDate) {this.startDate = startDate;}public Date getEndDate() {return endDate;}public void setEndDate(Date endDate) {this.endDate = endDate;} }public class Project {...private Period period;...public Period getPeriod() {return period;}public void setPeriod(Period period) {this.period = period;} }但是我們不希望Hibernate為該期間創(chuàng)建一個(gè)單獨(dú)的表,因?yàn)槊總€(gè)Project應(yīng)該僅具有一個(gè)開(kāi)始日期和結(jié)束日期,并且我們希望避開(kāi)其他聯(lián)接。 在這種情況下,Hibernate可以將嵌入式類Period的兩個(gè)字段映射到與Project類相同的表:
<hibernate-mapping package="hibernate.entity">...<class name="Project" table="T_PROJECT"><id name="id" column="ID"><generator class="sequence"/></id><property name="title" column="TITLE"/><set name="geeks" table="T_GEEKS_PROJECTS"><key column="ID_PROJECT"/><many-to-many column="ID_GEEK" class="Geek"/></set><component name="period"><property name="startDate" column="START_DATE"/><property name="endDate" column="END_DATE"/></component></class>... </hibernate-mapping>如何將此嵌入式類映射到表T_PROJECT字段的T_PROJECT是使用component元素,并在Project類中為name屬性提供字段的name 。 然后將Period類的兩個(gè)字段聲明為component屬性。
這將導(dǎo)致以下DDL語(yǔ)句:
... Hibernate: create table T_PROJECT (ID bigint not null,TITLE varchar(255),START_DATE timestamp,END_DATE timestamp,primary key (ID)) ...盡管START_DATE和END_DATE的字段位于單獨(dú)的類中,但是Hibernate將它們添加到表T_PROJECT 。 以下代碼創(chuàng)建一個(gè)新項(xiàng)目并為其添加一個(gè)句點(diǎn):
Project project = new Project(); project.setTitle("Java Project"); Period period = new Period(); period.setStartDate(new Date()); project.setPeriod(period); ... session.save(project);這將導(dǎo)致以下數(shù)據(jù)情況:
sql> select * from t_project; ID | TITLE | START_DATE | END_DATE 7 | Java Project | 2015-01-01 19:45:12.274 | null要將期間與項(xiàng)目一起加載,無(wú)需編寫(xiě)其他代碼,該期間將自動(dòng)加載并初始化:
List projects = session.createQuery("from Project as p where p.title = ?").setString(0, "Java Project").list(); for (Project project : projects) {System.out.println("Project: " + project.getTitle() + " starts at " + project.getPeriod().getStartDate()); }萬(wàn)一數(shù)據(jù)庫(kù)中該期間的所有字段都已設(shè)置為NULL ,Hibernate還將對(duì)Period的引用設(shè)置為null 。
6.用戶定義的數(shù)據(jù)類型
例如,在使用舊數(shù)據(jù)庫(kù)時(shí),某些列的建模方式可能不同于Hibernate映射它們的方式。 例如, Boolean數(shù)據(jù)類型在H2數(shù)據(jù)庫(kù)上映射為boolean類型。 如果原始開(kāi)發(fā)團(tuán)隊(duì)決定使用具有值“ 0”和“ 1”的字符串映射布爾值,則Hibernate允許實(shí)現(xiàn)用于映射的用戶定義類型。
Hibernate定義了必須實(shí)現(xiàn)的接口org.hibernate.usertype.UserType :
public interface UserType {int[] sqlTypes();Class returnedClass();boolean equals(Object var1, Object var2) throws HibernateException;int hashCode(Object var1) throws HibernateException;Object nullSafeGet(ResultSet var1, String[] var2, SessionImplementor var3, Object var4) throws HibernateException, SQLException;void nullSafeSet(PreparedStatement var1, Object var2, int var3, SessionImplementor var4) throws HibernateException, SQLException;Object deepCopy(Object var1) throws HibernateException;boolean isMutable();Serializable disassemble(Object var1) throws HibernateException;Object assemble(Serializable var1, Object var2) throws HibernateException;Object replace(Object var1, Object var2, Object var3) throws HibernateException; }下面顯示了不是針對(duì)我們的問(wèn)題的那些方法的簡(jiǎn)單實(shí)現(xiàn):
@Override public boolean equals(Object x, Object y) throws HibernateException {if (x == null) {return y == null;} else {return y != null && x.equals(y);} }@Override public int hashCode(Object o) throws HibernateException {return o.hashCode(); }@Override public Object deepCopy(Object o) throws HibernateException {return o; }@Override public boolean isMutable() {return false; }@Override public Serializable disassemble(Object o) throws HibernateException {return (Serializable) o; }@Override public Object assemble(Serializable cached, Object owner) throws HibernateException {return cached; }@Override public Object replace(Object original, Object target, Object owner) throws HibernateException {return original; }UserType有趣的部分是方法nullSafeGet()和nullSafeSet() :
@Override public Object nullSafeGet(ResultSet resultSet, String[] strings, SessionImplementor sessionImplementor, Object o) throws HibernateException, SQLException {String str = (String) StringType.INSTANCE.nullSafeGet(resultSet, strings[0], sessionImplementor, o);if ("1".equals(str)) {return Boolean.TRUE;}return Boolean.FALSE; }@Override public void nullSafeSet(PreparedStatement preparedStatement, Object value, int i, SessionImplementor sessionImplementor) throws HibernateException, SQLException {String valueToStore = "0";if (value != null) {Boolean booleanValue = (Boolean) value;if (booleanValue.equals(Boolean.TRUE)) {valueToStore = "1";}}StringType.INSTANCE.nullSafeSet(preparedStatement,valueToStore, i, sessionImplementor); }nullSafeGet()方法使用Hibernate的StringType實(shí)現(xiàn)從基礎(chǔ)查詢的ResultSet中提取布爾值的字符串表示ResultSet 。 如果返回的字符串等于“ 1”,則該方法返回“ true”,否則返回“ false”。
之前的insert可以被執(zhí)行的語(yǔ)句,在作為參數(shù)傳遞的布爾值value必須被“解碼”到任意字符串“1”或串“0”。 然后,方法nullSafeSet()使用Hibernate的StringType實(shí)現(xiàn)在PreparedStatement上設(shè)置此字符串值。
最后,我們必須告訴Hibernate從nullSafeGet()返回nullSafeGet()對(duì)象,以及該類型應(yīng)使用哪種列:
@Override public int[] sqlTypes() {return new int[]{ Types.VARCHAR }; }@Override public Class returnedClass() {return Boolean.class; }實(shí)現(xiàn)了UserType接口之后,現(xiàn)在可以將此類的實(shí)例提供給Configuration :
Configuration configuration = new Configuration(); configuration.configure("hibernate.cfg.xml"); configuration.registerTypeOverride(new MyBooleanType(), new String[]{"MyBooleanType"}); ...MyBooleanType是我們對(duì)UserType接口的實(shí)現(xiàn),而String數(shù)組定義了如何在映射文件中引用此類型:
<hibernate-mapping package="hibernate.entity"><class name="IdCard" table="T_ID_CARD"><id name="id" column="ID"><generator class="sequence"/></id><property name="idNumber" column="ID_NUMBER"/><property name="issueDate" column="ISSUE_DATE"/><property name="valid" column="VALID" type="MyBooleanType"/></class>... </hibernate-mapping>從上面的代碼片段可以看出,新類型“ MyBooleanType”用于表T_ID_CARD的布爾屬性:
sql> select * from t_id_card; ID | ID_NUMBER | ISSUE_DATE | VALID 2 | 4711 | 2015-03-27 11:49:57.533 | 17.攔截器
一個(gè)項(xiàng)目可能會(huì)要求對(duì)每個(gè)實(shí)體/表的創(chuàng)建和最后更新的時(shí)間戳進(jìn)行跟蹤。 在所有插入和更新操作中為每個(gè)實(shí)體設(shè)置這兩個(gè)值是一項(xiàng)相當(dāng)繁瑣的任務(wù)。 因此,Hibernate提供了實(shí)現(xiàn)在執(zhí)行插入或更新操作之前調(diào)用的攔截器的功能。 This way the code to set the creation and update timestamp can be extracted to a single place in the code base and does not have to be copied to all locations where it would be necessary.
As an example we are going to implement an audit trail that tracks the creation and update of the Project entity. This can be done by extending the class EmptyInterceptor :
public class AuditInterceptor extends EmptyInterceptor {@Overridepublic boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {if (entity instanceof Auditable) {for ( int i=0; i < propertyNames.length; i++ ) {if ( "created".equals( propertyNames[i] ) ) {state[i] = new Date();return true;}}return true;}return false;}@Overridepublic boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {if (entity instanceof Auditable) {for ( int i=0; i < propertyNames.length; i++ ) {if ( "lastUpdate".equals( propertyNames[i] ) ) {currentState[i] = new Date();return true;}}return true;}return false;} }As the class EmptyInterceptor already implements all methods defined in the interface Interceptor , we only have to override the methods onSave() and onFlushDirty() . In order to easily find all entities that have a field created and lastUpdate we extract the getter and setter methods for these entities into a separate interface called Auditable :
public interface Auditable {Date getCreated();void setCreated(Date created);Date getLastUpdate();void setLastUpdate(Date lastUpdate); }With this interface it is easy to check whether the instance passed into the interceptor is of type Auditable . Unfortunately we cannot modify the entity directly through the getter and setter methods but we have to use the two arrays propertyNames and state . In the array propertyNames we have to find the property created ( lastUpdate ) and use its index to set the corresponding element in the array state ( currentState ).
Without the appropriate property definitions in the mapping file Hibernate will not create the columns in the tables. Hence the mapping file has to be updated:
<hibernate-mapping>...<class name="Project" table="T_PROJECT"><id name="id" column="ID"><generator class="sequence"/></id><property name="title" column="TITLE"/><set name="geeks" table="T_GEEKS_PROJECTS"><key column="ID_PROJECT"/><many-to-many column="ID_GEEK" class="Geek"/></set><component name="period"><property name="startDate" column="START_DATE"/><property name="endDate" column="END_DATE"/></component><property name="created" column="CREATED" type="timestamp"/><property name="lastUpdate" column="LAST_UPDATE" type="timestamp"/></class>... </hibernate-mapping>As can be seen from the snippet above, the two new properties created and lastUpdate are of type timestamp :
sql> select * from t_person; ID | PERSON_TYPE | FIRST_NAME | LAST_NAME | CREATED | LAST_UPDATE | ID_ID_CARD | FAV_PROG_LANG 1 | hibernate.entity.Person | Homer | Simpson | 2015-01-01 19:45:42.493 | null | 2 | null 3 | hibernate.entity.Geek | Gavin | Coffee | 2015-01-01 19:45:42.506 | null | null | Java 4 | hibernate.entity.Geek | Thomas | Micro | 2015-01-01 19:45:42.507 | null | null | C# 5 | hibernate.entity.Geek | Christian | Cup | 2015-01-01 19:45:42.507 | null | null | Java8. Download Hibernate Tutorial Source Code
This was a Hibernate Tutorial.
下載You can download the full source code of this tutorial here: hibernate-tutorial-sources .
翻譯自: https://www.javacodegeeks.com/2015/03/hibernate-tutorial.html
總結(jié)
以上是生活随笔為你收集整理的Hibernate教程– ULTIMATE指南(PDF下载)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux进程内存占用大分析(linux
- 下一篇: 使用Junit测试名称