Hibernate学习笔记之基础知识

论坛 期权论坛 脚本     
匿名网站用户   2020-12-20 05:53   11   0

一、简介

1.1 概念

  • PO(Persistent):持久化对象
  • POJO(Plain Ordinary Java Object):普通的Java对象
  • OOA:面向对象分析
  • OOD:面向对象设计
  • OOP:面向对象编程
  • ORM(Object/Relation Mapping):即对象/关系数据库映射。

1.2 ORM

ORM可以理解成一种规范,它概述了这类框架的基本特征:完成面向对象的编程语言到关系数据库的映射。当ORM框架完成映射后,即可利用面向对象程序设计语言的简单易用性,又可利用关系数据库的技术优势。因此可以把ORM框架当成应用程序和数据库的桥梁。

JavaEE规范里的JPA规范就是一种ORM规范,JPA规范并不提供任何ORM实现,JPA规范提供了一系列编程接口,而JPA实现(本质上是ORM框架)则负责为这些编程接口提供实现。如果面向JPA编程,那么应用程序底层可以在不同的ORM框架之间自由切换。

ORM框架是面向对象程序设计语言与关系数据库发展不同步时的中间解决方案。在ORM框架中,持久化对象是一种中间媒介,应用程序只需操作持久化对象,ORM框架则负责将这种操作转换为底层数据库操作。

1.3 Hibernate

Hibernate是一个面向Java环境的对象/关系数据库映射工具,用于把面向对象模型表示的对象映射到基于SQL的关系模型的数据结构中。Hibernate可以消除针对特定数据库厂商的SQL代码,并且把结果集从表格式的形式转换成值对象的形式。

Hibernate不仅仅管理Java类到数据库表的映射(包括Java数据类型到SQL数据类型的映射),还提供数据查询和获取数据的方法,可以大幅度减少开发时人工使用SQL和JDBC处理数据的时间。

注:下面的代码所参考的资料针对的Hibernate版本为4.3.5,因此如果版本不同则肯报错,所以应该谨慎参考。(对自己所遇到的因版本造成区别地方,我会在使用时标注清楚)

官方网站:http://hibernate.org/

下载Hibernate后在lib目录下有required子目录,这个目录下存放着运行Hibernate的核心类库,,以及必须的第三方类库。

注意:Hibernate底层依然基于JDBC的,因此需要添加JDBC驱动。

1.3.1 Hibernate数据库简单操作

ORM框架中非常重要的媒介:PO(持久化对象)。持久化对象的作用是完成持久化操作,Hibernate中的PO非常简单,Hibernate是低侵入式设计,完全采用普通Java对象作为持久化对象使用,Hibernate不要求持久化类继承任何父类,或者实现任何接口,这样就保证代码不被污染。我们要想让POJO具备持久化的能力需要为其添加一些注解:

  • @Entity:声明该类是一个Hibernate的持久化类
  • @Table:指定该类映射的表
  • @Id:用于指定该类的表示属性。所谓表示属性,就是可以唯一标识该对象的属性,标识属性通常映射到数据表的主键列
  • @GeneratedValue:用于指定主键生成策略,其中strategy属性指定了主键生成策略为IDENTITY策略,也就是采用自动增长的主键生成策略

注:以上四个注解都是JPA的标准注解,位于javax.persistence包下。

PO=POJO+持久化注解

Hibernate配置文件可以使用*.properties属性文件,也可以使用XML文件配置,实际开发中通常使用XML文件配置,如下(hibernate.cfg.xml):

<?xml version="1.0" encoding="GBK"?>
<!DOCTYPE hibernate-configuration PUBLIC
 "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
 "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
 <session-factory>
  <!-- 指定连接数据库所用的驱动 -->
  <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
  <!-- 指定连接数据库的url,其中hibernate是本应用连接的数据库名 -->
  <property name="connection.url">jdbc:mysql://localhost/hibernate</property>
  <!-- 指定连接数据库的用户名 -->
  <property name="connection.username">root</property>
  <!-- 指定连接数据库的密码 -->
  <property name="connection.password">root</property>
  <!-- 指定连接池里最大连接数 -->
  <property name="hibernate.c3p0.max_size">20</property>
  <!-- 指定连接池里最小连接数 -->
  <property name="hibernate.c3p0.min_size">1</property>
  <!-- 指定连接池里连接的超时时长 -->
  <property name="hibernate.c3p0.timeout">5000</property>
  <!-- 指定连接池里最大缓存多少个Statement对象 -->
  <property name="hibernate.c3p0.max_statements">100</property>
  <property name="hibernate.c3p0.idle_test_period">3000</property>
  <property name="hibernate.c3p0.acquire_increment">2</property>
  <property name="hibernate.c3p0.validate">true</property>
  <!-- 指定数据库方言 -->
  <property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>
  <!-- 根据需要自动创建数据表 -->
  <property name="hbm2ddl.auto">update</property><!--①-->
  <!-- 显示Hibernate持久化操作所生成的SQL -->
  <property name="show_sql">true</property>
  <!-- 将SQL脚本进行格式化后再输出 -->
  <property name="hibernate.format_sql">true</property>
  <!-- 罗列所有持久化类的类名 -->
  <mapping class="org.crazyit.app.domain.News"/>
 </session-factory>
</hibernate-configuration>

Hibernate配置文件的默认名为hibernate.cfg.xml,当程序调用Configuration对象的configure()方法时,Hibernate将自动加载该文件。除此之外,Hibernate并不推荐采用DriverManager来连接数据库,而是推荐使用数据源来管理数据库连接,这样保证最好的性能。Hibernate推荐使用C3P0数据源。
示例如下:

@Entity
@Table(name = "news_inf")
public class News {
 // 消息类的标识属性
 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Integer id;
 // 消息标题
 private String title;
 // 消息内容
 private String content;

 // id的setter和getter方法
 // title的setter和getter方法
 // content的setter和getter方法
}
public class NewsManager {
 public static void main(String[] args) throws Exception {
  // 实例化Configuration,
  Configuration conf = new Configuration()
    // 不带参数的configure()方法默认加载hibernate.cfg.xml文件,
    // 如果传入abc.xml作为参数,则不再加载hibernate.cfg.xml,改为加载abc.xml
    .configure();
  ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(conf.getProperties())
    .build();
  // 以Configuration实例创建SessionFactory实例
  SessionFactory sf = conf.buildSessionFactory(serviceRegistry);
  // 创建Session
  Session sess = sf.openSession();
  // 开始事务
  Transaction tx = sess.beginTransaction();
  // 创建消息对象
  News n = new News();
  // 设置消息标题和消息内容
  n.setTitle("疯狂Java联盟成立了");
  n.setContent("疯狂Java联盟成立了," + "网站地址http://www.crazyit.org");
  // 保存消息
  sess.save(n);
  // 提交事务
  tx.commit();
  // 关闭Session
  sess.close();
  sf.close();
 }
}

逻辑非常清晰明了,向数据库中插入一条数据,而编码完全是面向对对象,Hibernate负责底层SQL操作。
在执行session.save(News)之前,先要获取Session对此昂。PO只有在Session的管理下才可完成数据库访问。为了使用Hibernate进行持久化操作,通常由如下操作步骤:

  1. 开发持久化类,由POJO+持久化注解组成
  2. 获取Configuration
  3. 获取SessionFactory
  4. 获取Session,打开事务
  5. 用面向对象的方式操作数据库
  6. 关闭事务,关闭Session

<1> 针对Hibernate-5.1.0启用配置的方式变化如下

// configures settings from hibernate.cfg.xml
StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure().build();
SessionFactory sf = new MetadataSources(registry).buildMetadata().buildSessionFactory();
Session session = sf.openSession();
Transaction transaction = session.beginTransaction();
// 创建Person对象
Person person = new Person();
// 为Person对象设置属性
person.setAge(20);
person.setName("crazyit.org");
session.save(person);
transaction.commit();
session.close();
sf.close();

注意:如果使用Hibernate-5.1.0环境,但还是使用原来的启用配置方式则会出现如下错误

INFO: HHH000424: Disabling contextual LOB creation as createClob() method threw error : java.lang.reflect.InvocationTargetException
Exception in thread "main" org.hibernate.MappingException: Unknown entity: org.crazyit.app.domain.Person(这是上面代码中的Person对象所在的包)
 at org.hibernate.internal.SessionFactoryImpl.getEntityPersister(SessionFactoryImpl.java:776)
 at org.hibernate.internal.SessionImpl.getEntityPersister(SessionImpl.java:1533)
 at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:104)
 at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
 at org.hibernate.event.internal.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:38)
 at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
 at org.hibernate.event.internal.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:32)
 at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
 at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:682)
 at org.hibernate.internal.SessionImpl.save(SessionImpl.java:674)
 at org.hibernate.internal.SessionImpl.save(SessionImpl.java:669)
 at lee.PersonManager.createAndStorePerson(PersonManager.java:39)
 at lee.PersonManager.main(PersonManager.java:21)Person(这是上面代码中的Person对象所在的包)
 at org.hibernate.internal.SessionFactoryImpl.getEntityPersister(SessionFactoryImpl.java:776)
 at org.hibernate.internal.SessionImpl.getEntityPersister(SessionImpl.java:1533)
 at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:104)
 at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:192)
 at org.hibernate.event.internal.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:38)
 at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:177)
 at org.hibernate.event.internal.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:32)
 at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
 at org.hibernate.internal.SessionImpl.fireSave(SessionImpl.java:682)
 at org.hibernate.internal.SessionImpl.save(SessionImpl.java:674)
 at org.hibernate.internal.SessionImpl.save(SessionImpl.java:669)
 at lee.PersonManager.createAndStorePerson(PersonManager.java:39)
 at lee.PersonManager.main(PersonManager.java:21)

随PO与Session的关联关系,PO可由如下三种状态:

  • 瞬态:如果PO实例从未与Session关联过,该PO实例化处于瞬态状态
  • 持久化:如果PO实例与Session关联起来,且该实例对应到数据库记录,则该实例处于持久化状态
  • 托管:如果PO实例曾经与Session关联过,但因为Session的关闭等原因,PO实例脱离了Session的管理,这种状态被称为脱管状态

