Hibernate 5.3 (二)

本文详细介绍了Hibernate 5.3中的SessionFactory、Configuration、数据库连接属性、方言、常用配置属性及持久化类的相关概念。讨论了持久化类的状态转换、Hibernate映射文件的结构、集合映射的性能分析和映射数据库对象的方法。通过示例解释了持久化类的映射、主键和普通属性的映射,以及集合类型如List、Set和Map的映射策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SeesionFactory

我们都知道Hibernate 进行持久化操作离不开SessionFactory对象,这个对象是整个数据库映射文件经过编译后的内存镜像,该对象的openSeesion方法可打开Session对象。该对象通常由Configuration对象的buildSessionFactory来产生。
方法

Configuration

每一个Hibernate 的配置都对应一个Configuration对象,在极端情况下,不使用任何配置文件,也可创建Configuration对象。

随着Hibernate 所使用的配置文件不同,创建Configuration的方式也不同。

  • 使用hibernate.properties文件作为配置文件。

这种配置方法可以去参考hibernate 文件包下的project/etc,给出每个常见数据库连接的属性。

 Configuration con1 = new Configuration();
	   con1.addResource("News.hbm.xml");

这种配置文件,可以看出没有添加映射文件的方式,所以必须手动通过addResource 方法去添加映射文件,这很费事。在实际开发的时候,不建议使用这种。

  • 使用Configure.cfg.xml 作为配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!-- 导入hibernate配置文件的标签 -->
<!DOCTYPE hibernate-configuration PUBLIC
 "-//Hibernate/Hibernate Mapping DTD 5.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>
        <property name="connection.url">jdbc:mysql://localhost/test</property>
        <property name="connection.username">root</property>
        <property name="connection.password">123456</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 resource="myhibernate.hbm.xml"/>//在这你就可以添加你映射的文件
        </session-factory>
        </hibernate-configuration>

Configuration con = new Configuration().configure();
  • 不用配置文件去创建Configuration 实例
// 实例化Configuration,不加载任何配置文件
		Configuration conf = new Configuration()
			con.addResource("News.hbm.xml");//添加映射类
			// 通过setProperty设置Hibernate的连接属性。
			.setProperty("hibernate.connection.driver_class"
				, "com.mysql.jdbc.Driver")
			.setProperty("hibernate.connection.url"
				, "jdbc:mysql://localhost/hibernate")
			.setProperty("hibernate.connection.username" , "root")
			.setProperty("hibernate.connection.password" , "32147")
			.setProperty("hibernate.c3p0.max_size" , "20")
			.setProperty("hibernate.c3p0.min_size" , "1")
			.setProperty("hibernate.c3p0.timeout" , "5000")
			.setProperty("hibernate.c3p0.max_statements" , "100")
			.setProperty("hibernate.c3p0.idle_test_period" , "3000")
			.setProperty("hibernate.c3p0.acquire_increment" , "2")
			.setProperty("hibernate.c3p0.validate" , "true")
			.setProperty("hibernate.dialect"
				, "org.hibernate.dialect.MySQL5InnoDBDialect")
			.setProperty("hibernate.hbm2ddl.auto" , "update");
jdbc 连接的常用属性
hibernate.dialect 	classname of org.hibernate.dialect.Dialect subclass
hibernate.connection.username	database username
hibernate.connection.password	database password
hibernate.connection.url 	JDBC URL (when using java.sql.DriverManager)
hibernate.connection.driver_class 	classname of JDBC driver
hibernate.connection.pool_size 	the maximum size of the connection pool (only when using java.sql.DriverManager)
hibernate.connection.datasource 	databasource JNDI name (when using javax.sql.Datasource)
hibernate.jndi.url	JNDI InitialContext URL
hibernate.jndi.class	JNDI InitialContext classname
hibernate.max_fetch_depth 	maximum depth of outer join fetching
hibernate.jdbc.batch_size 	enable use of JDBC2 batch API for drivers which support it
hibernate.jdbc.fetch_size 	set the JDBC fetch size
hibernate.hbm2ddl.auto 	enable auto DDL export
hibernate.default_schema 	use given schema name for unqualified tables (always optional)
hibernate.default_catalog 	use given catalog name for unqualified tables (always optional)
hibernate.session_factory_name 	If set, the factory attempts to bind this name to itself in the 

这里给出的是hiernate.properties里面配置属性,对于hibernate.cfg.xml和hibernate.properties两种设置属性不同,使用的自行百度查找,形式不同,本质一样。
我只是举出一些,具体的属性在Hibernate的api下environment.html。
Hibernate自带连接池仅有测试功能,在实际项目中,你还是需要使用,C3P0或者Proxool连接池,只需要做如下修改,代替hibernate.connection.pool_size即可:

		<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>
数据库方言

