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

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

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

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

目录

1.简介 2.项目设置 3.基础
3.1。 SessionFactory和Session 3.2。 交易次数 3.3。 桌子
4.继承 5.关系
5.1。 一对一 5.2。 一对多 5.3。 多对多 5.4。 组件
6.用户定义的数据类型 7.拦截器 8.下载

介绍

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_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元素, 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 intoT_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参数的值,则可以将记录器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_TYPEFAV_PROG_LANGPERSON_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_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 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       | 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定义了外键列的名称和属性的值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其三个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          | 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_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       | 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的新类,该类封装了两个字段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是我们对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 | 1

7.拦截器

一个项目可能会要求对每个实体/表的创建和最后更新的时间戳进行跟踪。 在所有插入和更新操作中为每个实体设置这两个值是一项相当繁琐的任务。 因此,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       | 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/360224.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql单源多表同步单库单表_MySQL主从复制单表或者多表

MySQL数据库安装不过多的介绍了&#xff1a;必须保证2个数据库的版本一致。 主数据库&#xff1a;192.168.0.43 从数据库&#xff1a;192.168.0.53 修改43主数据MySQL数据库安装不过多的介绍了&#xff1a;必须保证2个数据库的版本一致。主数据库&#xff1a;192.168.0.43从数据…

悲观锁定时如何避免可怕的死锁-以及Java 8的一些用法!

有时您根本无法避免&#xff1a;通过SQL进行悲观锁定。 实际上&#xff0c;当您要在共享的全局锁上同步多个应用程序时&#xff0c;它是一个很棒的工具。 有些人可能认为这是在滥用数据库。 如果可以解决您遇到的问题&#xff0c;我们认为可以使用您拥有的工具。 例如&#xf…

object - c 函数的值

函数名说明int rand()随机数生成。&#xff08;例&#xff09;srand(time(nil)); //随机数初期化int val rand()%50; //0&#xff5e;49之间的随机数int abs(int a)整数的绝对值&#xff08;例&#xff09;int val abs(-8); →8※浮点数的时候用fabs。double fabs(double …

xshell 秘钥连接_如何使用PuTTY和xshell 分别远程连接linux,并配置秘钥认证

使用PuTTY 连接并配置密钥认证第一步&#xff1a;下载PuTTY下载 .zip 64位的电脑 32位的putty也能用第二步&#xff1a;配置基本信息打开 PuTTY端口默认是22 (端口是可以改的)ip 地址如果忘记&#xff0c;ifconfig 查看一下Load >Open输入登录名 密码即可完成登录若出现上…

滨河新区(黄河楼)夜景

转载于:https://www.cnblogs.com/ysx4221/p/3454517.html

使用Junit测试名称

命名测试 在创建Junit测试时&#xff0c;通常没有实际使用该方法的名称。 Junit运行程序使用反射来发现测试方法&#xff0c;并且从版本4开始&#xff0c;您不再被限制以test开始方法的名称。 测试方法的名称用于文档目的。 人们遵循不同的风格。 您可以使用给定的given_Somet…

java 门面模式_Java门面模式

一、简介隐藏系统的复杂性&#xff0c;对外提供统一的访问入口&#xff0c;外部系统访问只通过此暴露出的统一接口访问。是一种结构型模式。封装子系统接口的复杂性&#xff0c;提供统一的对外接口&#xff0c;能够使子系统更加简单的被使用。二、结构及使用场景如上所示&#…

摘成功道路上容易被忽视的5项技能

本文摘自《电子工程师专辑》&#xff0c;来源&#xff1a;http://forum.eet-cn.com/FORUM_POST_10008_1200257568_0.HTM。 人人都渴望成功&#xff0c;在通往成功的道路上&#xff0c;少不了技能和运气&#xff0c;运气往往难以受人控制&#xff0c;技能却能好好把握&#xff0…

selenium java测试_java+selenium 自动化测试

在项目上使用自动化测试&#xff0c;是为了跑主流程的回归测试&#xff0c;提高测试效率&#xff0c;在每个测试版本中把主要的精力放在发版内容新增的需求中&#xff1b;根据项目的功能模块&#xff0c;把业务主流程和使用频率高的功能抽取出来进行自动化测试&#xff0c;作为…