对PO的操作必须在Session管理下才能同步到数据库。Session有SessionFactory工厂生成,SessionFactory是数据库编译后的内存镜像,同行一个应用对应一个SessionFactory对此昂。SessionFactory对此昂由Configuration对象生成,Configuration对对象负责加载Hibernate配置文件。

Hibernate针对使用JDBC操作数据库的方式的优点:

  • 不再需要编写SQL语句,而是允许采用OO方式来访问数据库
  • 在JDBC访问过程中大量的checked异常被包装成Hibernate的Runtime异常,从而不再要求程序必须处理所有异常

1.4 重点常用注解简单整理

  • @Entity:声明该POJO类是一个Hibernate的持久化类
  • @table:指定该类映射的表
  • @UniqueConstraint:用于为数据表定义唯一约束
  • @Index:用于为数据表定义索引
  • @SelectBeforeUpdate:指定Hibernate在更新某个持久化对象之前是否需要先进行一次查询
  • @Column:为某个属性所映射的数据列的详细信息
  • @Formula:指定该属性的值将根据表达式来计算
  • @Generated:设置该属性映射的数据列的值是否由数据库生成
  • @Transient:用来修饰不想持久保存的属性
  • @Enumerated:用来饰枚举类型的属性
  • @Lob/@Basic:用来修饰大数据类型的属性
  • @Temporal:用来修饰日期类型的属性
  • @Id:标识属性j将其映射成主键列
  • @GeneratedValue:指定逻辑主键自动生成主键值
  • @SequenceGenerator/@TableGenerator:在@GeneratedValue中使用,详见下面
  • @GenericGenerator:不使用指定的四种主键生成策略,可以使用该注解进行自定义
  • @ElementCollection:映射集合属性
  • @CollectionTable:映射保存集合属性表
  • @JoinColumn:用于定义外键列
  • @OrderColumn:用于定义List集合、数组的索引列
  • @MapKeyColumn:用于映射Map集合的索引列
  • @SortNatural:对集合元素采用自然排序
  • @SortComparator::对集合元素采用定制排序
  • @Embeddable:修饰非持久化实体的复合类(组件类)
  • @Embedded:如果在持久化类指定组件类可以使用@Embedded注解来代替在组件类上使用@Embeddable

注意:整理的是这篇博客所提及的一些常用的注解,而像@ManyToOne、@OneToMany、@ManyToMany和@OneToOne这些注解会在下一篇博客中进行详细说明。

二、详细

2.1 Hibernate配置文件

2.1.1 创建Configuration对象

org.hibernate.cfg.Configuration实例代表了应用程序到SQL数据库的配置信息,Configuration对象提供了一个buildSessionFactory()方法,该方法可以产生一个不可变的SessionFactory对象。

Hibernate所使用的配置文件的不同,创建Configuration对象的方式也不相同,通常由如下几种配置Hibernate的方式:

  1. 使用hibernate.properties文件作为配置文件
  2. 使用hibernate.cfg.xml文件作为配置文件
  3. 不使用任何配饰文件方式,以编码方式创建Configuration对此昂

Configuration实例的唯一作用是创建SessionFactory实例,所以它被设计成启动期间对象,一旦SessionFactory创建完成,他就被丢弃。下文主要针对第二种方式进行细说,其他两种方式可以参考官方文档。

Hibernate发布包的project\etc路径下,提供了一个hibernate.properties配置文件,该文件详细列出了Hibernate配置文件的所有属性。

我们使用hibernate.cfg.xml作为配置文件来创建Configuration实例代码如下:

Configuration conf = new Configuration()
    // 不带参数的configure()方法默认加载hibernate.cfg.xml文件,
    // 如果传入abc.xml作为参数,则不再加载hibernate.cfg.xml,改为加载abc.xml
    .configure();

2.1.2 JDBC连接属性

  • connection.driver_class:设置连接数据库的驱动
  • connection.url:设置所需连接数据库服务的URL
  • connection.username:设置连接数据库的用户名
  • connection.password:设置连接数据库的密码
  • dialect:设置连接数据库所使用的的方言

2.1.3 数据库方言

虽然所有关系数据库都支持使用标准SQL语句,但所有数据库都对标准SQL进行一些扩展,因此,Hibernate需要根据数据库来识别这些差异。这些数据库方言定义都在org.hibernet.dialect.*包下面,下面罗列出常用的方言:

RDBMS 方言
DB2 org.hibernate.dialect.DB2Dialect
DB2 AS/400 org.hibernate.dialect.DB2400Dialect
DB2 OS390 org.hibernate.dialect.DB2390Dialect
PostgreSQL org.hibernate.dialect.PostgreSQLDialect
MySQL org.hibernate.dialect.MySQLDialect
MySQL with InnoDB org.hibernate.dialect.MySQLInnoDBDialect
MySQL with MyISAM org.hibernate.dialect.MySQLMyISAMDialect
Oracle (any version) org.hibernate.dialect.OracleDialect
Oracle 9i/10g org.hibernate.dialect.Oracle9Dialect
Sybase org.hibernate.dialect.SybaseDialect
Sybase Anywhere org.hibernate.dialect.SybaseAnywhereDialect
Microsoft SQL Server org.hibernate.dialect.SQLServerDialect
SAP DB org.hibernate.dialect.SAPDBDialect
Informix org.hibernate.dialect.InformixDialect
HypersonicSQL org.hibernate.dialect.HSQLDialect
Ingres org.hibernate.dialect.IngresDialect
Progress org.hibernate.dialect.ProgressDialect
Mckoi SQL org.hibernate.dialect.MckoiDialect
Interbase org.hibernate.dialect.InterbaseDialect
Pointbase org.hibernate.dialect.PointbaseDialect
FrontBase org.hibernate.dialect.FrontbaseDialect
Firebird org.hibernate.dialect.FirebirdDialect

2.1.4 JNDI数据源的连接属性

如果无须Hibernate自己管理数据源,而是直接访问容器管理数据源,Hibernate可使用JNDI数据源的相关配置,下面是连接JNDI数据源的主要配置属性:

  • hibernate.connection.datasource:指定JNDI数据源的名字
  • hibernate.jndi.url:指定JNDI提供的URL,该属性是可选的。如果JNDI与Hibernate持久化访问的代码处于同一个应用中,则无须指定该属性
  • hibernate.jndi.class:指定JNDI InitialContexFactory的实现类,该属性是可选的。如果JNDI与Hibernate持久化访问的代码处于同一个应用中,则无须指定该属性
  • hibernate.connection.username:指定连接数据库的用户名,该属性是可选的
  • hibernate.connection.password:指定连接数据库的密码,该属性是可选的

注意:即使使用JNDI连接数据源,也一样需要指定连接数据库的方言。虽然设置数据库方言并不是必须的,但对于优化持久层访问很有必要。

配置Hibernate连接Tomcat中数据源的配置片段如下:

<!-- 配置JNDI数据源的JNDI名 -->
<property name="connection.datasource">java:comp/env/jdbc/dstest</property>
<!-- 配置数据库方言 -->
<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>

如果数据源所在容器支持跨事务资源的全局事务管理,从JNDI数据源获得的JDBC连接,可自动参与容器管理的全局事务,而不仅仅是Hibernate的局部事务。

2.1.5 Hibernate事务属性

事务也是Hibernate持久层访问的重要方面,Hibernate不仅提供了局部事务支持,也允许使用容器管理的全局事务。Hibernate关于事务管理的属性有如下几个:

  • hibernate.transaction.factory_class:指定Hibernate所用的事务工厂的类型,该属性必须是TransactionFactory的直接或间接子类
  • jta.UserTransaction:该属性值是一个JNDI名,Hibernate将使用JTA TransactionFactory从应用服务器获取JTAUserTransaction
  • hibernate.transaction.manager_look_class:该属性值应为TansactionManagerLookup类名,当使用JVM级别的缓存时,或者在JTA环境中使用hilo生成器策略时,需要该类
  • hibernate.transaction.flush_before_completion:指定Session是否在事务完成后自动将数据刷新(flush)到底层数据库,该属性只能是true或者false。当前更好的方法是使用Context相关的Session管理
  • hibernate.transaction.auto_close_session:指定是否在事务结束后自动关闭Session。该属性值只能是true或false。当前更好的方法是使用Context相关的Session管理

2.1.6 二级缓存相关属性

Hibernate的SessionFactory可持有一个可选的二级缓存,通过使用这种二级缓存可以提高Hibernate的持久化访问的性能。Hibernate关于二级缓存的属性有如下几个:

  • hibernate.cache.use_second_level_cache:用于设置是否启用二级缓存,该属性可完全禁止使用二级环涔
  • hibernate.cache.region.factory_class:该属性用于设置二级缓存RegionFactory实现类的类名
  • hibernate.cache.region_prefix:设置二级缓存区名称的前缀
  • hibernate.cache.use_minimal_puts:以频繁的读操作作为代价,优化二级缓存以实现最小化写操作,这个设置对集群缓存非常有用,对于集群缓存的实现而言,默认是开启的
  • hibernate.cache.use_query_cache:设置是否允许查询缓存。个别查询仍然需要显式设置为可缓存的
  • hibernate.cache.query_cache_factory:设置查询缓存工厂的类名,查询缓存工厂必须实现QueryCache接口。该属性默认为內建的StandardQueryCache
  • hibernate.cache.use_structured_entries:用于设置是否强制Hibernate以可读性更好的格式将数据存入二级缓存

2.1.7 外链接抓取属性

外链接抓取能限制执行SQL语句的次数来提高效率,这种外连接抓取通过单个select语句中使用outer join来一次抓取多个数据表的数据。外连接抓取允许在单个select语句中,通过@ManyToOne、@OneToMany、@ManyToMany和@OneToOne等关联获取连接对象的整个对象图。

将hibernate.max_fetch_depth设为0,将在全局范围内禁止外连接抓取,设置为1或更高值能启用上面的多对多的外连接抓取。除此之外,还应该在持久化注解中通过fetch=FechType.EAGER来指定这种外连接抓取。