Hibernate 底层依然是使用标准的数据库进行操作的,所有的数据库都是支持SQL的标准的,但是在它上面进行了封装,所有语法上有了差异,因此Hibernate需要根据数据库来识别这些差异。我们在把项目进行迁移不同数据库的时候,访问数据库会有差异,我们必须告诉Hibernate我们使用的数据库,就是这样借助于数据库方言去实现的。

至于,每一种数据库对应的方言,在这我就不叙说,有问题,自己百度去查找。

常用的配置属性
  • 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-drop值, 显示关闭SessionFactory时,将Drop刚建的数据表。该属性可以为update、create 和create-drop三个值。

这里我们需要主要说明一下:

create:每次加载hibernate时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
(注意这种方式,每次对于表结构也是重新开始的创建,数据全部删除)
create-drop :每次加载hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除。
update:最常用的属性,第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据 model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。**要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。(**这个相当于表的内部结构如字段类型主键等,应该是在一开始就设置好的,不可以半途去修改,(除了)但是可以添加新的字段或者删除字段,这个是可以的。数据是可以在这几处上增加)

那我们有没有办法修改表的内部结构,就是将该属性设置create,然后对你需要的表做一次数据修改,它的表结构就会重构,此时不影响其他的,但是如果设置外键等,执行的时候可能会报一些错其他(table 已经存在),这并不影响。修改好了,还是他要将还属性设置为update。

hibernate.jdbc.fetch_size 50 //读

Fetch Size 是设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数。

例如一次查询1万条记录,对于Oracle的JDBC驱动来说,是不会1次性把1万条取出来的,而只会取出Fetch Size条数,当纪录集遍历完了这些记录以后,再去数据库取Fetch Size条数据。

hibernate.jdbc.batch_size 30 //写

Batch Size是设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,有点相当于设置Buffer缓冲区大小的意思。

需要注意:就是这两个属性不是对每一个数据库都对这两个属性支持,比如mysql就不支持。

持久化类

持久化类基本是普通、传统的Java 对象。

持久化的要求
  • 提供一个无参的构造函数。
  • 提供一个标识的属性,标识属性通常会映射成数据库表的主键。(这里建议,标识属性应该使用引用类型)
  • 会持久化的属性提供set、get方法。
  • 重新写equals和hashcode方法。(如果自动生成标识值的(也就是主键自动生成),只有在持久化的状态,才会有标识值,而如果没有持久化,怎么比较两个对象的实例)
持久化的状态
  • 瞬态:对象由new操作符创建,且尚未与HibernateSession关联的对象被认为处于瞬态。瞬态对象不会被持久化到数据库中,也不会被赋予持久化标识。如果程序中失去了瞬态对象的引用,瞬态对象将被垃圾回收机制销毁。使用Hibernate Session可以将其变为持久化状态。
  • 持久化:持久化实例在数据库中有对应的记录,并拥有一一个持久化标识(identifer)。持久化的实例可以是刚刚保存的, 也可以是刚被加载的。无论哪一种,持久化对象都必须与指定的Hibernate Session关联。Hibernate 会检测到处于持久化状态对象的改动,在当前操作执行完成时将对象数据写回数据库。开发者不需要手动执行UPDATE。
  • 脱管:某个实例曾经处于持久化状态,但随着与之关联的Session被关闭,该对象就变成脱管状态。脱管对象的引用依然有效,对象可继续被修改。如果重新让脱管对象与某个Session关联,这个脱管对象会重新转换为持久化状态,而脱管期间的改动也不会丢失,也可被写入数据库。正是因为这个功能,逻辑上的长事务成为可能,它被称为应用程序事务。即事务可以跨越用户的思考,因为当对象处于脱管状态时,对该对象的操作无须锁定数据库,不会造成性能的下降。
改变持久化状态的方法

这里写图片描述

持久化常用的方法
  • save
  • persist

这两个方法都是将对象进行持久化操作,把数据保存到数据库中。指定标识值(如果主键的标识值不是自动生成的),需要在save前,进行手动赋值。调用这两个方法的时候,会在底层执行insert语句,插入到数据库。此外,save方法调用的时候返回的是持久化对象的标识符,但是调用persist方法没有任何返回值。

  • load
    来加载一个持久化实例,这种加载就是根据持久化类的标识属性值加载持久化实例一其实质就是根据主键从数据表中加载一条新记录。

方法可能抛出HibernateException 异常,如果我们在类映射文件中指定了延迟加载,则load0方法会返回一个未初始化的代理对象(可以理解为持久化对象的替身),这个代理对象并没有装载数据记录,直到程序调用该代理对象的某方法时,Hibermate 才会去访问数据库。

  • get
    该方法也用于根据主键装载持久化实例,但get方法会立刻访问数据库,如果没有对应的记录,get0方 法返回null,而不是返回一个代理对象。

