Hibernate教程– ULTIMATE指南(PDF下载)
編者注:在本文中,我們提供了全面的Hibernate教程。 Hibernate ORM(簡稱Hibernate)是一個對象關系映射框架,它有助于將面向對象的域模型轉換為傳統的關系數據庫。 Hibernate通過用高級對象處理功能代替直接與持久性相關的數據庫訪問,解決了對象關系阻抗不匹配的問題。
Hibernate是目前最流行的Java框架之一。 由于這個原因,我們在Java Code Geeks上提供了很多教程,其中大多數可以在此處找到。
現在,我們希望創建一個獨立的參考文章,以提供有關如何使用Hibernate的框架,并幫助您快速啟動Hibernate應用程序。 請享用!
目錄
1.簡介 2.項目設置 3.基礎介紹
Hibernate是Java世界中最流行的對象/關系映射(ORM)框架之一。 它允許開發人員將普通Java類的對象結構映射到數據庫的關系結構。 借助ORM框架,將對象實例中的數據從內存中存儲到持久數據存儲并將它們加載回同一對象結構的工作變得非常容易。
同時,像Hibernate這樣的ORM解決方案旨在從用于存儲數據的特定產品中抽象出來。 這允許將相同的Java代碼與不同的數據庫產品一起使用,而無需編寫處理受支持產品之間細微差異的代碼。
Hibernate還是JPA提供者,這意味著它實現了Java Persistence API(JPA) 。 JPA是用于將Java對象映射到關系數據庫表的獨立于供應商的規范。 由于Ultimate系列的另一篇文章已經介紹了JPA,因此本文重點介紹Hibernate,因此不使用JPA批注,而是使用Hibernate特定的配置文件。
Hibernate由三個不同的組件組成:
- 實體 :Hibernate映射到關系數據庫系統的表的類是簡單的Java類(普通的舊Java對象)。
- 對象關系元數據 :如何將實體映射到關系數據庫的信息由注釋(自Java 1.5開始)或基于XML的舊式配置文件提供。 這些文件中的信息在運行時用于執行到數據存儲并返回到Java對象的映射。
- Hibernate查詢語言(HQL) :使用Hibernate時,不必使用本機SQL編寫發送到數據庫的查詢,而是可以使用Hibernate的查詢語言進行指定。 由于這些查詢在運行時轉換為所選產品的當前使用的方言,因此以HQL編寫的查詢獨立于特定供應商的SQL方言。
在本教程中,我們將研究框架的各個方面,并將開發一個簡單的Java SE應用程序,該應用程序可以在關系數據庫中存儲和檢索數據。 我們將使用以下庫/環境:
- Maven> = 3.0作為構建環境
- 休眠(4.3.8.Final)
- H2作為關系數據庫(1.3.176)
項目設置
第一步,我們將在命令行上創建一個簡單的maven項目:
mvn archetype:create -DgroupId=com.javacodegeeks.ultimate -DartifactId=hibernate此命令將在文件系統中創建以下結構:
|-- src | |-- main | | `-- java | | `-- com | | `-- javacodegeeks | | `-- ultimate | `-- test | | `-- java | | `-- com | | `-- javacodegeeks | | `-- ultimate `-- pom.xml我們的實現所依賴的庫以以下方式添加到pom.xml文件的dependencies部分:
1.3.1764.3.8.Finalcom.h2databaseh2${h2.version}org.hibernatehibernate-core${hibernate.version}為了更好地了解各個版本,我們將每個版本定義為一個maven屬性,稍后在“依賴項”部分中對其進行引用。
3.基礎
SessionFactory和Session
現在,我們開始實施第一個O / R映射。 讓我們從一個簡單的類開始,該類提供在應用程序的main方法中調用的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()方法創建org.hibernate.cfg.Configuration類的新實例,該實例隨后使用XML文件hibernate.cfg.xml進行配置。 將配置文件放置在我們項目的src/main/resources文件夾中,使maven可以將其放置到創建的jar文件的根目錄中。 這樣,可以在運行時在類路徑上找到文件。
第二步, run()方法構造一個使用先前加載的配置的ServiceRegistry 。 現在可以將此ServiceRegistry的實例作為參數傳遞給Configuration buildSessionFactroy()方法。 現在,可以使用該SessionFactory獲取將實體存儲和加載到基礎數據存儲所需的會話。
配置文件hibernate.cfg.xml具有以下內容:
org.h2.Driverjdbc:h2:~/hibernate;AUTOCOMMIT=OFF1org.hibernate.dialect.H2Dialectthreadorg.hibernate.cache.internal.NoCacheProvidertruetruecreate從上面的示例中可以看到,配置文件為會話工廠定義了一組屬性。 第一個屬性connection.driver_class指定應使用的數據庫驅動程序。 在我們的示例中,這是H2數據庫的驅動程序。 通過屬性connection.url ,可以指定JDBC-URL。 在我們的案例中,定義了我們要使用h2,并且H2存儲其數據的單個數據庫文件應位于用戶的主目錄中,并應命名為hibernate ( ~/hibernate )。 因為我們要自己在示例代碼中提交事務,所以我們還定義了特定于H2的配置選項AUTOCOMMIT=OFF 。
接下來,配置文件定義數據庫連接的用戶名和密碼,以及連接池的大小。 我們的示例應用程序僅在一個線程中執行代碼,因此我們可以將池大小設置為1。 如果應用程序必須處理多個線程和多個用戶,則必須選擇適當的池大小。
屬性dialect指定一個Java類,該類執行轉換為數據庫特定的SQL方言。
從3.1版開始,Hibernate提供了一個名為SessionFactory.getCurrentSession()的方法,該方法使開發人員可以獲得對當前會話的引用。 使用配置屬性current_session_context_class可以配置Hibernate從中獲取此會話的位置。 此屬性的默認值為jta ,表示Hibernate從基礎Java事務API(JTA)獲取會話。 由于本示例中未使用JTA,因此我們指示Hibernate使用配置值thread來存儲與當前線程之間的會話,以及從當前線程中檢索會話。
為了簡單起見,我們不想使用實體緩存。 因此,我們將屬性cache.provider_class為org.hibernate.cache.internal.NoCacheProvider 。
以下兩個選項告訴Hibernate將每個SQL語句輸出到控制臺并對其進行格式化,以提高可讀性。 為了減輕開發人員的負擔,可以手動創建模式,我們指示Hibernate使用選項hbm2ddl.auto設置為create以在啟動期間創建所有表。
最后但并非最不重要的一點是,我們定義了一個映射資源文件,其中包含我們應用程序的所有映射信息。 該文件的內容將在以下各節中說明。
如上所述,會話用于與數據存儲進行通信,實際上代表了JDBC連接。 這意味著與連接的所有交互都通過會話完成。 它是單線程的,并為其迄今為止使用的所有對象提供緩存。 因此,應用程序中的每個線程都應使用自己從會話工廠獲取的會話。
與會話相反,會話工廠是線程安全的,并為定義映射提供了不可變的緩存。 對于每個數據庫,只有一個會話工廠。 可選地,會話工廠除了可以提供會話的第一級緩存外,還可以提供應用程序范圍的第二級緩存。
交易次數
在hibernate.cfg.xml配置文件中,我們已配置為自行管理事務。 因此,我們必須手動啟動和提交或回滾每個事務。 以下代碼演示了如何從會話中獲取新事務以及如何啟動和提交該事務:
try {Transaction transaction = session.getTransaction();transaction.begin();...transaction.commit(); } catch (Exception e) {if (session.getTransaction().isActive()) {session.getTransaction().rollback();}throw e; }第一步,我們調用getTransaction()來檢索新事務的引用。 通過在其上調用方法begin()立即開始此事務。 如果以下代碼毫無例外地繼續進行,則事務將被提交。 如果發生異常并且當前事務處于活動狀態,則將回滾該事務。
由于上面顯示的代碼對于所有即將出現的示例都是相同的,因此不會一再重復以確切的形式進行重復。 讀者可以將使用例如模板模式將代碼重構為可重用形式的步驟留給讀者。
桌子
既然我們已經了解了會話工廠,會話和事務,那么現在就該開始進行一流的映射了。 為了簡單起見,我們選擇一個只有幾個簡單屬性的簡單類:
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類帶有兩個用于存儲人名的屬性( firstName和lastName )。 字段id用于將對象的唯一標識符存儲為長值。 在本教程中,我們將使用映射文件而不是注釋,因此,我們將此類映射到表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用于定義實體所在的包(此處為hibernate.entity )。 在此元素內,為每個類提供了一個class元素, class元素應映射到數據庫中的表。
id元素指定包含唯一標識符的類字段的名稱( name )以及該值存儲在( ID )中的列的名稱。 Hibernate通過其子元素generator了解如何為每個實體創建唯一標識符。 在上面顯示的native值旁邊,Hibernate支持一長串不同的策略。
本native策略只是為所使用的數據庫產品選擇最佳策略。 因此,該策略可以應用于不同的產品。 其他可能的值例如是: sequence (使用數據庫中的序列), uuid (生成128位UUID)并進行assigned (讓應用程序自行分配值)。 除了預定義的策略之外,還可以通過實現org.hibernate.id.IdentifierGenerator接口來實現自定義策略。
使用XML元素property ,將firstName和lastName字段映射到FIRST_NAME和LAST_NAME列。 屬性name和column定義了類和列中的字段名稱。
以下代碼顯示了如何在數據庫中存儲人員的示例:
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;} }在處理事務的代碼旁邊,它創建了Person類的新實例,并將兩個值分配給firstName和lastName字段。 最后,它通過調用會話的方法save()將人員存儲在數據庫中。
當我們執行上述代碼時,控制臺上將打印以下SQL語句:
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在啟動時刪除并創建表一樣,打印出來的第一條語句是drop table和create table語句。 我們還可以看到表T_PERSON的三列ID , FIRST_NAME和LAST_NAME以及主鍵的定義(此處為ID )。
創建表之后,對session.save()的調用將向數據庫發出一條insert語句。 由于Hibernate在內部使用PreparedStatement ,因此在控制臺上看不到這些值。 如果您還想查看綁定到PreparedStatement參數的值,則可以將記錄器org.hibernate.type的記錄級別設置為FINEST 。 這在具有以下內容的名為logging.properties的文件中完成(例如,文件路徑可以作為系統屬性-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設置為與將Hibernate配置文件中的屬性show_sql設置為true具有相同的效果。
現在,您可以在控制臺上看到以下輸出以及實際值:
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映射解決方案的一個有趣的功能是繼承的使用。 用戶可以選擇如何將超類和子類映射到關系數據庫的表。 Hibernate支持以下映射策略:
- 每個類一個表:超類和子類都映射到同一張表。 附加列標記該行是超類還是子類的實例,并且超類中不存在的字段保留為空。
- 聯接的子類 :此策略為每個類使用單獨的表,而子類的表僅存儲超類中不存在的字段。 要檢索子類實例的所有值,必須在兩個表之間執行聯接。
- 每個類的表 :該策略還為每個類使用一個單獨的表,但在子類的表中還存儲了超類的字段。 通過這種策略,子類表中的一行包含所有值,并且為了檢索所有值,不需要join語句。
我們將要研究的方法是“每班單一表”方法。 作為人員的子類,我們選擇Geek類:
public class Geek extends Person {private String favouriteProgrammingLanguage;public String getFavouriteProgrammingLanguage() {return favouriteProgrammingLanguage;}public void setFavouriteProgrammingLanguage(String favouriteProgrammingLanguage) {this.favouriteProgrammingLanguage = favouriteProgrammingLanguage;} }該類擴展了已知的Person類,并添加了一個名為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>第一個區別是引入了discriminator列。 如上所述,此列存儲當前實例屬于哪種類型的信息。 在我們的例子中,我們將其PERSON_TYPE ,為了更好的可讀性,字符串表示實際類型。 在這種情況下,默認情況下,Hibernate僅采用類名。 為了節省存儲空間,也可以使用整數類型的列。
除了區分FAV_PROG_LANG之外,我們還添加了subclass元素,該元素告知Hibernate新的Java類Geek及其字段favouriteProgrammingLanguage ,該字段應映射到FAV_PROG_LANG列。
以下示例代碼顯示了如何在數據庫中存儲Geek類型的實例:
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();執行上面顯示的代碼,將導致以下輸出:
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現在包含兩個新列PERSON_TYPE和FAV_PROG_LANG 。 PERSON_TYPE列包含用于怪胎的值hibernate.entity.Geek 。
為了調查T_PERSON表的內容,我們可以利用H2 jar文件中附帶的Shell應用程序:
> 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存儲實例的類型,而列FAV_PROG_LANG包含超類Person實例的值null 。
以類似于下圖的方式更改映射定義,Hibernate將為超類和子類創建一個單獨的表:
<?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創建表T_GEEK的子類Geek與其他列ID_PERSON 。 這種額外的鍵列存儲外鍵表T_PERSON要想在每一行分配T_GEEK在其父行T_PERSON 。
使用上面顯示的Java代碼在數據庫中存儲一些怪胎,會在控制臺上產生以下輸出:
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現在,Hibernate創建了兩個表而不是一個表,并為表T_GEEK定義了引用表T_PERSON的外鍵。 表T_GEEK由兩列組成: ID_PERSON用于引用相應的人員)和FAV_PROG_LANG用于存儲喜歡的編程語言)。
現在,將極客存儲在數據庫中包括兩個插入語句:
Hibernate: insert intoT_PERSON(ID, FIRST_NAME, LAST_NAME, ID_ID_CARD) values(null, ?, ?, ?) Hibernate: insert intoT_GEEK(FAV_PROG_LANG, ID_PERSON) values(?, ?)第一條語句將新行插入到表T_PERSON ,而第二條語句將新行插入到表T_GEEK 。 這兩個表的內容如下所示:
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僅存儲超類的屬性,而表T_GEEK僅存儲子類的字段值。 列ID_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="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提供實體的名稱( Geek )以及單獨的表的名稱( T_GEEK )作為屬性。 與其他方法一樣,將字段favouriteProgrammingLanguage聲明為子類的屬性。
關于其他方法的另一個重要更改包含在定義id生成器的行中。 由于其他方法使用的是native生成器,該生成器在H2上退回到一個身份列,因此此方法需要一個ID生成器,該生成器創建兩個表( T_PERSON和T_GEEK )唯一的身份。
標識列只是一種特殊的列,它會自動為每一行創建一個新的ID。 但是,兩個表,我們也有兩個標識列以及與其在該IDS T_PERSON表可以是相同的T_GEEK表。 這與僅通過讀取表T_GEEK一行就可以創建Geek類型的實體以及所有人和Geek的標識符都是唯一的要求相矛盾。 因此,我們使用的是序列,而不是通過切換對所述值標識列class從屬性native到sequence 。
現在,Hibernate創建的DDL語句如下所示:
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現在在FAV_PROG_LANG旁邊還包含超類的列( FIRST_NAME和LAST_NAME )。 該語句不會在兩個表之間創建外鍵。 還請注意,現在列ID不再是標識列,而是創建了一個序列。
一個人和一個怪胎的插入向數據庫發出以下語句:
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(?, ?, ?, ?, ?)對于一個人和一個極客,我們顯然只有兩個insert語句。 T_GEEK表完全由一次插入填充,并且包含Geek實例的所有值:
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.關系
到目前為止,我們看到的兩個表之間的唯一關系是“擴展”表。 除了單純的繼承,Hibernate還可以映射基于列表的關系,其中一個實體具有另一個實體的實例列表。 區分以下類型的關系:
- 一對一 :這表示一種簡單的關系,其中類型A的一個實體恰好屬于類型B的一個實體。
- 多對一 :顧名思義,這種關系涵蓋了類型A的實體具有許多類型B的子實體的情況。
- 多對多 :在這種情況下,可以有許多類型A的實體屬于許多類型B的實體。
為了更好地理解這些不同類型的關系,我們將在下面進行研究。
一對一
作為“一對一”案例的示例,我們將以下類添加到我們的實體模型中:
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;} }身份卡作為內部唯一標識符以及外部idNumber,發行日期和布爾值標志(指示該卡是否有效)。
在關系的另一側,此人獲得一個名為idCard的新字段,該字段引用此人的卡:
public class Person {...private IdCard idCard;...public IdCard getIdCard() {return idCard;}public void setIdCard(IdCard idCard) {this.idCard = idCard;}要使用Hibernate特定的映射文件映射此關系,我們可以通過以下方式對其進行更改:
<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>首先,我們為新class添加一個新的class元素,指定類的名稱及其對應的表名(此處為T_ID_CARD )。 字段id成為唯一標識符,應使用序列值填充。
另一方面, Person映射現在包含新的XML元素many-to-one并且其屬性name引用存儲了對IdCard的引用的Person類的字段。 可選的屬性column使我們可以在表T_PERSON中指定鏈接到該人的身份證的外鍵列的確切名稱。 由于此關系應為“一對一”類型,因此我們必須將unique屬性設置為true 。
執行此配置將導致以下DDL語句(請注意,為了減少表的數量,我們已切換回“每個類只有一個表”的方法,在該方法中,對于超類和子類只有一個表):
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與前面的示例有關的變化是,表T_PERSON現在包含一個附加列ID_ID_CARD ,該列被定義為表T_ID_CARD外鍵。 表T_ID_CARD本身包含預期的三列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);創建IdCard的實例很簡單,也請注意,從Person到IdCard的引用設置在最后一行,但只有一行。 這兩個實例都傳遞給Hibernate的save()方法。
仔細看一下上面的代碼,可能會爭論為什么我們必須將兩個實例都傳遞給會話的save()方法。 這一點是合理的,因為Hibernate允許定義在處理完整實體圖時應“級聯”某些操作。 要啟用與IdCard的關系的級聯,我們可以簡單地將屬性cascade添加到映射文件中的many-to-one元素:
<many-to-one name="idCard" column="ID_ID_CARD" unique="true" cascade="all"/>使用值all告訴Hibernate級聯所有類型的操作。 由于這并非始終是處理實體之間關系的首選方法,因此也只能選擇特定的操作:
<many-to-one name="idCard" column="ID_ID_CARD" unique="true" cascade="save-update,refresh"/ ?- >上面的示例演示了如何配置映射,以便僅級聯對save() , saveOrUpdate()和refresh調用save()從數據庫中重新讀取給定對象的狀態)。 例如,不會轉發對Hibernate方法delete()或lock()調用。
使用上述兩種配置中的一種,可以將存儲人員及其身份證的代碼重寫為以下代碼:
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()的目的在于,它還可用于更新現有實體。 兩種實現之間的細微差別是, save()方法返回創建的新實體標識符的事實:
Long personId = (Long) session.save(person);例如,在編寫服務器端代碼時這將很有幫助,該代碼應將此標識符返回給方法的調用者。 另一方面,方法update()不會返回標識符,因為它假定實體已經存儲到數據存儲中,因此必須具有標識符。 嘗試更新沒有標識符的實體將引發異常:
org.hibernate.TransientObjectException: The given object has a null identifier: ...因此,在需要省略確定實體是否已存儲的代碼的情況下, saveOrUpdate()有所幫助。
一對多
在O / R映射期間經常出現的另一個關系是“一對多”關系。 在這種情況下,一組實體屬于另一種類型的一個實體。 為了對這種關系進行建模,我們在模型中添加了Phone類:
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;} }通常,實體Phone具有一個內部標識符( id )和一個用于存儲實際電話號碼的字段。 現場person將參考保存回擁有此電話的人員。 由于一個人可以擁有多個電話,因此我們向“ Person類添加一個“ Set以收集一個人的所有電話:
public class Person {...private Set phones = new HashSet();...public Set getPhones() {return phones;}public void setPhones(Set phones) {this.phones = phones;} }映射文件必須相應地更新:
<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生成的常規標識符( id )旁邊,此定義還包含many-to-one元素。 與我們之前看到的“一對一”關系相反,將該屬性unique設置為false 。 除此之外,在屬性column定義了外鍵列的名稱和屬性的值cascade休眠應該如何級聯此關系操作。
執行上述配置后,將打印出以下DDL語句:
... 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 ...接下來表T_PERSON休眠現在還創建新表T_PHONE其三個ID , NUMBER和ID_PERSON 。 因為后者列存儲的參考Person ,Hibernate也增加了一個外鍵約束表T_PHONE指向列ID表的T_PERSON 。
為了向現有人員之一添加電話號碼,我們首先加載特定人員,然后添加電話:
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();此示例說明如何使用Hibernate的查詢語言(HQL)從數據存儲中加載人員。 與SQL相似,此查詢由from和where子句組成。 未使用其SQL名稱引用FIRST_NAME列。 而是使用Java字段/屬性的名稱。 可以使用setString()方法將諸如名字之類的參數傳遞到查詢中。
在下面的代碼中,對找到的人(應該是一個)進行迭代,并創建一個新的Phone實例,該實例將添加到找到的人的電話集中。 從電話返回到該人的鏈接也將在進行交易之前設置。 執行完此代碼后,數據庫如下所示:
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結果集的兩個選擇語句以上表明,在該行的T_PHONE被連接到在所選擇的行T_PERSON因為它包含在它的第一列名“荷馬”的人的ID ID_ID_PERSON 。
多對多
下一個有趣的關系是“多對多”關系。 在這種情況下,許多類型A的實體可以屬于許多類型B的實體,反之亦然。 在實踐中,例如極客和項目就是這種情況。 一個極客可以在多個項目中(同時或順序)工作,而一個項目可以包含一個以上的極客。 因此,引入了新的實體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;} }它旁邊是標題的標識符( id )和一組怪胎。 在關系的另一側, Geek類具有一組項目:
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;} }為了支持這種關系,必須以以下方式更改映射文件:
<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 。 其唯一標識符存儲在字段id ,其字段title存儲在TITLE列中。 XML元素set定義了映射的一側: geeks集內的項目應存儲在名為T_GEEKS_PROJECTS的單獨表中,該表的列ID_PROJECT和ID_GEEK 。 在關系的另一側,在Geek的subclass內set的XML元素定義了逆關系( inverse="true" )。 在這方面, Geek類中的字段稱為projects ,而引用類中的字段為Project 。
創建表的結果語句如下所示:
... 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 ...這些語句創建新表T_PROJECT以及T_GEEKS_PROJECTS 。 表T_PROJECT由列的ID和TITLE由此在列中的值ID的新的表被稱為T_GEEKS_PROJECTS在其列ID_PROJECT 。 該表上的第二個外鍵指向T_PERSON的主鍵。
為了將可以使用Java編程的極客項目插入數據存儲區,可以使用以下代碼:
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”作為其最喜歡的編程語言的怪胎。 然后,將創建一個新的Project實例,并將查詢結果集中的所有怪胎添加到該項目的怪胎集中。 在關系的另一側,將項目添加到極客項目集。 最后,項目被存儲,事務被提交。
執行此代碼后,數據庫如下所示:
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第一個選擇表明,只有id為3和5的兩個極客表示Java是他們最喜歡的編程語言。 因此,標題為“ Java Project”(ID:7)的項目由ID為3和5(最后選擇語句)的兩個怪胎組成。
零件
面向對象的設計規則建議將常用字段提取到單獨的類中。 例如,上面的Project類仍然錯過開始和結束日期。 但是由于這樣的時間段也可以用于其他實體,因此我們可以創建一個名為Period的新類,該類封裝了兩個字段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為該期間創建一個單獨的表,因為每個Project應該僅具有一個開始日期和結束日期,并且我們希望避開其他聯接。 在這種情況下,Hibernate可以將嵌入式類Period的兩個字段映射到與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類的兩個字段聲明為component屬性。
這將導致以下DDL語句:
... 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的字段位于單獨的類中,但是Hibernate將它們添加到表T_PROJECT 。 以下代碼創建一個新項目并為其添加一個句點:
Project project = new Project(); project.setTitle("Java Project"); Period period = new Period(); period.setStartDate(new Date()); project.setPeriod(period); ... session.save(project);這將導致以下數據情況:
sql> select * from t_project; ID | TITLE | START_DATE | END_DATE 7 | Java Project | 2015-01-01 19:45:12.274 | null要將期間與項目一起加載,無需編寫其他代碼,該期間將自動加載并初始化:
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()); }萬一數據庫中該期間的所有字段都已設置為NULL ,Hibernate還將對Period的引用設置為null 。
6.用戶定義的數據類型
例如,在使用舊數據庫時,某些列的建模方式可能不同于Hibernate映射它們的方式。 例如, Boolean數據類型在H2數據庫上映射為boolean類型。 如果原始開發團隊決定使用具有值“ 0”和“ 1”的字符串映射布爾值,則Hibernate允許實現用于映射的用戶定義類型。
Hibernate定義了必須實現的接口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; }下面顯示了不是針對我們的問題的那些方法的簡單實現:
@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實現從基礎查詢的ResultSet中提取布爾值的字符串表示ResultSet 。 如果返回的字符串等于“ 1”,則該方法返回“ true”,否則返回“ false”。
之前的insert可以被執行的語句,在作為參數傳遞的布爾值value必須被“解碼”到任意字符串“1”或串“0”。 然后,方法nullSafeSet()使用Hibernate的StringType實現在PreparedStatement上設置此字符串值。
最后,我們必須告訴Hibernate從nullSafeGet()返回nullSafeGet()對象,以及該類型應使用哪種列:
@Override public int[] sqlTypes() {return new int[]{ Types.VARCHAR }; }@Override public Class returnedClass() {return Boolean.class; }實現了UserType接口之后,現在可以將此類的實例提供給Configuration :
Configuration configuration = new Configuration(); configuration.configure("hibernate.cfg.xml"); configuration.registerTypeOverride(new MyBooleanType(), new String[]{"MyBooleanType"}); ...MyBooleanType是我們對UserType接口的實現,而String數組定義了如何在映射文件中引用此類型:
<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.攔截器
一個項目可能會要求對每個實體/表的創建和最后更新的時間戳進行跟蹤。 在所有插入和更新操作中為每個實體設置這兩個值是一項相當繁瑣的任務。 因此,Hibernate提供了實現在執行插入或更新操作之前調用的攔截器的功能。 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
總結
以上是生活随笔為你收集整理的Hibernate教程– ULTIMATE指南(PDF下载)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux进程内存占用大分析(linux
- 下一篇: 使用Junit测试名称