2.1.8 其他常用的配置属性

  • hibernate.show_sql:是否在控制台输出Hibernate持久化操作底层所使用的的SQL语句。只能为true或false
  • hibernate.format_sql:是否将SQL语句转成换格式良好的SQL。只能为true或false
  • hibernate.use_sql_comments:是否在Hibernate生成的SQL语句中添加有助于调试的注释。只能为true或false
  • hibernate.jdbc.fetch_size:指定JDBC抓取数量的大小。它可接受一个整数值,其实质是调用Statement.setFetchSize()方法
  • hibernate.jdbc.batch_size:指定Hibernate使用JDBC2的批量更新的大小,它可以接受一个整数值,建议取5~30之间的值
  • hibernate.connection.autocommit:设置是否自动提交。通常不建议打开自动提交
  • hibernate.hbm2ddl.auto:设置当创建SessionFactory时,是否根据持久化类的映射关系自动建立数据库表。该属性可取如下四个值:
    • create:每次创建SessionFactory时都会重新建表,因此当前插入的数据会丢失
    • create-drop:每次显式关闭SessionFactory时,程序会自动Drop刚刚创建的数据表
    • update:每次创建SessionFactory时,如果数据库中没有与持久化类对应的表,Hibernate会自动建表;如果已有,则保留已有的数据表和数据,只进行更新或插入数据,因此通常将该属性设置为update
    • validate:每次创建SessionFactory时,验证现有schema与你配置的hibernate是否一致,如果不一致就抛出异常,并不做更新

2.2 持久化类的要求

虽然Hibernate对持久化类没有太多要求,但还是应该遵守如下规则:

  • 提供一个无参数构造器。通常为了方便Hibernate在运行时生成代理,构造器的访问控制修饰符至少是包可见的,即大于或等于默认的访问控制符。
  • 提供一个标识属性。标识属性通常映射数据库表的主键,当然可以不指定任何标识属性,而是在持久化注解中直接将多个普通属性映射成一个联合主键,但不推荐这么做。标识类型可以是任何基本类型或其包装类型,但建议使用包装类型作为标识属性的类型。
    • 虽然Hibernate可以允许持久化类没有标识属性,而是让Hibernate内部来追踪对象的识别。但这样做将导致Hibernate的许多功能无法使用。而且,Hibernate建议使用可以为空的类型来作为标识属性的类型,因此应该尽量避免使用基本数据类型。
  • 为持久化类的每个成员变量提供setter和getter方法。
  • 使用非final的类
  • 重写equals()和hashCode()方法。如果需要把持久化类的实例放入Set中,则应该为该持久类重写equals()和hashCode()方法。遗憾的是,对采用自动生成标识值的对象不能使用这种方法。Hibernate仅为那些持久化对象指定标识值,一个新创建的实例将不会有任何标识值。--》暂不理解

2.3 持久化对象的状态

  • 瞬态:对象有new操作符创建,且尚未与Hibernate Session关联的对象处于瞬态。瞬态的对象不会被持久化到数据库中,也不会被赋予持久化标识。使用Hibernate Session可以将其变为持久化状态
  • 持久化:持久化实例在数据库中有对应的记录,并拥有一个持久化标识(identifier)。Hibernate会检测到处于持久化状态对象的改动,在当前操作执行完成时将对象数据写会数据库。开发者不需要手动执行update
  • 托管:实例曾经处于持久化状态,但随着与之关联的Session被关闭,该对象就变成脱管状态。脱管状态对象的引用依然有效,对象可继续被修改。如果重新让托管对象与某个Session关联,这个托管对象会重新转为持久化状态,而托管期间的改动不会丢失,也可被写入数据库。正是因为这个功能,逻辑上的长事务称为可能,他被称为应用程序事务,即事务可以跨越用户的思考,因为当对象处于托管状态是,对该对象的操作无须锁定数据库,不会造成性能下降

2.4 改变持久化对象状态的方法

2.4.1 持久化实体

为了让瞬态对象转换为持久化状态,Hibernate Session提供了如下几个方法:

  • Serializable save(Object obj):将object对象变为持久化状态,该对象的属性将被保存到数据库
  • void persist(Object obj):将object对象变为持久化状态,该对象的属性将被保存到数据库
  • Serializable save(Object obj,Object pk):将object对象变为持久化状态,该对象的属性将被保存到数据库,保存到数据库时,指定主键值
  • void persist(Object obj,Object pk):将object对象变为持久化状态,该对象的属性将被保存到数据库,保存到数据库时,指定主键值

如果对象的标识属性(identity)是generated类型(指定了主键生成器策略时)的,那么Hibernate将会在执行save()方法时自动生成标识属性值,并将该标识属性值分配给该对象,并且标识属性会在save()被调用时自动产生并分配给该对象。如果标识属性是assigned类型的,或者是复合主键(composite key),那么该标识属性值应当在调用save()之前手动赋给该对象。

Hibernate之所以提供与save()功能几乎完全类似的persist()方法,一方面是为了照顾JPA的用法习惯;另一方面是它们返回值的不同,save()方法会立即将持久化对象对应的数据插入到数据库;而persist()则保证当它在一个事务外部被调用时,并不立即转换成insert语句。这个功能很有用,尤其是需要封装一个长会话流程的时候,persist()方法就显得尤为重要了。

2.4.2 根据主键加载持久化实体

程序可以通过load()来加载一个持久化实例,第一个参数是持久化类的Class对象,第二个参数是主键值,如果没有匹配的记录,则抛出HibernateException异常。如果在持久化注解中指定了延迟加载,在该方法会返回一个未初始化的代理对象(可以理解为持久化对象的替身),这个代理对象并没有加载数据记录,知道程序调用该代理对象的某个方法时,Hibernate才会去访问数据库。

如果希望在某个对象中创建一个指向另一个对象的关联,又不想在从数据库中装载该对象的同时立即装载所有关联的全部对象,延迟加载方式就非常有用了。

与load()方法类似的是get()方法,get()方法也用于根据主键加载持久化实例,不同的是当没有匹配的记录会返回null,而不是抛出异常。

2.4.3 更新持久化实体

程序对持久化实例所做的修改在Session flush之前被自动保存到数据库,无须程序调用其他方法(不需要调用update()方法)来将修改持久化。也就是说,修改对象的最简单的方法就是在Session处于打开状态时load()它,然后直接修改即可。

2.4.4 更新托管实体

对于一个曾经持久化过、但现在已脱离了Session管理的持久化对象,它被认为处于脱管状态。当程序修改托管对象的状态后,程序应该显式地使用新的Session来保存这些修改。Hibernate提供了update()、merge()、和updateOrSave()等方法来保存这些修改。

示例:

News n=firstSess.load(News.class,pk)
//第一个Session已经关闭了
firstSess.close();
//修改脱管状态下的持久化对象
n.setTitle("新标题");
//打开第二个Session
Session secondSession=...
//保存托管对象所做的修改
secondSession.update(n)

当需要使用update()来保存程序对持久化对象所做的修改时,如果不清楚该对象是否曾经持久化过,则可以使用updateOrSave()方法,如果曾经持久化过则执行update()操作;否则将执行save()操作。

merge()方法也可将程序对托管对象所做的修改保存到数据库,但merge()与update()方法的最大区别是:merge()方法不会持久化给定的对象。举例来说,当程序执行sess.update(a)代码后,a对象将会变成持久化状态;而执行sess.merge(a)代码后,a对象一眼不是持久化状态,a对象依然不会被关联到Session上,merge()方法会返回a对象的副本——该副本处于持久化状态。
当程序使用merge()方法来保存程序对托管对象所做的修改时,如果Session中存在相同持久化标识的持久化对象,merge()方法里提供的对象状态将覆盖原有持久化实例的状态。如果Session中没有相应的持久化实例,则尝试从数据库中加载,或者创建新的持久化实例,最后返回该持久化实例。

使用load()和get()方法加载持久化对象时,还可指定一个“锁模式”参数。Hibernate使用LockOptions对象代表“锁模式”,LockOptions提供了READ和UPGRADE两个静态属性来代表共享、修改锁。如果需要加载某个持久化对象以供修改,则可用如下代码:

News n=Session.get(News.class,pk,LockOptions.UPGRADE);

Session.LockRequese的lock()方法也将某个托管对象重新持久化,但该托管对对象必须是没有修改过的!示例如下:

// 简单地重新持久化
sess.buildLockRequese(LockOptions.NONE).lock(news);
// 先检查持久化对象的版本,然后重新持久化对象
sess.buildLockRequese(LockOptions.NONE).lock(news);
// 先检查持久化对象的版本,然后使用SELECT .. FOR UPDATE重新持久化该对象
sess.buildLockRequese(new LockOptions(LockMod.PESSIMISTIC_WRITE)).lock(news);

注意:lock()可以搭配多种LockOptions。

2.4.5 删除持久化实体

通过Session的delete()方法来删除该持久化实例,一旦删除了该持久化实例,该持久化实例对应的数据记录也将被删除。

New n=sess.load(News.class,pk);
sess.delete(n);

Hibernate本身不提供直接执行update或delete语句的API,Hibernate提供的是一种面向对象的状态管理。

2.5 Hibernate映射

Hibernate提供了如下三种方式将POJO变成PO类:

  • 使用持久化注解(以JPA标准注解为主,如果一些特殊要求,则依然需要使用Hibernate本身提供的注解)
  • 使用JPA2提供的XML配置描述文件(XMLdeployment descriptor),这种方式可以让Hibernate的PO类与JPA实体类兼容。但在实际开发中,很少有公司使用这种方式。
  • 使用Hibernate传统的XML映射文件(*.hbm.xml文件的形式),由于这种方式是传统Hibernate的推荐方式,因此依然由少数企业采用这种方式

2.5.1 常用注解