当程序通过load或get方法加载实体时,Hibermate 会在底层对应地生成一条 select 语句,这条select语句带有“where <主键列>= <标识属性值>”子句,表明将会根据主键加载。

load方法和get方法的王要区别在于定否延达加载,使用load方法将具有延达加载功能,load方法不会立即访问数据库,当试图加载的记录不存在时,load方法可能返回一个未初始化的代理对象;而get()方法总是立即访问数据库,当试图加载的记录不存在时,get(方法将直接返回null。

  • flush
    一旦通过load或者get加载某持久化实例,该对象就处于持久化状态,你就可以进行持久化实例的修改(也就是说你修改的时候,必须先加载对应的数据)。

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

如果我们调用持久化实体的setter 方法改变了它的属性,Hibernate 会在Session fush 之前生成一-条update语句,这条update语句带有“where < 主键列>=<标识属性值>”子句,表明将会根据主键来修改特定记录。

  • update
    如果该托管对象,之前有过持久化过,就使用该方法持久化,之后状态会变成持久化状态。
  • merge
    该方法是针对持久化过的(也就是有了主键标识),就是保存到数据库,merge方法在执行之前都回去缓存中找是不是有相应的记录,也就是会有一条select语句,执行改语句的目的是为了判断该对象是否被修改了,但是和update的区别,该方法执行之后,状态还是托管状态(当我们使用merge的时候,执行完成后,我们提供的对象A还是脱管状态。hibernate或者是new了一个B(此时执行插入操作),或者检索到一个持久对象B(此时执行更新操作),并把我们提供的对象A的所有的值拷贝到这个B,执行完成后B是持久状态,而我们提供的A还是托管状态。
    )。如果原来的Seesion中有该相同的持久化标识值,会覆盖。
  • updateorsave(一般在Hibernate更新操作之后,使用这个)
    如果该托管对象,不确定之前有没有持久化过,持久化过就update,没有就save。

tip

这里,我要提示,这里为啥要强调第一次持久化和之后的持久化的区别。第一次持久化操作之后,会产生主键标识值,这时如果你托管状态,你进行修改,在保存到数据库。主键标识值是不变的。

  • delete
    通常该方法,可以删除持久化实例,对应的持久化数据,也会删除。(在删除的时候,还是需要先通过load加载该持久类的实例)
Hibernate 映射文件

hibernate 对应的映射文件就是XXX.hbm.xml。前面的XXX是你的实体类的类名。

映射文件基本结构
<hibernate-mapping>
<class> 
</class>
</hibernate-mapping>

映射文件的根标签hibernate-mapping,该标签下可以有多个class标签。

hibernate-mapping 的可选属性
  • default-cascade:设置Hibernate默认的级联风格,该属性的默认值是none。当配置Java属性映射和集合映射时还可指定cascade属性, 用于覆盖默认的级联风格。 如果配置Java属性映射、集合映射时没有指定cascade属性,则Hibernate将采用此处指定的级联风格。
  • default-access:指定Hibernate默认的属性访问策略,默认值为property,即使用getter/setter方法对来访问属性(例如需要访问abc属性, 则应该提供setAbc()和getAbc()两个方法)。如果指定access=“field”,则Hibernate会忽略getter/setter方法对,而是通过反射来访问成员变量。如果需要实现自己的属性访问策略,则需要自己提供PropertyAccessor接口的实现类,再在access中设置自定义属性访问策略类的名字。
  • default-lazy: 设置Hibernate默认的延迟加载策略,该属性值默认为true,即启用延迟加载策略。 当配置Java属性映射和集合映射时还可指定lazy属性,用于覆盖默认的延迟加载策略。如果配置Java属性映射、集合映射时没有指定lazy属性,则Hibernate将采用此处指定的延迟加载策略。( 通常情况下,不应该关闭延迟加载策略,例如当加载一个leacher对象,且该leacher对象有N个关联的Student 对象时。如果关闭延迟加载策略,则Hibermate在加载Teacher对象时会自动加载所有的Student对象一一如 果该Teacher有1万个关联的Student对象, 更甚至有100万个,而程序仅需要访问Teacher对象,则一次加载这些Student对象纯属多余。)
  • auto-import:设置是否允许在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。该属性默认是true。(如果同一份映射文件中有两个持久化类映射,它们的类名是一-样的(只是处于不同包结构下,它们全限定类名不同, 依然是两个不同的类),则应该设置auto-import= “false”;否则Hibernate将无法准确分别两个类, 从而导致Hibermate抛出一个异常。)
  • package:包前缀。该属性指定一个包前缀,对于映射文件中没有指定全限定的类名,则默认使用该配置包前缀,一定要指定,否则找不到类。
class 的可选属性

接下来看元素, 每个元素对应一一个持久化类。

  • name:来指定该持久化类映射的持久化类的类名,此处的类名应该是全限定的类名。如果不使用全限定的类名,则必须在<hibermate-mapping…>元素里指定package属性,package 属性指定持久化类所在的包名。
  • table:用来指定该持久化类映射的数据库的表名。
  • mutable:用于指定持久化类的实例是可变对象还是不可变对象,该属性只能接受true和false两个属性值, 该属性默认值是true。
  • proxy: 指定一 一个接口, 在延迟装载时作为代理使用,也可以在这里指定该类自己的名字。
  • dynamic-update:指定用于更新记录的update语句是否在运行时动态生成,并且只更新那些改变过的字段。该属性默认是false。 开启该属性将导致Hibernate 需要更多时间来生成sql语句。(如果该属性设置为true,表明Hibernate在向数据库发送更新语句时只会包括属性值发生改变的属性,其实要说明的就是当将该属性设置为true,并不表明你的设置就起作用了,这取决于你使用的更新方法和查找是否处在同一个session当中。对于更新操作而言,分三步走,第一步是查找出需要更新的实体,第二步是更新需要更新的属性,第三步是保存更新实体。如果这三步操作没有在同一个session的管理之下,那么即便设置了dynamic-update=true,这个属性也是不会起任何作用的)
  • dynamic-insert:指定用于插入记录的insert语句是否在运行时动态生成,并且只插入那些非空字段。该属性默认是false。开启该属性将导致Hibernate需要更多时间来生成SQL语句。
  • batch-size:指定根据标识符(identifer)来抓取实例时每批抓取的实例数。该属性值默认是1。
  • optimistic-lock: 该属性指定乐观锁定策略。该属性的默认值是version。
  • check: 指定一个SQL表达式,用于为该持久化类所对应的表指定一个多行的Check约束。
  • subselect : 该属性用于映射不可变的、只读实体。通俗地说,就是将数据库的子查询映射成Hibernate持久化对象。当需要使用视图(其实质就是一个查询)来代替数据表时,该属性比较有用。
  • where:该属性类似在SQL语句中添加的where,一旦指定了该属性,那么load、get查询该持久化对象的时候,该where语句都会生效。
映射主键

Hibernate 建议为持久化类定义一个标识属性,用于标识某一个持久化实例,该标识属性映射到数据库底层的主键。

id标签

标识属性是通过id标签去定义。

  • name:标识属性的属性名。(必选)
  • type:指定该标识属性的数据类型,该类型既可以是Hibernate内建类型,也可以是Java类型,如果使用Java类型则需要使用全限定类名(带包名)。该属性是可选的,如果映射文件中没有指定该属性,则由Hibernate自行判断该标识属性的数据类型,通常建议设置该属性,这会保证更好的性能。(这里type定义必须要保证Java对象和标准数据库类型对应,否则会报错)

我们正常在hibernate第一次根据映射文件,就确定了表的字段类型,如果你想要修改,就需要根据配置文件中.hbm2ddl.auto 的值,如果是create,那么你可以修改这个type,此时这个表就重构了,就是根据你设置全新的表;如果是update,那么你在这修改type,是一点用没用。因为update 是在原有基础上进行修改,字段类型在一开始就确定,无法半途去修改。

类型参看

这里写图片描述

  • column: 设置标识属性所映射的数据列的列名。在默认情况下,该列的列名与该标识属性的属性名相同。(必选)
  • generator:该属性是指定主键生成策略。(必选)
    (下面是生成策略:
  • increment:为long、short 或者int 类型主键生成唯一标识。 只有在没有其他进程往同一张表中插入数据时才能使用。在集群下不要使用。
  • identity:在DB2、MySQL、Microsoft SQL Server, Sybase和HypersonicSQL等提供identity。
  • sequence: 在DB2、PostgreSQL、 Oracle、SAP DB、McKoi等提供sequence支持的数据表中适用。返回的标识属性值是long、short 或int类型的。
  • uuid:用一个128位的UUID算法生成字符串类型的标识符, 这在一个网络中是唯一一的(IP地址也作为算法的数据源)。UUID被编码为一一个32位十六进制数的字符串。
  • assigned:让应用程序在save()之前为对象分配-一个标识符。这相当于不指定<generator…/>元素时所采用的默认策略。
  • native:根据底层数据库的能力选择identity、sequence 或者hilo中的一种。)
映射普通属性

映射普通的属性主要是通过property标签来映射的。

  • name:该属性主要对应持久化类的属性名。(必选)
  • column:该属性主要指定数据库对应的列名,默认情况下,数据库字段的名称是和你的属性名一致的,可以不指定该属性,如果不同,则必须指定(必选)
  • type:同上讲解。
  • length:指定该属性所映射数据列的字段长度。
  • scale:指定该属性所映射数据列的小数位数,对double、float、 decimal 等类型的数据列有效。
  • generated: 设置该属性映射的数据列的值是否由数据库生成,该属性可以接受never (不由数据库生成)、 insert (该属性值是insert时生成,但不会在随后的update时重新生成)、always (该属性值在insert和update时都会被重新生成)。
   <property name="createtime"  update="false" generated="insert"  insert="false" />

generated主要用在持久化对象不是由Java 方法去set、而是通过数据库去生成。(包含timestamp时间戳、触发器生成的值均需要),需要注意通过generated 在数据库中会有对应的列。

在timestamp,数据库表中一定要设置当前时间为默认值,每次修改就是参看这个时间的。

  • formula:该属性指定-一个SQL表达式,指定该属性的值将根据表达式来计算,计算属性没有和它对应的数据列。
    (使用formula属性时有如下几个注意点:
  • formula="( sql )"的英文括号不能少。
  • formula="()"的括号里面是SQL表达式语句,SQL表达式中的列名和表名都应该和数据库对应, 而不是和持久化对象的属性对应。
  • 如果需要在formula属性中使用参数,则直接使用where cur.id= currencyID 形式,其中currencyID就是参数,当前持久化对象的currencyID属性将作为参数传入。)
 <property name="fullcontent" formula="(select concat(n.title,n.content) from news n where n.id =id)"  ></property>

注意一下:该属性只需要在持久化类中进行配置,数据库表中无需有该列。

映射集合类型

集合属性大致有两种:

  • 一种是单纯的集合属性,例如像List、Set 或数组等集合属性。
  • 一种是Map结构的集合属性,每个属性值都有对应的key映射。

注意点:

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

两个持久化对象不能共享(即同时操作)同一个集合元素的引用。

集合映射的元素大致有如下:

  1. list: 用于映射List集合属性。(重复元素的算两个)
  2. set:用于映射Set集合属性。(重复元素的算一个)
  3. map:用于映射Map集合属性。
  4. array:用于映射数组集合属性。
  5. rimitive-array:专门用于映射基本数据类型的数组。
  6. bag:用于映射无序集合。
  7. idbag:用于映射无序集合,但为集合增加逻辑次序。

注意有序的集合,在映射文件中都会配置索引column。

这里主要说一下bag,这个有两个特点:

  • 可以存放重复的元素的,这个特点像List,实际上有序是无序的一种特例罢了。
  • 没有顺序,这点像Set。

在Java 中List、Set是继承于Collection接口的,而这里的bag就可以与之映射,bag 包含List、Set的特点,是更抽象的表现。

映射集合属性时通常需要指定

  1. name:用于标明该集合属性的名称。(必选)
  2. table: 指定保存集合属性的表名。如果没有指定该属性,则表名默认与集合属性同名。
  3. lazy: 设置是否启动延迟加载,该属性默认是true,适用于非常大的集合。
  4. inverse:指定该集合关联的实体在双向关联关系中不控制关联关系。
  5. cascade:指定对持久化对象的持久化操作(如save、update、 delete) 是否会级联到它所关联的子实体。
  6. order-by:该属性用于设置数据库对集合元素排序,该属性仅对1.4或更高版本的JDK有效。这个属性前提集合一定要好排序的。该属性的值为指定表的指定字段(一个或几个)加上asc或者desc关键字,这种排序是数据库进行SQL查询时进行排序的,而不是直接在内存中排序。
  7. sort:指定集合的排序顺序,可以为自然排序,或者使用给定的排序类进行排序。
  8. 因为集合属性都需要保存到另一个数据表中,所以保存集合属性的数据表必须包含一个外键列,用于参照到主键列。该外键列通过在. 等集合元素中使用子元素来映射。

这里写图片描述

  1. column:指定外键字段的列名。
  2. on-delete:指定外键约束是否打开数据库级别的级联删除。
  3. property-ref:指定外键引用的字段是否为原表的主键。
  4. not-null:指定外键列是否具有非空约束,如果指定非空约束,则意味着无论何时,外键总是主键的一部分。
  5. update:指定外键列是否可更新,如果不允许更新,则意味着无论何时,外键总是主键的一部分。
  6. unique: 指定外键列是否具有唯一约束, 如果指定唯一一约束,则意味着无论何时,外键总是主键的一部分。
  7. 在Java的所有集合类型(包括数组、Map)中,只有Set 集合是无序的,即没有显式的索引值。List、数组使用整数作为集合元素的索引值,而Map则使用key作为集合元素的索引。 因此,在所有的集合映射中,除了和元素外,都需要为集合元素的数据表指定一个索引列一用于保存数组索引,或者List的索引,或者Map集合的key索引。 用于映射索引列的元素有如下几个:
      <list-index>:用于映射List集合、数组的索引列。
      <map-key>:  用于映射Map集合、基本数据类型的索引列。
      <map-key-many-to-many>:用于映射Map集合、实体引用类型的索引列。 
      <composite-map-key>:用于映射Map集合、复合数据类型的索引列。

Hibernate集合元素数据类型

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

  综合所有情形,用于映射集合元素的大致包括如下几种元素。
  • : 当集合元素是基本类型及其包装类、字符串类型和日期类型时使用该元素。
  • <composite-element…/>:当集合元素是复合类型时使用该元素。复杂类型就是里面元素是一个对象。
  • 或:当集合元素是其他持久化对象的引用时使用它们。也就是说,这两个元素主要用于进行关联关系映射。
集合属性配置与使用例子
  • List
public class Person {
	private Integer id;
	private String name;
	private Integer age;
	private List<String> schools = new java.util.ArrayList<String>();
	//集合必须显示用接口申明
	//这里必须显示初始化集合,否则会引起空指针异常,我测试过了,不初始化也行,可能是版本的问题,保险还是写一下。
	public Integer getId() {
		return id;
	}
	public void setId(Integer id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public List<String> getSchools() {
		return schools;
	}
	public void setSchools(List<String> schools) {
		this.schools = schools;
	}
	

}

<class name="Person" table="PPERON" dynamic-insert="true" dynamic-update="false">
    <id name="id" type="integer" >
    <generator class="identity"></generator>
    </id>
    <property name="name" type="string"></property>
    <property name="age" type="integer"></property>
    <list name="schools" table="SCHOOL"><!-- 在该表中 持久化类的主键和集合索引列构成联合主键 -->
       <key column="id"></key><!-- 集合的属性值不可能与持久化类在同一个表,所以需要一个外键,对应主表中的主键 -->
       <list-index column="list_index" ></list-index> <!-- List 的索引默认都是整数类型,所以无需设置。集合索引列 -->
       <element column="school_name" type="string"></element> <!-- type指定元素的类型,这样Hibernate就不需要动态反射POJO的集合泛型了 集合元素的数据列-->
    </list>
</class>
</hibernate-mapping>
			Person  p = new Person();	
		   	p.setAge(23);
		   	p.setName("lao");
		   	List<String> schools = new java.util.ArrayList<String>();
		   	schools.add("xiao1");
		   	schools.add("sdss");
		   	p.setSchools(schools);
		   	ss.save(p);
		   	Person p1 = ss.load(Person.class, 1);
		   	System.out.println(p1.getName()+p1.getAge());
		   	List school = p1.getSchools();
		   	for(int i =0;i<school.size();i++){
		   		System.out.println(school.get(i));//因为List集合只有一个内容,它在这回默认回去配置标签<element>对应的列的值
		   		//还有一点就是集合元素是懒加载的,只有当你调用集合中的方法,才会加载该集合内容,不让每次加载,多费事。
		   	       //**才会发起select语句,体现了hibernate的延迟加载、懒加载的特性。**
		   	}
  • 数组

数组的操作和List操作很类似,最大区别就是数组的个数是固定,而List是动态变化的。

在刚才Perspn POJO 基础:

private String[] documents;//并提供它的set、get方法
<array name="documents" table="DOCUMENT">
      <key column="id"></key><!-- 外键 -->
       <list-index column="list_index" ></list-index> <!--数组的默认索引也是整数类型 集合索引列 -->
       <element column="document_name" type="string"></element> <!--  集合元素的数据列-->
    </array>

用法参看List

	Person  p = new Person();
		   	p.setAge(23);
		   	p.setName("lao");
		   	List<String> schools = new java.util.ArrayList<String>();
		   	schools.add("xiao1");
		   	schools.add("sdss");
		   	p.setSchools(schools);
		   	String[] documents = new String[5];
		   	documents[0] = "kkk";
		   	documents[1] = "lll";
		   	p.setDocuments(documents);
		   	ss.save(p);
  • Set

在POJO 中添加如下:

private Set schools = new HashSet();
	//集合申明接口类型

	public Set getSchools() {
		return schools;
	}

	public void setSchools(Set schools) {
		this.schools = schools;
	}

 <set name="schools" table="SCHOOL1">
    <key column="id" not-null="true"></key><!-- 外键 用来持久化类和集合属性的关联 -->
    <!-- set  是无序,所有不需要索引列 -->
    <element type="string" column="school_name"  not-null="true"> </element>
    </set>

注意:

对比List和Set两种集合属性: List 集合的元素有顺序,而Set集合的元素没有顺序。当集合属性在另外的表中存储时,List 集合属性可以用关联持久化类的外键列和集合元素索引列作为联合主键,但Set集合没有索引列,则以关联持久化类的外键和元素列作为联合主键, 前提是元素列不能为空,否则该表没有主键。

			Person  p = new Person();
		   	p.setAge(23);
		   	p.setName("lao");
		   	Set<String> schools = new java.util.HashSet<String>();
		   	schools.add("xiao1");
		   	schools.add("sdss");
		   	p.setSchools(schools);
		   	ss.save(p);
  • bag

bag 标签可以映射成List,也可以映射成Set。但是我们通常还是把bag和Collection接口进行映射。

注意使用bag标签:集合属性的表没有主键。

在原先的POJO 类基础上添加如下:

private Collection schools = new java.util.ArrayList<String>();

	public Collection getSchools() {
		return schools;
	}

	public void setSchools(Collection schools) {
		this.schools = schools;
	}
     <bag name="schools" table="SCHOOL2">
     <key column="id" not-null="true"></key><!-- 外键 用来持久化类和集合属性的关联 -->
     <element type="string" column="school_name"  not-null="true"> </element><!-- **即使指定数据列不能为空,还是没有主键** -->
     </bag>
			Person  p = new Person();
		   	p.setAge(23);
		   	p.setName("lao");
		   	java.util.Collection<String> schools = new java.util.ArrayList<String>();
		   	schools.add("xiao1");
		   	schools.add("sdss");
		   	p.setSchools(schools);
		   	ss.save(p);
  • Map

在原来的POJO 类添加如下:

private Map<String,Float> scores = new java.util.HashMap<String, Float>();
	
	public Map<String, Float> getScores() {
		return scores;
	}

	public void setScores(Map<String, Float> scores) {
		this.scores = scores;
	}
    <map name="scores" table="SCORE" >
    <key column="id" not-null="true"></key><!-- 持久化和map 集合关联的外键  -->
    <map-key column="subject" type="string"></map-key><!-- 这时map的key 基本属性用该标签,复杂对象的,后面再说  -->
    <element column="score" type="float" ></element><!-- map 的value -->
    </map>
    <!-- 这里有很奇怪,明明我在POJO 中已经定义了泛型,为啥这边还要强制去设置type,不设置就报错 -->

生成的表,是以外键列和key列作为联合主键。

			Person  p = new Person();
		   	p.setAge(23);
		   	p.setName("lao");
		   	Map map = new java.util.HashMap<String,Float>();
		   	map.put("sss", 97.0f);
		   	map.put("sadsa", 78.0f);
		   	p.setScores(map);
		   	ss.save(p);

上面我们演示过了,对于集合元素添加操作,但是又时我们会对集合进行更新操作,那么对应的Set、List、Map 是如何操作,其实还是把握住Java 集合的对象方法即可。

                    Person  p = ss.load(Person.class,16);
		    p.getScores().replace("sss", 99f);//对于Map,使用replace方法
		    ss.saveOrUpdate(p);

对于List,因为有索引,直接调用List的set方法。
对于Set,没有索引,只能删除属性值,在插入。

至于其他的增删改查的方法还是一句话对应Java 集合框架的方法去看。

集合属性的性能分析

当系统从数据库中初始化某个持久化类时,集合属性是否随持久化类一起初始化呢?

包含十万,甚至百万的记录,在初始化持久化类之时,完成所有集合属性的抓取,将导致性能急剧下降。完全有可能系统只需要使用持久化类集合属性中的部分记录,而完全不是集合属性的全部,这样,没有必要一次加载所有的集合属性。

对于集合属性,通常推荐使用延迟加载策略。所谓延迟加载就是等系统需要使用集合属性时才从数据库装载关联的数据。

集合分类:

  1. 有序集合:集合里的元素可以根据key或index访问。
  2. 无序集合: 集合里的元素只能遍历,不支持随机存取。

有序集合都拥有一一个由外键列和集合元素索引列组成的联合主键,在这种情况下,集合属性的更新是非常高效的一主键 已经被有效地索引,因此当Hibernate试图更新或删除一~行时, 可以迅速找到该行数据。

无序集合,例如Set的主键由<key…/>和其他元素字段构成,或者根本没有主键。如果集合中元素是组合元素或者大文本、大二进制字段,数据库可能无法有效地对复杂的主键进行索引。即使可以建立索引,性能也非常差。

Set集合不同,Set集合需要保证集合元素不能重复,因此当程序试图向Set集合中添加元素时,集合需要先加载所有集合元素,再依次比较添加的新元素是否重复, 最后才可以决定是否能成功添加新元素,所以向Set集合中添加元素时性能较低。

在这种情况下,<ba…>映射是最差的。因为<ba…> 允许有重复的元素值,也没有索引字段,因此不可能定义主键。Hibernate 无法判断重复行。如果试图修改这种集合属性时,Hibernate 先删除全部集合, 然后重新创建整个集合,效率非常低下。

不过需要指出的是:虽然数组也是有序集合,但数组无法使用延迟加载(因为数组的长度不可变),所以实际上用数组作为集合的性能并不高, 通常我们认为List、Map集合性能较高,而Set则紧随其后,最后算是bag了。

在这里有一点,集合在删除的时候,有一个便捷的方法,就是将集合对象置为null,这里底层就是执行一条delete,删除所有的集合的数据,如果,你一条一条的delete,消耗大量底层数据库资源。但是这个适用于要删除大量数据,少量的数据可以在插入尽量,这种方法只是仅供参考。

集合的属性表的数据是完全从属于主表,无法直接访问集合属性表的数据,必须都先访问主表。

有序集合映射

Hibermate还支持使用SortedSet 和SortedMap两个有序集合(这里只针对Set、Map,因为list已经有序),当我们需要映射这种有序集合时,应该为<map…>元素或<set…>元素指定sort属性,该属性值可以是如下三个值。

  • unsorted:映射有序集合时不排序。
  • natural:映射有序集合时使用自然排序。
  • java.util.Comparator实现类的类名:使用排序器对有序集合元素进行定制排序。
Tip

有人考虑使用Hibernate之后,心想,一边我们在Hibernate 设置表的结构,一边我可以在数据库设置,那两种不同的时候,我擦,该用哪个,经过测试发现,如果你在Hibernate 配置的和数据库设置不一样,不能匹配,那么在Hibernate 就会产生该列到数据库,不管你是不是多打一个字的差距,它理解就是不同的列。

在Hibernate 的映射文件中,子标签是有一定的顺序,顺序乱,标签会报错。可以把鼠标悬浮在标签上查看子标签有哪些、以及顺序。

在原先的POJO类添加如下:

private java.util.SortedSet<String> trainings = new java.util.TreeSet<String>();
	//这里需要用接口申明
	public java.util.SortedSet<String> getTrainings() {
		return trainings;
	}

	public void setTrainings(java.util.SortedSet<String> trainings) {
		this.trainings = trainings;
	}


     <set name="trainings" table="TRAININGS" sort="natural" order-by="training_name desc">><!-- sort 就是指定排序,自然排序是数字从0-9 ,字母a-z,**order-by 这个是根据某个字段列,查询的时候排序(这个是针对所有的,并且是在数据库排序好的,而不是在内存中),在获取trainings 这个实体集合属性,都会排序;而sort只是插入的数据排序,用于SortSet、SortMap** -->
    <key   column="id" ></key><!-- 这里就是映射主表的主键列,因为主表的主键没有指定主键列,默认就是和name一致 -->
    <element column="training_name" type="string"></element>
    </set>

Person  p = new Person();
		   	p.setAge(23);
		   	p.setName("lao");
		   	//Map map = new java.util.HashMap<String,Float>();
		   	java.util.SortedSet<String> trainings = new java.util.TreeSet<String>();
		   	trainings.add("AML");
		   	trainings.add("BNJ");
		   	trainings.add("CEDML");
		   	trainings.add("RML");
		   	//注意这个是每一次插入的数据,进行排序插入。并不是理解的是数据库中的所有的数据都排序
		        p.setTrainings(trainings);
		   	ss.save(p);
映射数据库对象

有时候我们希望在映射文件中创建和删除触发器、存储过程等数据库对象,Hibermate提供了<database-object…/>元素来满足这种需求。

使用<database-objec…>元素只有如下两种形式:

  • 第一种形式是在映射文件中显式声明create和drop命令。(每一个<database-objec…> 只有一组create和drop命令)
  • 第二种形式必须实现org.hibernate.mapping.AuxiliaryDatabaseObject接口。
实现的例子

在映射文件中标签下,添加如下:

<database-object>
<!-- <definition class=""></definition> -->//这个是基于第二种
<create>create table test(t_name varchar(255));</create><!-- 在crate和drop 里面可以执行execute 多有的sql语句 -->
<drop></drop>
<dialect-scope name="org.hibernate.dialect.MySQL5InnoDBDialect"></dialect-scope><!-- 指定数据库对象在特定的对象中才可以使用 -->
</database-object>
                   Configuration con = new Configuration().configure();
		   SessionFactory sf = con.buildSessionFactory();//系统将在创建SessionFactory 的时候,生成数据库对象。

基于映射文件的配置数据库对象,是不需要修改hbm2ddl.auto属性,不需要修改成create,因为这和表的内部细节,没有多大关系。

上面讲解数据库对象是由某个映射文件生成的,那么SchemaExport工具去生成。

        Configuration con = new Configuration().configure();//会加载配置文件
		//,根据里面的映射文件去生成数据库对象
        StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
                .configure().build();
        Metadata metadata = new MetadataSources(serviceRegistry)
                .buildMetadata();
        //这里使用SchemaExport不同是在于Hibernate版本的问题
        SchemaExport schemaExport = new SchemaExport();
        schemaExport.setFormat(true);//设置sql 格式化
        schemaExport.setOutputFile("my.sql");//
        schemaExport.create(EnumSet.of(TargetType.DATABASE), metadata);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值