hibernate_Hibernate教程– ULTIMATE指南(PDF下载)

hibernate

hibernate

编者注:在本文中,我们提供了全面的Hibernate教程。 Hibernate ORM(简称Hibernate)是一个对象关系映射框架,它有助于将面向对象的域模型转换为传统的关系数据库。 Hibernate通过用高级对象处理功能代替直接持久性相关的数据库访问,解决了对象关系阻抗不匹配的问题。

Hibernate是目前最流行的Java框架之一。 由于这个原因,我们在Java Code Geeks上提供了很多教程,大多数教程可以在此处找到。

现在,我们希望创建一个独立的参考文章,以提供有关如何使用Hibernate的框架,并帮助您快速启动Hibernate应用程序。 请享用!

介绍

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作为构建环境
  • Hibernate(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.176
	4.3.8.Final



	
		com.h2database
		h2
		${h2.version}
	
	
		org.hibernate
		hibernate-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.Driver
        jdbc:h2:~/hibernate;AUTOCOMMIT=OFF
        
        
        1
        org.hibernate.dialect.H2Dialect
        thread
        org.hibernate.cache.internal.NoCacheProvider
        true
        true
        create

从上面的示例中可以看到,配置文件为会话工厂定义了一组属性。 第一个属性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,因此我们使用配置值thread指示Hibernate存储与当前线程之间的会话并从中检索会话。

为了简单起见,我们不想使用实体缓存。 因此,我们将属性cache.provider_classorg.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类带有两个属性来存储人名( firstNamelastName )。 字段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元素,应将其映射到数据库中的表。

id元素指定包含唯一标识符的类的字段的名称( name )以及该值存储在( ID )中的列的名称。 Hibernate通过其子元素generator了解如何为每个实体创建唯一标识符。 在上面显示的native值旁边,Hibernate支持一长串不同的策略。

native策略只是为所使用的数据库产品选择最佳策略。 因此,该策略可以应用于不同的产品。 其他可能的值例如是: sequence (使用数据库中的序列), uuid (生成128位UUID)并进行assigned (让应用程序自行分配值)。 除了预定义的策略之外,还可以通过实现org.hibernate.id.IdentifierGenerator接口来实现自定义策略。

使用XML元素property ,将firstNamelastName字段映射到FIRST_NAMELAST_NAME列。 属性namecolumn定义了类和列中的字段名称。

以下代码显示了如何在数据库中存储人员的示例:

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类的新实例,并将两个值分配给firstNamelastName字段。 最后,它通过调用会话的方法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 
    into
        T_PERSON
        (ID, firstName, lastName, ID_ID_CARD) 
    values
        (null, ?, ?, ?)

正如我们选择让Hibernate在启动时删除并创建表一样,打印出来的第一条语句是drop tablecreate table语句。 我们还可以看到表T_PERSON的三列IDFIRST_NAMELAST_NAME以及主键的定义(此处为ID )。

创建表后,对session.save()的调用将向数据库发出一条insert语句。 由于Hibernate在内部使用PreparedStatement ,因此在控制台上看不到这些值。 如果您还想查看绑定到PreparedStatement参数的值,则可以将logger org.hibernate.type的日志记录级别设置为FINEST 。 这在具有以下内容的名为logging.properties的文件中完成(例如,文件路径可以作为系统属性-Djava.util.logging.config.file=src/main/resources/logging.properties ) :

.handlers = java.util.logging.ConsoleHandler
.level = INFO

java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

org.hibernate.SQL.level = FINEST
org.hibernate.type.level = FINEST

将记录器org.hibernate.SQL设置为与将Hibernate配置文件中的属性show_sql设置为true具有相同的效果。

现在,您可以在控制台上看到以下输出以及实际值:

DEBUG: 
    insert 
    into
        T_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 
    into
        T_PERSON
        (ID, FIRST_NAME, LAST_NAME, FAV_PROG_LANG, PERSON_TYPE) 
    values
        (null, ?, ?, ?, 'hibernate.entity.Geek')

与前面的示例相比,表T_PERSON现在包含两个新列PERSON_TYPEFAV_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 
    into
        T_PERSON
        (ID, FIRST_NAME, LAST_NAME, ID_ID_CARD) 
    values
        (null, ?, ?, ?)
Hibernate: 
    insert 
    into
        T_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_PERSONT_GEEK )唯一的身份。

标识列只是一种特殊的列,它会自动为每一行创建一个新的ID。 但是,两个表,我们也有两个标识列以及与其在该IDS T_PERSON表可以是相同的T_GEEK表。 这与仅通过读取表T_GEEK一行就可以创建Geek类型的实体以及所有人和Geek的标识符都是唯一的要求相矛盾。 因此,我们使用的是序列,而不是通过切换对所述值标识列class从属性nativesequence

现在,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_NAMELAST_NAME )。 该语句不会在两个表之间创建外键。 还请注意,现在列ID不再是标识列,而是创建了一个序列。

一个人和一个怪胎的插入向数据库发出以下语句:

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        T_PERSON
        (FIRST_NAME, LAST_NAME, ID) 
    values
        (?, ?, ?, ?)
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    insert 
    into
        T_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       | Java

5.关系

到目前为止,我们看到的两个表之间的唯一关系是“扩展”表。 除了单纯的继承,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_NUMBERISSUE_DATEVALID

用于将人及其身份证插入的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的实例很简单,也请注意,从PersonIdCard的引用设置在最后一行,但只有一行。 这两个实例都传递给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定义了外键列的名称和属性的值cascadeHibernate应该如何级联此关系操作。

执行上述配置后,将打印出以下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_PERSONHibernate现在还创建新表T_PHONE其三个IDNUMBERID_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          | null

sql> 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_PROJECTID_GEEK 。 在关系的另一侧,在Geeksubclassset的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由列的IDTITLE由此在列中的值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       | Java

sql> select * from t_project;
ID | TITLE
7  | Java Project

sql> 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的新类,该类封装了两个字段startDateendDate

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_DATEEND_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 is here our implementation of the UserType interface, whereas the String array defines how to reference this type in the mapping file:

<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>

As can be seen from the snippet above, the new type “MyBooleanType” is used for the boolean property of the table T_ID_CARD :

sql> select * from t_id_card;
ID | ID_NUMBER | ISSUE_DATE              | VALID
2  | 4711      | 2015-03-27 11:49:57.533 | 1

7. Interceptors

A project may come with the requirement that for each entity/table the timestamp of its creation and its last update should be tracked. Setting these two values for each entity on all insert and update operations is a fairly tedious task. Therefore Hibernate offers the ability to implement interceptors that are called before an insert or update operation is performed. 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 {

	@Override
	public 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;
	}

	@Override
	public 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       | Java

8. 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值