对于Hibernate PO类而言,通常可以采用@Entity和@Table这两个注解来修饰它。注意:没有@标识符的情况的二级菜单表示的是注解所支持的属性。

  • @Entity:被该注释修饰的POJO就是一个实体。包含一个name属性,指定该实体类的名称,但大部分时候无须指定该属性,系统默认以该类的类名作为默认值
  • @Table:该注解指定持久化类所映射的表
    • catalog:非必需属性,用于设置将持久化类所映射的表放入指定的catalog中
    • indexes:非必需属性,为持久化类所映射的表设置索引。该属性的值是一个@Index注解数组
    • name:非必需属性,设置持久化类所映射的表的表名。如果没有指定该属性,name该表的表名将于持久化类的类名相同
    • schema:非必需属性,设置将持久化类所映射的表放入指定的schema中。
    • uniqueConstraints:非必需属性,为持久化类所映射的表设置唯一约束。该属性的值是一个@UniqueConstraint注解数组
  • @UniqueConstraint用于为数据表定义唯一约束
    • columnNames:该属性的值是一个字符串数组,每个字符串元素代表一个数据列
  • @Index:用于为数据表定义索引
    • columnList:必要元素,设置哪些列建立索引,该属性的值可指定多个数据列的列名
    • name:非必须属性,设置该索引的名字
    • unique:设置该索引是否具有唯一性。该属性的值只能是boolean值
  • @Proxy:该注解的proxyClass属性指定一个接口,在延迟加载时作为代理使用,也可以在这里指定该类自己的名字
  • @DynamicInsert:指定用于插入记录的insert语句是否在运行时动态生成,并且只插入那些非空字段。该属性默认为false。开启后会导致Hibernate需要更多时间来生成SQL语句
  • @DynamicUpdate:与@DynamicInsert类同,动态生成,并且只更新那些改变的字段。当使用该注解后,持久化注解可以指定如下几种乐观锁定的策略:
    • OptimisticLockType.VERSION:检查version/timestamp字段。(性能最佳,也是唯一能够处理在Session外进行托管操作的策略)
    • OptimisticLockType.ALL:检查全部字段
    • OptimisticLockType.DIRTY:只检查修改过的字段
    • OptimisticLockType.NONE:不适用乐观锁定
  • @SelectBeforeUpdate:指定Hibernate在更新某个持久化对象之前是否需要先进行一次查询。该注解的value值为true,则Hibernate可以保证只有当持久化对象的状态被修改过时,才会使用update语句来保存其状态。该注解的value值默认为false。如果某个持久化对象的状态经常会发生改变,为了提高性能则应该设置为false。
  • @polymorphismType:当采用TABLE_PER_CLASS继承映射策略时,该注解用于指定是否需要采用隐士多态查询。默认为支持。
  • @Where:该注解的clause属性可指定一个附加的SQL语句过滤条件(类似于添加where子句),如果一旦指定了该注解,则不管采用load()、get()还是其他查询方法,只要试图加载该持久化类的对象时,该where条件就会生效。也就是说,只有符合该where条件的记录才会被加载
  • @BatchSize:当Hibernate抓去集合属性或延迟加载的实体时,该注解的size属性指定每批抓取的实例数
  • @OptimisticLocking:该注解的type属性指定乐观锁定策略。
  • @Check:该注解可通过constraints指定一个SQL表达式,用于为该持久化类所对应的表指定一个Check约束
  • @Subselect:该注解用于映射不可变的、只读实体。通俗来说,就是将数据库的子查询映射成Hibernate持久化对象。当需要使用视图(其实质就是一个查询)来代替数据表时,该注解比较有用

2.5.2 映射属性

默认情况下,被@Entity修饰的持久化类的所有属性都会被映射到底层数据表。为某个属性所映射的数据列的详细信息,可以在实体类中使用@Column修饰该属性。

@Column常用属性(所有属性非必须):

  • columnDefinition:该属性值是一个代表列定义的SQL字符串(列后面部分),指定创建该数据列的SQL语句
  • insertable:指定该列是否包含在Hibernate生成的insert语句的列列表中,默认为true
  • length:指定该列所能保存的数据的最大长度,默认值为25
  • name:指定该列的列名,该列的列名默认与@Column修饰的成员变量名相同
  • nullable:指定该列是否允许为null,默认值为true
  • precision:当该列是decimal类型时,该属性指定该列支持的最大有效数字位
  • scale:当该列是decimal类型时,该属性指定该列最大支持的小数位数
  • table:指定该列所属的表名,当需要用多个表来保存一个实体时,往往需要指定该属性
  • unique:指定该列是否具有唯一约定。默认值为false,即不具有唯一约束
  • updateble:指定该列是否包含在Hibernate生成的update语句的列列表中,默认值为true

除此之外,Hibernate还为属性映射提供了如下特殊的注解:

  • @Formula:该注解的value属性可指定一个SQL表达式,指定该属性的值将根据表达式来计算,持久化类对应的表中没有和计算属性对应的数据列——因为该属性值时动态计算出来的,无须保存到数据库。注意如下几点:
    • value="(sql)"的英文括号不能少
    • value=“()”的括号里面是SQL表达式,SQL表达式中的列名与表名都应该和数据库对应,而不是和持久化对象的属性对应
    • 如果需要在@Formula的value属性中使用参数,则直接使用where cur.id=currencyID形式,其中currencyID就是参数,当前持久化对象的currencyID属性将作为参数传入。
  • @Generated:设置该属性映射的数据列的值是否由数据库生成,该注解的value属性值可以为:
    • GenerationTime.NEVER:不由数据库生成
    • GenerationTime.INSERT:该属性值在执行insert语句时生成,但不会在执行update语句时重新生成
    • GenerationTime.ALWAYS:该属性值在执行insert和update语句时都会被重新生成

示例:

@Entity
@Table(name="news_inf")
public class News{
 // 消息类的标识属性
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Integer id;
 // 消息标题
 private String title;
 // 消息内容
 private String content;
 // 消息全部内容,由系统根据公式生成
 @Formula("(select concat(nt.title,nt.content)"
  + "from news_inf nt where nt.id= id)")
 private String fullContent;

 // 省略所有的setter和getter方法
 ...
}

当设定@Generated属性的持久化对象,每当执行insert或update语句时,Hibernate会立即执行一条select语句来获得该数据列的值,并将该值赋给持久化对象的该属性。

<1> 使用@Transient修饰不想持久保存的属性

默认情况下,持久化类的所有属性会自动映射到数据表的数据列,如果在实际应用中不想持久保存某些属性,则可以考虑使用@Transient来修饰它们。

<2> 使用@Enumerated修饰枚举类型的属性

在特殊情况下,持久类的属性是枚举类型,这意味着该属性只能接受有限的几个固定值。可以考虑使用@Enumerated修饰实体类中枚举类类型的属性。

底层数据库即可保存枚举值名称来代表枚举值,也可保存枚举值序号来代表枚举值,这一点可通过@Enumerated的value属性来指定,当@Enumerated的value属性为EnumType.STRING时,底层数据库保存枚举值的名称;当@Enumerated的value属性为EnumType.ORDINAL时,底层数据库保存枚举值的序号。这样我们就可以根据对应的方式来使用。

<3> 使用@Lob、@Basic修饰大数据类型的属性

当保存大容量数据数据时,数据库通常采用Blob、Clob类型来保存。Hibernate使用@Lob来修饰这种大数据类型,当持久化类的属性为byte[]、Byte[]或java.io.Serializable类型时,@Lob修饰的属性将映射为底层的Blob列;当持久化类的属性为char[]、Character[]或java.lang.String类型时,@Lob修饰的属性将映射为底层的Clob列。

对于使用@Lab修饰的大型数据类型,底层数据库往往会采用Blob或Clob类型的列来保存这种大数据类型的值。数据库加载这种大数据类型的值也是需要较大开销的。

Hibernate加载Person对象时并不立即加载它的大数据类型的属性,而是只加载一个“虚拟”的代理,等到程序真正需要该属性时才从底层数据表中加载数据——这就是典型的代理模式。Hibernate为这种机制提供了支持,并将这种 机制称为延迟加载,只需要在开发实体时使用@Basic修饰该属性即可。

@Basic可以指定如下属性:

  • fetch:指定是否延迟加载该属性
    • FetchType.EAGER:指定立即加载
    • FetchType.LAZY:指定使用延迟加载
  • optional:指定该属性映射的数据列是否允许使用null值

<4> 使用@Temporal修饰日期类型的属性

对于Java程序而言,表示日期、时间的类型只有两种:java.util.Date和java.util.Calendar;但对于数据库而言,表示日期、时间的类型就比较多了。Hibernate进行映射时,可以使用@Temporal来修饰这种类型的属性,使用@Temporal时刻指定一个value属性,该属性支持TemporalType.DATE、TemporalType.TIME、TemporalType.TIMESTAMP三个值之一,用于指定该属性映射到数据表的date、time和timestamp类型的类型列。

2.5.3 映射主键

通常情况下,Hibernate建议为持久化类定义一个标识属性,用于唯一地标识某个持久化实例,而标识属性则需要映射到底层数据表的主键。

现代的数据库建模理论都推荐不要使用具有实际意义的物理主键,而是推荐使用没有任何实际意义的逻辑主键。使用物理主键会增加数据库维护的复杂度,主从表之间的约束约束关系隐晦难懂,南与维护。逻辑主键没有实际意义,仅仅用来标识一行记录。Hibernate为这种逻辑主键提供了主键生成器,它负责为每个持久化实例生成唯一的逻辑主键值。

如果实体类的标识属性(映射成主键列)是基本数据类型、基本类型的包装类、String、Date等类型,可以使用@Id修饰该实体属性即可。使用@Id注解时无需指定任何属性。

如果希望Hibernate为逻辑主键自动生成主键值,则还应该使用@GeneratedValue来修饰实体的标识属性,使用@GeneratedValue时可指定如下属性:

  • strategy:非必需属性,指定Hibernate对该主键列使用怎样的主键生成策略,该属性支持四个属性值:
    • GenerationType.AUTO:Hibernate自动选择最适合底层数据库的主键生成策略,这是默认值
    • GenerationType.IDENTITY:对于MySQL、SQL Server这样的数据库,选择自增长的主键生成策略
    • GenerationType.SEQUENCE:对于Oracle这样的数据库,选择使用基于Sequence的主键生成策略。应与@SequenceGenerator一起使用
    • GenerationType.TABLE:使用辅助表来生成主键。应与@TableGenerator一起使用
  • generator:非必需属性,当使用GenerationType.SEQUENCE、GenerationType.TABLE主键生成策略时,该属性引用@SequenceGenerator、@TableGenerator所定义的生成器的名称

AUTO、IDENTITY两种策略都比较简单,而使用SEQUENCE、TABLE主键生成策略,则还需要结合@SequenceGenerator、@TableGenerator使用。@SequenceGenerator、@TableGenerator的功能大致相似,都用于定义主键生成器。