通用名称

泛型类型参数名称通常包含一个大写字母字符。 如果您开始阅读有关泛型的官方ORACLE文档&#xff0c;则第一个示例是 /*** Generic version of the Box class.* param <T> the type of the value being boxed*/ public class Box<T> {// T stands for "Type&q…

扩大缩小Linux物理分区大小

由于产品在不同的标段&#xff0c;设备硬盘也不同&#xff0c; 有些500G&#xff0c;有些320G有些200G&#xff0c;开始在大硬盘上做的配置&#xff0c;想把自己定制好的Linux克隆到小硬盘上&#xff0c;再生龙会纠结空间大小的问题&#xff0c; 因此需要做一些分区的改变。 网…

java 文件上传 servlet_java文件上传-原始的Servlet方式

前言&#xff1a;干了这几个项目&#xff0c;也做过几次文件上传下载&#xff0c;要么是copy项目以前的代码&#xff0c;要么是百度的&#xff0c;虽然做出来了&#xff0c;但学习一下原理弄透彻还是很有必要的。刚出去转了一圈看周围有没有租房的&#xff0c;在北京出去找房子…

内存泄漏 和 内存溢出

在计算机科学中&#xff0c;内存泄漏(memory leak)指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失&#xff0c;而是应用程序分配某段内存后&#xff0c;由于设计错误&#xff0c;失去了对该段内存的控制&#xff0c;因而造成了内…

不要被泛型和向后兼容性所愚弄。 使用泛型类型

最近&#xff0c;我与jOOQ的早期客户Ergon的 Sebastian Gruber进行了非常有趣的讨论&#xff0c;我们与之保持了密切联系。 与Sebastian交谈使我们的工程团队得出结论&#xff0c;认为我们应该完全重写jOOQ API。 现在&#xff0c;我们已经有很多用于各种用途的泛型&#xff0c…

java 什么是耦合_什么是耦合、解耦

什么是耦合、解耦一、耦合1、耦合是指两个或两个以上的体系或两种运动形式间通过相互作用而彼此影响以至联合起来的现象。2、在软件工程中&#xff0c;对象之间的耦合度就是对象之间的依赖性。对象之间的耦合越高&#xff0c;维护成本越高&#xff0c;因此对象的设计应使类和构…

基于SharePoint 的企业信息平台架构

转载于:https://www.cnblogs.com/jackljf/p/3589224.html

java bufferarray_Java中的ByteBuffer array()方法

可以使用array()类java.nio.ByteBuffer中的方法获得缓冲区的字节数组。如果返回的数组被修改&#xff0c;则缓冲区的内容也会被类似地修改&#xff0c;反之亦然。如果缓冲区是只读的&#xff0c;则抛出ReadOnlyBufferException。演示此的程序如下所示-示例import java.nio.*;im…

java freemarker 分页_10小时入门java开发04 springboot+freemarker+bootstrap快速实现分页功能...

本节是建立在上节的基础上&#xff0c;上一节给大家讲了管理后台表格如何展示数据&#xff0c;但是当我们的数据比较多的时候我们就需要做分页处理了。这一节给大家讲解如何实现表格数据的分页显示。准备工作还是老规矩&#xff0c;看效果图可以看出我们实现了如下功能1&#x…

获取linux服务器基本信息脚本

为了方便日常运维写的一段简单脚本&#xff0c;用于集中获取服务器操作系统、CPU、内存使用、负载、硬盘使用、网络信息。 脚本比较简单&#xff0c;就不解释了&#xff0c;有兴趣的朋友请自行查看。 #!/bin/bash##Name:system_info#Ver:1.0#Author:lykyl###程序说明:#获取服务…

您认为有关垃圾收集的7件事-完全错了

关于Java垃圾收集的最大误解是什么&#xff0c;它的真实情况如何&#xff1f; 小时候&#xff0c;我的父母曾经告诉我&#xff0c;如果我学习不好&#xff0c;我将成为垃圾收集者。 他们所知道的很少&#xff0c;垃圾回收实际上很棒。 也许这就是为什么即使在Java世界中&#…