使用@SequenceGenerator定义的主键生成器还会在底层数据库中额外生成一个Sequence,因此必须底层数据库本身能支持机制(如Oracle数据库)。@SequenceGenerator包含如下属性:

  • name:必需属性,该属性指定该主键生成器的名称
  • allocationSize:非必需属性,该属性指定底层Sequence每次生成主键值的个数,对于Oracle而言,该属性指定的整数值,将作为定义Sequence时increment by的值
  • catalog:非必需属性,该属性指定将底层Sequence放入指定的catalog中
  • schema:非必需属性,该属性指定将底层Sequence放入指定的schema中
  • initialValue:非必需属性,该属性指定底层Sequence的初始值,对于Oracle而言,该属性指定的整数值将作为定义Sequence时start with的值
  • sequenceName:非必需属性,该属性指定底层Sequence的民称

使用@TableGenerator定义的主键生成器会在底层数据库中额外生成一个辅助表,该注解包含如下几个属性:

  • name:必需属性,该属性指定该主键生成器生成器的名称
  • allocationSize:非必需属性,该属性指定底层辅助表每次生成主键值的个数
  • catalog:非必需属性,该属性指定将辅助表放入指定的catalog中
  • schema:非必需属性,该属性指定将辅助表放入指定的schema中
  • table:非必需属性,指定辅助表的表名
  • initialValue:非必需属性,该属性指定的整数值将作为辅助表的初始值。默认值为:0
  • pkColumnName::非必需属性,该属性指定存放主键名的列表
  • pkColumnValue::非必需属性,该属性指定主键名
  • valueColumnValue::非必需属性,该属性指定存放主键值的列名
  • indexes:非必需属性,该属性值是一个@Index数组,用于为辅助表定义索引
  • uniqueConstraints:非必需属性,该属性值是一个@uniqueConstraint数组,用于为辅助表创建唯一约束

不管是@SequenceGenerator还是@TableGenerator,它们定义主键生成器时都需要通过name指定主键生成器的名称,该名称将作为@GeneratedValue注解的generator属性值。

示例如下:

@Entity
@Table(name="news_inf")
public class News{
 // 消息类的标识属性
 @Id
 // 定义主键生成器
 @TableGenerator(name="newsGen" , table="NEWS_ID_GEN",
  pkColumnName="gen_key", valueColumnName="gen_value",
  pkColumnValue="news_id")
 // 使用GenerationType.TABLE主键生成策略
 @GeneratedValue(strategy=GenerationType.TABLE
  , generator="newsGen")
 private Integer id;
 // 消息标题
 private String title;
 // 消息内容
 private String content;

 // id的setter和getter方法 
 // title的setter和getter方法 
 // content的setter和getter方法 
}

2.5.4 使用Hibernate的主键生成策略

JPA标准注解只只吃AUTO、IDENTITY、SEQUENCE和TABLE这四种主键生成策略,例如上面说的@GeneratedValue的strategy属性的值包含上面的四种策略。而Hibernate支持更多的主键生成策略,如果希望使用Hibernate提供的主键生成策略,就需要使用Hibernate本身的@GenericGenerator注解,该注解用于定义主键生成器的名称。@GenericGenerator注解主要支持如下两个属性:

  1. name:必需属性,设置该主键生成器的名称,该名称可以被@GeneratedValue的generator属性引用
  2. strategy:必需属性,设置该主键生成器的主键生成策略

@GenericGenerator的strategy属性可指定如下常用的主键生成策略:

  • increment:为long、short或者int类型主键生成唯一标识。只有在没有其他进程往同一个表中插入数据是才能使用。警告:在集群下不可使用!
  • identity:在DB2、MySQL、Microsoft SQL Server、Sybase和HypersonicSQL等提供identity(自增长)主键支持的数据表中使用。反悔的标识属性值时long、short或int类型的。
  • sequence:在DB2、PostgreSQL、Oracle、SAP DB、McKoi等提供Sequence支持的数据表中使用。返回的标识属性值是long、short或int类型的。
  • hilo:使用一个高/低位算法高效地生成long、short或int类型的标识符。给定一个表和字段(默认分别是hibernate_unique_key和next_hi)作为高位值的来源。高/低位算法生成的标识属性值只在一个特定的数据库中是唯一的。
  • seqhilo:使用一个高/低位算法高效地生成long、short或int类型的标识符,需要给定一个数据库Sequence名,该算法与hilo稍有不同,他将主键历史状态保存在Sequence中,适用于支持Sequence的数据库,如Oracle
  • uuid:用一个128位的UUID算法生成字符串类型的标识符,这在一个网络中是唯一的(IP地址也作为算法的数据源)。UUID被编码为一个32位十六进制数的字符串
  • uuid2:与上面的uuid类同,但是对应的生成器不同,其生成格式是一个36位十六进制数的字符串
  • guid:在Microsoft SQL Server和MySQL中使用数据库生成的GUID字符串
  • native:根据底层数据库的能力选择identity、sequence或者hilo中的一个
  • assigned:让应用程序在save()之前为对象分配一个标识符。这相当于不指定主键生成策略所采用的的默认策略。
  • select:通过数据库触发器选择某个唯一主键的行,并返回其主键值作为标识属性值。
  • foreign:表民直接使用另一个关联的对象的标识属性值(即本持久化对象不能生成主键)。这种主键生成器只在基于主键的1-1关联映射中才有用。

如果使用identity或sequence主键生成策略,则直接使用标准的@GeneratedValue注解即可。因此使用@GenericGenerator都会使用一些Hibernate所特有的注解,比如uuid或hilo之类的。示例如下:

@Entity
@Table(name="news_inf")
public class News
{
 // 消息类的标识属性
 @Id @Column(name="news_id")
 // 使用@GenericGenerator定义主键生成器。
 // 该主键生成器名为fk_hilo,使用Hibernate的hilo策略,
 @GenericGenerator(name="fk_hilo" , strategy="hilo")
 // 指定使用fk_hilo主键生成器
 @GeneratedValue(generator="fk_hilo")
 private Integer id;
 // 消息标题
 private String title;
 // 消息内容
 private String content;

 // id的setter和getter方法 
 // title的setter和getter方法
 // content的setter和getter方法 
}

2.5.5 映射集合属性

集合属性大致有两种:一种是单纯的集合属性,例如List、Set或数组等集合属性;另一种是Map结构的集合属性,每个属性值都由对应的key映射。

Hibernate要求持久化集合值字段必须为接口,实际的接口可以是java.util.Set、java.util.Collection、java.util.List、java.util.Map、java.util.SortedSet、java.util.SortedMap等,甚至是自定义类型(只需要实现org.hibernate.usertype.UserCollectionType接口即可)。

Hibernate之所以要求用集合接口来声明集合属性,是因为当程序持久化某个实例时,Hibernate会自动把程序中的集合实现类替换成Hibernate自己的集合实现类,因此不要试图把Hibernate集合属性强制类型转换为集合类,如HashSet、HashMap等,但可以转换为Set、Map等集合,因为Hibernate自己的集合类实现了Map、Set等接口。

集合类实例具有值类型的行为:当持久化对象被保存时,这些集合属性会被自动持久化;当持久化对象被删除时,这些集合属性对应的记录被自动删除。假设集合元素被从一个持久化对象传递到另一个持久化对象,该集合元素对应的记录会从一个表转移到另一个表。注:两个持久化对象不能共享同一个集合元素的引用。

不管哪种类型的集合属性,都统一使用@ElementCollection注解进行映射,其包含的属性如下:

  • fetch:非必需,指定该实体对集合属性的抓取策略(当程序初始化该实体时,是否立即从数据库抓取该实体的集合属性中的所有元素),默认值为:
    • FetchType.EAGER:立即抓取
    • FetchType.LAZY:延迟抓取
  • targetClass:该属性指定集合属性中集合元素的类型

由于集合属性总需要保存到另一个数据表中,所以保存集合属性的数据表必须包含一个外键列,用于参照到主键列,该外键使用@JoinColumn进行映射。

Hibernate使用标准的@CollectionTable注解映射保存集合属性表,该注解包含如下属性(全非必需):

  • name:指定保存集合属性的数据表的表名
  • schema:指定将保存集合属性的数据表放入指定catelog中
  • schema:指定将保存集合属性的数据表放入指定schema中
  • indexes:为持久化类所映射的表设置索引,该属性值是一个@Index注解数组
  • joinColums:该属性值为@JoinColumn数组,每个@JoinColumn映射一个外键列(通常只需要一个外键列即可,但如果主实体采用了复合主键,保存集合属性的表就需要定义多个外键列)
  • uniqueConstraints:为持久化类所映射的表设置唯一约束,该属性值是一个@UniqueConstraint注解组

@JoinColumn注解专门用于定义外键列,包含属性(全非必需)如下:

  • columnDefinition:指定Hibernate使用该属性值指定的SQL片段来创建外键列
  • name:指定该外键列的列名
  • insertable:指定该列是否包含在Hibernate生成的insert语句的列列表中。默认值:true
  • updateble:指定该列是否包含在Hibernate生成的update语句的列列表中。默认值:true
  • nullable:指定该列是否允许为null,该属性的默认值:true
  • table:指定该列所在数据表的表名
  • unique:指定是否为该列增加唯一约束
  • referencedColumnName:指定该列所参照的主键列的列名

当集合元素是基本数据类型、字符串类型、日期类型或其他复合类型时,因为这些集合元素都是从属于持久化对象的,而且这些数据类型在数据表中只需要一列就可保存,因此使用@Column注解定义集合元素列即可。

在Java的所有集合类型(包括数组、Map)中,只有Set集合是无序的,即没有显式的索引值。List、数组使用整数作为集合元素的索引值,而Map则使用key作为集合元素的索引。因此如果要映射带索引的集合(List、数组、Map),就需要为集合元素所在的数据表指定一个索引列——用于保存数组索引、List的索引、或者Map集合的key索引。

用于映射索引列的注解有如下两个:

  • @OrderColumn:用于定义List集合、数组的索引列
  • @MapKeyColumn:用于映射Map集合的索引列

@OrderColumn、@MapKeyColumn都用于映射索引列,因此这两个注解支持的属性与普通的@Column大致相似,在此不做赘述。如果程序需要显式指定Map key的类型,则可使用@MapKeyColumn注解,该注解只有一个value属性,该属性用于指定Map key的类型。

Hibernate集合元素的数据几乎可以是任何数据类型,包括基本类型、字符串类型、日期类型、自定义类型、复合类型,以及对其他持久化对象的引用。如果集合元素时基本类型、字符串类型、日期类型、自定义类型、复合类型等,则位于集合中的对象可能根据“值”语义来操作(其生命周期完全依赖于集合持有者,必须通过集合持有者来访问这些集合元素);如果集合元素是其他持久化对象的引用,此时就变成了关联映射,name这些集合元素都具有自己的声明周期。

综合所有情形,集合元素的类型大致可分为如下几种情况:

  • 集合元素时基本类型及其包装类、字符串类型和日期类型:此时使用@ElementCollection映射集合属性,并使用普通的@Column映射集合元素对应的列
  • 集合元素时组件(非持久化实体的复合类型):此时使用@ElementCollection映射集合属性,然后使用@Embeddable修饰非持久化实体的复合类
  • 集合元素时关联的持久化实体:此时已经不再是集合属性了,应该使用@OneToMany或@ManyToMany进行关联映射。

<1> List集合属性

List是有序集合,因此持久化到数据库时也必须增加一列来标识集合元素的次序。

示例:(持久化类Person中有一个集合属性:schools,该属性对应多个学校)

import org.hibernate.*;
import org.hibernate.service.*;
import org.hibernate.boot.registry.*;
import org.hibernate.cfg.*;

public class HibernateUtil {
 public static final SessionFactory sessionFactory;

 static {
  try {
   // 使用默认的hibernate.cfg.xml配置文件创建Configuration实例
   Configuration cfg = new Configuration().configure();
   // 以Configuration实例来创建SessionFactory实例
   ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(cfg.getProperties())
     .build();
   sessionFactory = cfg.buildSessionFactory(serviceRegistry);
  } catch (Throwable ex) {
   System.err.println("Initial SessionFactory creation failed." + ex);
   throw new ExceptionInInitializerError(ex);
  }
 }

 // ThreadLocal可以隔离多个线程的数据共享,因此不再需要对线程同步
 public static final ThreadLocal<Session> session = new ThreadLocal<Session>();

 public static Session currentSession() throws HibernateException {
  Session s = session.get();
  // 如果该线程还没有Session,则创建一个新的Session
  if (s == null) {
   s = sessionFactory.openSession();
   // 将获得的Session变量存储在ThreadLocal变量session里
   session.set(s);
  }
  return s;
 }

 public static void closeSession() throws HibernateException {
  Session s = session.get();
  if (s != null)
   s.close();
  session.set(null);
 }
}
import java.util.List;
import java.util.ArrayList;
import javax.persistence.*;

@Entity
@Table(name = "person_inf")
public class Person {
 @Id
 @Column(name = "person_id")
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 // 标识属性
 private Integer id;
 private String name;
 private int age;
 // 集合属性,保留该对象关联的学校
 @ElementCollection(targetClass = String.class)
 // 映射保存集合属性的表
 @CollectionTable(name = "school_inf", // 指定表名为school_inf
   joinColumns = @JoinColumn(name = "fk_person_id", nullable = false))
 // 指定保存集合元素的列为 school_name
 @Column(name = "school_name")
 // 映射集合元素索引的列
 @OrderColumn(name = "list_order")
 private List<String> schools = new ArrayList<>();

 // id的setter和getter方法
 // name的setter和getter方法 
 // age的setter和getter方法 
 // schools的setter和getter方法 
}
import org.hibernate.Transaction;
import org.hibernate.Session;
import org.crazyit.app.domain.*;
public class PersonManager {
 public static void main(String[] args) {
  PersonManager mgr = new PersonManager();
  mgr.createAndStorePerson();
  HibernateUtil.sessionFactory.close();
 }

 // 创建并保存Person对象
 private void createAndStorePerson() {
  // 打开线程安全的session对象
  Session session = HibernateUtil.currentSession();
  // 打开事务
  Transaction tx = session.beginTransaction();
  // 创建Person对象
  Person person = new Person();
  // 为Person对象设置属性
  person.setAge(20);
  person.setName("crazyit.org");
  // 向person的schools属性中添加2个元素
  person.getSchools().add("小学");
  person.getSchools().add("中学");
  session.save(person);
  tx.commit();
  HibernateUtil.closeSession();
 }
}

上面的代码执行后,如果没有对应表,且配置文件配置<property name="hbm2ddl.auto">update</property>,则会自动生成对应的表。数据库会生成两个数据表其中主表为person_inf,包含字段为person_id,age,name,其中person_id为主键;而其中List集合会生成对应的另外一个表school_inf,包含字段为fk_person_id,school_name,list_order,其中fk_person_id为主键,并且以外键的形式关联默认person_inf表的主键,而list_order保存了保存属于Person对象中集合的索引。
注意:上面Person类定义了List属性时使用了泛型来限制集合元素的类型,这样Hibernate可以通过反射来取得集合元素的数据类型,因此使用@ElementCollection注解时无须指定targetClass属性。但此处依然通过targetClass属性告诉Hibernate集合元素的类型,这样就避免了Hibernate自己去识别。

<2> 数组属性

Hibernate对数组和List的处理方式非常相似,实际上,List和数组也非常像,由于JDK1.5 增加了自动装箱、拆箱,它们用法的区别只是List的长度可以变化,而数组的长度不可变而已。

示例如下:

import javax.persistence.*;
@Entity
@Table(name = "person_inf")
public class Person {
 @Id
 @Column(name = "person_id")
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 // 标识属性
 private Integer id;
 private String name;
 private int age;
 // 集合属性,保留该对象关联的学校
 @ElementCollection(targetClass = String.class)
 // 映射保存集合属性的表
 @CollectionTable(name = "school_inf", // 指定表名为school_inf
   joinColumns = @JoinColumn(name = "person_id", nullable = false))
 // 指定保存集合元素的列为 school_name
 @Column(name = "school_name")
 // 映射集合元素索引的列
 @OrderColumn(name = "array_order")
 private String[] schools;

 // id的setter和getter方法 
 // name的setter和getter方法 
 // age的setter和getter方法 
 // schools的setter和getter方法

import org.hibernate.Transaction;
import org.hibernate.Session;
import org.crazyit.app.domain.*;
public class PersonManager {
 public static void main(String[] args) {
  PersonManager mgr = new PersonManager();
  mgr.createAndStorePerson();
  HibernateUtil.sessionFactory.close();
 }

 // 创建并保存Person对象
 private void createAndStorePerson() {
  // 打开线程安全的session对象
  Session session = HibernateUtil.currentSession();
  // 打开事务
  Transaction tx = session.beginTransaction();
  // 创建Person对象
  Person person = new Person();
  // 为Person对象设置属性
  person.setAge(20);
  person.setName("crazyit.org");
  // 创建String[]数组
  String[] schools = new String[] { "小学", "中学" };
  person.setSchools(schools);
  session.save(person);
  tx.commit();
  HibernateUtil.closeSession();
 }
}

<3> Set集合属性

Set集合属性的映射与List有点不同,但因为Set是无序、不可重复的集合,因此Set集合属性无须用@OrderColumn注解映射集合元素的索引列。与映射List集合相同的是,映射Set集合同样需要使用@ElementCollection映射集合属性,使用@CollectionTable映射保存集合属性的表,如果集合元素是基本类型及其包装类、String、Date等类型,也可使用@Column映射保存集合元素的数据列。

注意:声明Set集合属性时,只能使用Set接口,不能使用实现类。

示例如下:

import java.util.Set;
import java.util.HashSet;
import javax.persistence.*;
@Entity
@Table(name = "person_inf")
public class Person {
 @Id
 @Column(name = "person_id")
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 // 标识属性
 private Integer id;
 private String name;
 private int age;
 // 集合属性,保留该对象关联的学校
 @ElementCollection(targetClass = String.class)
 // 映射保存集合属性的表
 @CollectionTable(name = "school_inf", // 指定表名为school_inf
   joinColumns = @JoinColumn(name = "person_id", nullable = false))
 // 指定保存集合元素的列为 school_name,nullable=false增加非空约束
 @Column(name = "school_name", nullable = false)
 private Set<String> schools = new HashSet<>();
 // id的setter和getter方法 
 // name的setter和getter方法 
 // age的setter和getter方法
 // schools的setter和getter方法 
}

<4> Map集合属性

Map集合属性同样需要使用@ElementCollection映射集合属性,使用@CollectionTable映射保存集合属性的数据表,如果Map的value是基本类型及其包装类、String或Date类型永阳也可使用@Column映射保存Map value的数据列。除此之外,程序需要使用@MapKeyColumn映射保存Map key的数据列。

Hibernate将以外键列和key列作为联合主键。与所有集合属性类似的是,集合属性的声明只能使用接口,但程序依然需要显式初始化该集合的属性。

示例如下:

import java.util.HashMap;
import java.util.Map;
import javax.persistence.*;
@Entity
@Table(name = "person_inf")
public class Person {
 @Id
 @Column(name = "person_id")
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 // 标识属性
 private Integer id;
 private String name;
 private int age;
 // 集合属性,保留该对象关联的考试成绩
 @ElementCollection(targetClass = Float.class)
 // 映射保存集合属性的表
 @CollectionTable(name = "score_inf", // 指定表名为score_inf
   joinColumns = @JoinColumn(name = "person_id", nullable = false))
 @MapKeyColumn(name = "subject_name")
 // 指定Map key的类型为String类型
 @MapKeyClass(String.class)
 // 映射保存Map value的数据列
 @Column(name = "mark")
 private Map<String, Float> scores = new HashMap<>();

 // id的setter和getter方法
 // name的setter和getter方法 
 // age的setter和getter方法 
 // scores的setter和getter方法 
}

实例中scores属性时Map类型的属性,为了映射该Map类型的集合属性,程序除了使用@ElementCollection、@CollectionTable、@MapColumn之外还使用了@MapKeyColumn映射保存Map key的数据列,并通过@MapKeyClass指定Map key的类型。虽然程序定义Person类时使用了泛型来限制Map集合的key、value的类型,但程序中依然通过注解强制执行Map key、Map value的类型,这样可以避免Hibernate通过反射区获取,从而提升了程序性能。

2.5.6 集合属性的性能分析

如果集合属性里包含十万甚至百万的记录,在初始化持久化实体时,立即抓取所有的集合属性,将导致性能急剧下降。完全有可能系统只需要使用持久化类集合属性中的部分记录,而不是集合属性的全部,这样就没必要一次加载所有的集合属性。对于集合属性,通常推荐使用延迟加载策略,Hibernate对集合属性默认采取的也是延迟加载策略,在某些特殊情况下,为@ElementCollection注解设置fetch=FetchType.EAGER来采取延迟加载。

有序集合的元素可以根据key或index访问,无序集合的元素只能遍历。因此有序集合效率比较高,显然有序集合在增加、删除、修改中拥有较好的性能表现。对于多对多关联、值数据的集合而言,有序集合比Set多一个好处——因为Set集合内部结构的原因,如果“改变”Set集合的某个元素,Hibernate并不会立即更新(update)该元素对应的数据行。因此对于Set集合而言,只有在执行插入(insert)和删除(delete)操作时“改变”才有效。

注意:虽然数组也是有序集合,但数组无法使用延迟加载(因为数组的长度不可变),所以实际用数组作为集合的性能并不高,通常认为List、Map集合性能较高,而Set则紧随其后。

当删除集合的全部元素时,Hibernate时比较智能的,直接调用List集合的clear()方法 删除所有元素,而不是一个个删除,但是如果删除的是部分而不是全部,则没有那么智能了,会一个个删除,如果删除大部分则效率就不高了,此时需要一点小小的技巧,就可前置Hibernate先执行一条delete语句删除全部集合元素,在执行一条insert语句插入希望剩下的集合元素。如下:(暂时测试没有达到预期效果,待定

List<String> tmp = person.getSchools();
//强制person的schools集合属性为null
//Hibernate将调用delete语句删除person关联的全部集合元素
person.setSchools(null);
//此处采用循环方式删除tmp集合中的19个元素
//但此时的tmp集合与person实体无关,因此不会产生delete语句
。。。
//再次将tmp集合设置成person的schools属性
//Hibernate将只需要一条insert语句即可插入希望剩下的记录
person.setSchools(tmp);

集合属性表里的记录完全“从属”于主表的实体,当主表的记录被删除时,集合属性表里“从属”于该记录的数据将会被删除;Hibernate无法直接加载、查询集合属性表中的记录,只能限价在主表实体,在通过主表实体去获取集合属性对应的记录。

2.5.7 有序集合映射

Hibernate还支持使用SortedSet和SortedMap两个有序集合,当需要映射这种有序集合时,只有使用Hibernate本身提供的@SortNatural或@SortComparator注解,其中前者表明对集合元素采用自然排序,后者表明对集合元素采用定制排序,因此使用@SortComparator时必须指定value属性,该属性值为Comparator实现类。Hibernate的有序集合与java.util.TreeSet或java.util.TreeMap的行为非常相似。下面以SortedSet为例:

@Entity
@Table(name = "person_inf")
public class Person {
 @Id
 @Column(name = "person_id")
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 // 标识属性
 private Integer id;
 private String name;
 private int age;
 // 有序集合属性
 @ElementCollection(targetClass = String.class)
 // 映射保存集合元素的表
 @CollectionTable(name = "training_inf", joinColumns = @JoinColumn(name = "person_id", nullable = false))
 // 定义保存集合元素的数据列
 @Column(name = "training_name", nullable = false)
 // 使用@SortNatural指定使用自然排序
 @SortNatural
 private SortedSet<String> trainings = new TreeSet<>();

 // id的setter和getter方法
 // name的setter和getter方法
 // age的setter和getter方法 
 // trainings的setter和getter方法 
}
private void createAndStorePerson() {
 Session session = HibernateUtil.currentSession();
 Transaction tx = session.beginTransaction();
 // 创建Person对象
 Person wawa = new Person();
 wawa.setAge(21);
 wawa.setName("crazyit.org");
 // 为trainings集合属性添加2个元素
 wawa.getTrainings().add("Wild Java Camp");
 wawa.getTrainings().add("Sun SCJP");
 session.save(wawa);
 Person p = (Person) session.get(Person.class, 1);
 // 再次添加一个集合元素
 p.getTrainings().add("CCNP");
 tx.commit();
 HibernateUtil.closeSession();
}

在添加“CCNP”时修改的是持久化状态的Person对象,所以无须显式调用save()方法来保存修改,最终生成结果虽然“CCNP”是最后添加的,但是还是会按照顺序排在最前面。如果希望数据库查询自己对集合元素排序,则可以利用Hibernate自己提供的@OrderBy注解,该注解只能在JDK1.4及其以上使用(因为底层需要李荣LinkedHashSet或LinkedHashMap来实现),它会在SQL查询中完成排序,而不是在内存中完成排序。

示例:@OrderBy("training_name desc")//获取集合属性时,在生成的SQL语句中会添加order by training desc子句。

2.5.8 映射数据库对象

如果希望在映射文件中创建和删除触发器、存储过程等数据库对象,Hibernate提供了<database-object>元素来满足这种需求。借助Hibernate的Schema交互工具,就可让Hibernate映射文件拥有完全定义用户Schema的能力。

注意:映射数据库对象这种功能目前无法用注解来实现,只能通过传统的*.hbm.xml映射文件的方式来实现。

这部分咱不理————》》》》》后续完善,参考《JavaEE 轻量级企业应用 第四版》p422

SchemaExport工具类的用途很丰富,详细可参考API。

2.6 映射组件属性

组件属性的意思是,持久化类的属性并不是基本数据类型,也不是字符串、日期等标量数据的变量,而是一个复合类型的对象,在持久化过程中,它仅仅被当做值类型,而并非引用另一个持久化实体。组件属性的类型可以是任何自定义类。

2.6.1 在组件类上使用@Embeddable注解

实体类Person:

@Entity
@Table(name = "person_inf")
public class Person {
 @Id
 @Column(name = "person_id")
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Integer id;
 private int age;
 // 组件属性name
 private Name name;

 // id的setter和getter方法
 // age的setter和getter方法
 // name的setter和getter方法 
}

Person的name属性不是基本数据类型,而是自定义类,Hibernate无法使用单独的数据列来保存Name对象,因此不能直接使用@Column注解来映射name属性。为了让Hibernate知道Name将会作为组件类型使用,可以使用@Embeddable注解,该注解与@Entity类似,都不需要任何属性,只是@Entity修饰的类将作为组件类型使用,而@Embeddable修饰的类将作为持久化类的组件使用。

组件Name:

@Embeddable
public class Name {
 // 定义first成员变量
 @Column(name = "person_firstname")
 private String first;
 // 定义last成员变量
 @Column(name = "person_lastname")
 private String last;
 // 引用拥有该Name的Person对象
 @Parent //告诉Hibernate这个组件的所有者是谁
 private Person owner;
 // 无参数的构造器
 public Name() {
 }
 // 初始化全部成员变量的构造器
 public Name(String first, String last) {
  this.first = first;
  this.last = last;
 }
 // first的setter和getter方法 
 // last的setter和getter方法 
 // owner的setter和getter方法
}

其中Parent注解说明的是该组件的所有者是谁。

private void createAndStorePerson() {
 Session session = HibernateUtil.currentSession();
 Transaction tx = session.beginTransaction();
 // 创建Person对象
 Person person = new Person();
 // 为Person对象设置属性
 person.setAge(29);
 // 设置组件属性
 person.setName(new Name("crazyit.org", "疯狂Java联盟"));
 session.save(person);
 tx.commit();
 HibernateUtil.closeSession();
}

执行结果如下图:

2.6.2 在持久化类中使用@Embedded注解

如果需要为组件属性所包含的子属性指定列名,则可使用@AttributeOverrides和@AttributeOverride注解,其中@AttributeOverride指定一个属性的映射配置,如果是多个则使用@AttributeOverrides注解来管理多个属性的映射配置。

@AttributeOverride支持的属性

  • name:必需,指定对组件类的哪个属性进行配置
  • column:必需,指定该属性所映射的数据列的列名

示例如下:

@Entity
@Table(name="person_inf")
public class Person{
 @Id @Column(name="person_id")
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 private Integer id;
 private int age;
 // 组件属性name
 @Embedded
 @AttributeOverrides({
  @AttributeOverride(name="first", column = @Column(name="person_firstname")),
  @AttributeOverride(name="last", column = @Column(name="person_lastname"))
 })
 private Name name;

 // id的setter和getter方法 
 // age的setter和getter方法
 // name的setter和getter方法
}

注:Name组件中仍需要使用@parent注解指出所属与谁。

2.7 映射组件属性(二)

2.7.1 组件属性为集合

如果组件类又包括了List、Set、Map等集合属性,则可直接在组件类中使用@ElementCollection修饰集合属性,并使用@CollectionTable指定保存集合属性的数据表——与普通实体类中映射集合属性的方式基本相同。

2.7.2 集合属性的元素为集合

对于集合元素是组件的集合属性,程序依然使用@ElementCollection修饰集合属性,使用@CollectionTable指定保存集合属性的数据表。对于带索引的集合,如果是List集合,则使用@OrderColumn映射索引列;如果是Map集合,则使用@MapKeyColumn映射索引列。

不同的是,程序不再使用@Column映射保存集合元素(组件类型)的数据列——Hibernate无法使用单独的数据列保存集合元素(组件类型),程序只要使用@Embeddable修饰组件类即可。

示例如下:

组件Name:

@Embeddable
public class Name {
 // 定义first成员变量
 @Column(name = "person_firstname")
 private String first;
 // 定义last成员变量
 @Column(name = "person_lastname")
 private String last;
 // 引用拥有该Name的Person对象
 @Parent
 private Person owner;

 // 无参数的构造器
 public Name() {
 }

 // 初始化全部成员变量的构造器
 public Name(String first, String last) {
  this.first = first;
  this.last = last;
 }

 // first的setter和getter方法 
 // last的setter和getter方法
 // owner的setter和getter方法 
}

组件Score:

@Embeddable
public class Score {
 // 定义first成员变量
 @Column(name = "score_level")
 private String level;
 // 定义last成员变量
 @Column(name = "score_mark")
 private Integer mark;
 // 引用拥有该Name的Person对象
 @Parent
 private Person owner;

 // 无参数的构造器
 public Score() {
 }

 // 初始化全部成员变量的构造器
 public Score(String level, Integer mark) {
  this.level = level;
  this.mark = mark;
 }

 // level的setter和getter方法
 // mark的setter和getter方法 
 // owner的setter和getter方法 
}

持久化类Person:

@Entity
@Table(name = "person_inf")
public class Person {
 @Id
 @Column(name = "person_id")
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Integer id;
 private int age;
 // Map集合元素是组件
 @ElementCollection(targetClass = Score.class)
 @CollectionTable(name = "score_inf", joinColumns = @JoinColumn(name = "person_id", nullable = false))
 @MapKeyColumn(name = "subject_name")
 @MapKeyClass(String.class)
 private Map<String, Score> scores = new HashMap<>();
 // List集合元素是组件
 @ElementCollection(targetClass = Name.class)
 @CollectionTable(name = "nick_inf", joinColumns = @JoinColumn(name = "person_id", nullable = false))
 @OrderColumn(name = "list_order")
 private List<Name> nicks = new ArrayList<>();

 // id的setter和getter方法 
 // age的setter和getter方法
 // nicks的setter和getter方法 
 // scores的setter和getter方法 
}

测试:

public class PersonManager {
 public static void main(String[] args) {
  PersonManager mgr = new PersonManager();
  mgr.createAndStorePerson();
  HibernateUtil.sessionFactory.close();
 }

 private void createAndStorePerson() {
  Session session = HibernateUtil.currentSession();
  Transaction tx = session.beginTransaction();
  // 创建Person对象
  Person person = new Person();
  // 为Person对象设置属性
  person.setAge(29);
  // 创建一个Map集合
  Map<String, Name> nicks = new HashMap<String, Name>();
  // 向List集合里放入Name对象
  person.getNicks().add(new Name("Wawa", "Wawa"));
  person.getNicks().add(new Name("Yeeku", "Lee"));
  // 向List集合里放入Score对象
  person.getScores().put("语文", new Score("良好", 85));
  person.getScores().put("数学", new Score("优秀", 92));
  session.save(person);
  tx.commit();
  HibernateUtil.closeSession();
 }
}

2.7.3 组件为Map的索引

由于Map集合的特殊性,他允许使用符合类型的对象作为Map的key,所以Hibernate也对这种组件作为Map key的情形提供支持。对于这种情形,程序依然使用@ElementCollection修饰集合属性,使用@CollectionTable映射保存集合属性的表。对于带索引的集合,如果是List集合,则使用@OrderColumn映射索引列;如果是Map集合,则使用@MapKeyColumn映射索引列。

不同的是,由于此时Map key是组件类型,因此建议使用@MapKeyClass注解指定Map key的类型。示例:(Person类包含一个Map类型的集合属性,该Map集合的key是Name组件类型)

持久化对象Person:

@Entity
@Table(name = "person_inf")
public class Person {
 // 标识属性
 @Id
 @Column(name = "person_id")
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Integer id;
 private int age;
 // 集合属性nickPower
 @ElementCollection(targetClass = Integer.class)
 @CollectionTable(name = "nick_power_inf", joinColumns = @JoinColumn(name = "person_id", nullable = false))
 @Column(name = "nick_power", nullable = false)
 // 指定Map key的类型
 @MapKeyClass(Name.class)
 private Map<Name, Integer> nickPower = new HashMap<Name, Integer>();

 // id的setter和getter方法 
 // age的setter和getter方法 
 // nickPower的setter和getter方法 
}

上面的持久化类中使用了Name对象作为Map的key,所以程序应该重写Name类的equals()和hashCode()两个方法。Name类源码如下:

@Embeddable
public class Name {
 // 定义first成员变量
 @Column(name = "person_firstname")
 private String first;
 // 定义last成员变量
 @Column(name = "person_lastname")
 private String last;
 // 引用拥有该Name的Person对象
 @Parent
 private Person owner;

 // 无参数的构造器
 public Name() {
 }

 // 初始化全部成员变量的构造器
 public Name(String first, String last) {
  this.first = first;
  this.last = last;
 }

 // first的setter和getter方法
 // last的setter和getter方法
 // owner的setter和getter方法
 // 重写equals()方法,根据first、last进行判断
 public boolean equals(Object obj) {
  if (this == obj) {
   return true;
  }
  if (obj != null && obj.getClass() == Name.class) {
   Name target = (Name) obj;
   return target.getFirst().equals(getFirst()) && target.getLast().equals(getLast());
  }
  return false;
 }

 // 重写hashCode方法,根据first、last计算hashCode值
 public int hashCode() {
  return getFirst().hashCode() * 31 + getLast().hashCode();
 }
}

2.7.4 组件作为复合主键

如果数据库采用简单的逻辑主键,则不会出现组件类型的主键。但在一些特殊的情况下,总会出现组件类型主键,Hibernate也为这种组件类型的主键提供了支持。

使用组件作为复合主键,也就是使用组件作为持久化类的标识符,则该组件类必须满足以下要求:

  • 有无参数的构造器
  • 必须实现java.io.Serializable接口
  • 建议正确地重写equals()和hashCode()方法,也就是根据组件类的关键属性来区分组件对象

注:当使用组件作为复合主键时,Hibernate无法为这种复合主键自动生成主键值,所以程序必须为持久化实例分配这种组件标识符。

示例:

Name组件类:

public class Name implements java.io.Serializable {
 // 定义first成员变量
 private String first;
 // 定义last成员变量
 private String last;

 // 无参数的构造器
 public Name() {
 }

 // 初始化全部成员变量的构造器
 public Name(String first, String last) {
  this.first = first;
  this.last = last;
 }

 // 省略setter和getter方法
 ...
 // 重写equals()方法,根据first、last进行判断
 public boolean equals(Object obj) {
  if (this == obj) {
   return true;
  }
  if (obj != null && obj.getClass() == Name.class) {
   Name target = (Name) obj;
   return target.getFirst().equals(getFirst()) && target.getLast().equals(getLast());
  }
  return false;
 }

 // 重写hashCode()方法,根据first、last计算hashCode值
 public int hashCode() {
  return getFirst().hashCode() * 31 + getLast().hashCode();
 }
}

因为Name类型的属性将作为标识属性,所以Name类应实现java.io.Serializable接口,并正确重写equals()和hashCode()方法。程序中使用Name组件作为主键,来唯一标识 ,能唯一标识的应该是first和last两个属性,因此重写equals()和hashCode()方法都根据这两个成员变量来判断。

当持久化类使用组件作为复合主键时,程序需要使用@EmbeddedId来修饰该主键。该注解与@Embedded用法类似,只是@Embedded用于修饰普通的组件属性,而@EmbeddedId用于修饰组件类型的主键。使用@EmbeddedId时同样可以结合@AttributeOverrides和@AttributeOverride两个注解。

持久化类Person:

@Entity
@Table(name = "person_inf")
public class Person {
 // 以Name组件作为标识属性
 @EmbeddedId
 @AttributeOverrides({
   // 指定
   @AttributeOverride(name = "first", column = @Column(name = "person_firstname")),
   @AttributeOverride(name = "last", column = @Column(name = "person_lastname")) })
 private Name name;
 private int age;

 // 省略setter和getter方法
 ...
}

测试:

private void createAndStorePerson() {
 Session session = HibernateUtil.currentSession();
 Transaction tx = session.beginTransaction();
 // 创建Person对象
 Person person = new Person();
 // 为Person对象设置属性
 person.setAge(21);
 // 创建一个Name对象作为Person对象的标识属性值
 person.setName(new Name("crazyit.org", "疯狂Java联盟"));
 session.save(person);
 tx.commit();
 HibernateUtil.closeSession();
}

2.7.5 多列作为联合主键

Hibernate还提供了另一种联合主键支持,Hibernate允许直接将持久化的多个属性映射成联合主键。如果需要直接将持久化类的多列映射成联合组件,则该持久化类必须满足如下条件:

  • 有无参数的构造器
  • 实现java.io.Serializable接口
  • 建议根据联合主键列所映射的属性来重写equals()和hashCode()方法

将持久化类的多个属性映射为联合主键非常简单,直接使用多个@Id修饰这些属性即可。示例如下:

@Entity
@Table(name="person_inf")
public class Person implements java.io.Serializable{
 // 定义first属性,作为标识属性的成员
 @Id
 private String first;
 // 定义last属性,作为标识属性的成员
 @Id
 private String last;
 private int age;

 // first的setter和getter方法
 // last的setter和getter方法
 // age的setter和getter方法

 // 重写equals()方法,根据first、last进行判断
 public boolean equals(Object obj){
  if (this == obj)
  {
   return true;
  }
  if (obj != null && obj.getClass() == Person.class)
  {
   Person target = (Person)obj;
   return target.getFirst().equals(getFirst())
    && target.getLast().equals(getLast());
  }
  return false;
 }

 // 重写hashCode()方法,根据first、last计算hashCode值
 public int hashCode(){
  return getFirst().hashCode() * 31
   + getLast().hashCode();
 }
}

&#8195&#8195

最后修改时间:2017年5月10日23:21:50

********************************************************************************结束语********************************************************************************************
我在写这篇博客的时候也是一名初学者,有任何疑问或问题请留言,或发邮件也可以,邮箱为:fanxiaobin.fxb@qq.com,我会尽早的进行更正及更改。
在我写过的博客中有两篇博客是对资源的整理,可能对大家都有帮助,大家有兴趣的话可以看看!!
下载资料整理——目录http://blog.csdn.net/fanxiaobin577328725/article/details/51894331
这篇博客里面是我关于我见到的感觉不错的好资源的整理,里面包含了书籍及源代码以及个人搜索的一些资源,如果有兴趣的可以看看,我会一直对其进行更新和添加。
优秀的文章&优秀的学习网站之收集手册http://blog.csdn.net/fanxiaobin577328725/article/details/52753638
这篇博客里面是我对于我读过的,并且感觉有意义的文章的收集整理,纯粹的个人爱好,大家感觉有兴趣的可以阅读一下,我也会时常的对其进行更新。
********************************************************************************感谢********************************************************************************************

分享到 :
0 人收藏
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

积分:1136255
帖子:227251
精华:0
期权论坛 期权论坛
发布
内容

下载期权论坛手机APP