上一篇
代码基础:
//全局配置文件/Hibernate/src/hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC"-//Hibernate/Hibernate Configuration DTD 3.0//EN""http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration><session-factory><!-- 配置连接数据库的基本信息 --><property name="connection.username">classiccars</property><property name="connection.password">myeclipse</property><property name="connection.driver_class">org.apache.derby.jdbc.ClientDriver</property><property name="connection.url">jdbc:derby://localhost:1527/myeclipse</property><!-- 配置 hibernate 的基本信息 --><!-- hibernate 所使用的数据库方言 --><property name="dialect">org.hibernate.dialect.DB2Dialect</property> <!-- 执行操作时是否在控制台打印 SQL --><property name="show_sql">true</property><!-- 是否对 SQL 进行格式化 --><property name="format_sql">true</property><!-- 指定自动生成数据表的策略 --><property name="hbm2ddl.auto">update</property>
<!--给Hibernate设置事物隔离级别--><property name="connection.isolation">2</property><!-- 删除对象后, 使其 OID 置为 null --><property name="use_identifier_rollback">true</property><!-- 指定关联的 .hbm.xml 文件 --><mapping resource="wo/News.hbm.xml"/></session-factory></hibernate-configuration>
////////////////////////////////////////////////////////////////
//映射文件/Hibernate/src/wo/News.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping><class name="wo.News" table="NEWS" ><id name="id" type="java.lang.Integer"><column name="ID" /><!-- 指定主键的生成方式, native: 使用数据库本地方式 --><generator class="native" /></id><property name="title" type="java.lang.String" ><column name="TITLE" /></property><property name="author" type="java.lang.String"><column name="AUTHOR" /></property><property name="date" type="java.sql.Date"><column name="DATE" /></property></class></hibernate-mapping>
///////////////////////////////////////////////////////
//javabean/Hibernate/src/wo/News.java
package wo;import java.sql.Blob;
import java.util.Date;public class News {private Integer id; //fieldprivate String title;private String author;private Date date;public Integer getId() { //propertyreturn id;}public void setId(Integer id) {this.id = id;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getAuthor() {return author;}public void setAuthor(String author) {this.author = author;}public Date getDate() {return date;}public void setDate(Date date) {this.date = date;}public News(String title, String author, Date date) {super();this.title = title;this.author = author;this.date = date;}public News() {// TODO Auto-generated constructor stub}@Overridepublic String toString() {return "News [id=" + id + ", title=" + title + ", author=" + author+ ", date=" + date + "]";}}
/////////////////////////////////////////////////
//测试/Hibernate/src/wo/HibernateTest.java
package wo;import java.sql.Connection;
import java.sql.Date;
import java.sql.SQLException;import org.hibernate.*;
import org.hibernate.cfg.Configuration;
import org.hibernate.jdbc.Work;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.junit.*;public class HibernateTest {private SessionFactory sessionFactory;private Session session;private Transaction transaction;@Beforepublic void init(){Configuration configuration = new Configuration().configure();ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();sessionFactory = configuration.buildSessionFactory(serviceRegistry);session = sessionFactory.openSession();transaction = session.beginTransaction();}@Afterpublic void destroy(){transaction.commit();session.close();sessionFactory.close();}
}
Hibernate 的配置文件
Hibernate 配置文件主要用于配置数据库连接和 Hibernate 运行时所需的各种属性
每个 Hibernate 配置文件对应一个 Configuration 对象
Hibernate配置文件可以有两种格式:
hibernate.properties
hibernate.cfg.xml
hibernate.cfg.xml的常用属性
JDBC 连接属性
connection.url:数据库URL
connection.username:数据库用户名
connection.password:数据库用户密码
connection.driver_class:数据库JDBC驱动
dialect:配置数据库的方言,根据底层的数据库不同产生不同的 sql 语句,Hibernate 会针对数据库的特性在访问时进行优化
C3P0 数据库连接池属性
hibernate.c3p0.max_size: 数据库连接池的最大连接数
hibernate.c3p0.min_size: 数据库连接池的最小连接数
hibernate.c3p0.timeout: 数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁
hibernate.c3p0.max_statements: 缓存 Statement 对象的数量
hibernate.c3p0.idle_test_period: 表示连接池检测线程多长时间检测一次池内的所有链接对象是否超时. 连接池本身不会把自己从连接池中移除,而是专门有一个线程按照一定的时间间隔来做这件事,这个线程通过比较连接对象最后一次被使用时间和当前时间的时间差来和 timeout 做对比,进而决定是否销毁这个连接对象。
hibernate.c3p0.acquire_increment: 当数据库连接池中的连接耗尽时, 同一时刻获取多少个数据库连接
1. 在 hibernate 中使用 C3P0 数据源:
1). 导入 jar 包:
hibernate-release-4.2.4.Final\lib\optional\c3p0*.jar
2). 加入配置:
hibernate.c3p0.max_size: 数据库连接池的最大连接数
hibernate.c3p0.min_size: 数据库连接池的最小连接数
hibernate.c3p0.acquire_increment: 当数据库连接池中的连接耗尽时, 同一时刻获取多少个数据库连接
hibernate.c3p0.timeout: 数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁
hibernate.c3p0.idle_test_period: 表示连接池检测线程多长时间检测一次池内的所有链接对象是否超时.
连接池本身不会把自己从连接池中移除,而是专门有一个线程按照一定的时间间隔来做这件事,
这个线程通过比较连接对象最后一次被使用时间和当前时间的时间差来和 timeout 做对比,进而决定是否销毁这个连接对象。
hibernate.c3p0.max_statements: 缓存 Statement 对象的数量
例子:(前面的基础上):
//全局配置文件/Hibernate/src/hibernate.cfg.xml增加
<!-- 配置 C3P0 数据源 --><property name="hibernate.c3p0.max_size">10</property><property name="hibernate.c3p0.min_size">5</property><property name="c3p0.acquire_increment">2</property><property name="c3p0.idle_test_period">2000</property><property name="c3p0.timeout">2000</property><property name="c3p0.max_statements">10</property>//////////////////////////////////////////////////////////////////测试增加/Hibernate/src/wo/HibernateTest.java@Testpublic void testDoWork(){session.doWork(new Work() {@Overridepublic void execute(Connection connection) throws SQLException {// TODO Auto-generated method stubSystem.out.println(connection); }});}
其他
show_sql:是否将运行期生成的SQL输出到日志以供调试。取值 true | false
format_sql:是否将 SQL 转化为格式良好的 SQL . 取值 true | false
hbm2ddl.auto:在启动和停止时自动地创建,更新或删除数据库模式。取值 create | update | create-drop | validate
jdbc.fetch_size 和 jdbc.batch_size
hibernate.jdbc.fetch_size:实质是调用 Statement.setFetchSize() 方法设定 JDBC 的 Statement 读取数据的时候每次从数据库中取出的记录条数。
例如一次查询1万条记录,对于Oracle的JDBC驱动来说,是不会 1 次性把1万条取出来的,而只会取出 fetchSize 条数,当结果集遍历完了这些记录以后,再去数据库取 fetchSize 条数据。因此大大节省了无谓的内存消耗。Fetch Size设的越大,读数据库的次数越少,速度越快;Fetch Size越小,读数据库的次数越多,速度越慢。Oracle数据库的JDBC驱动默认的Fetch Size = 10,是一个保守的设定,根据测试,当Fetch Size=50时,性能会提升1倍之多,当 fetchSize=100,性能还能继续提升20%,Fetch Size继续增大,性能提升的就不显著了。并不是所有的数据库都支持Fetch Size特性,例如MySQL就不支持
hibernate.jdbc.batch_size:设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,类似于设置缓冲区大小的意思。batchSize 越大,批量操作时向数据库发送sql的次数越少,速度就越快。
测试结果是当Batch Size=0的时候,使用Hibernate对Oracle数据库删除1万条记录需要25秒,Batch Size = 50的时候,删除仅仅需要5秒!Oracle数据库 batchSize=30 的时候比较合适。
例子(在前面的基础上):
//全局配置文件/Hibernate/src/hibernate.cfg.xml增加<!-- 设定 JDBC 的 Statement 读取数据的时候每次从数据库中取出的记录条数 --><property name="hibernate.jdbc.fetch_size">100</property><!-- 设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小 --><property name="jdbc.batch_size">30</property>
对象关系映射文件
hibernate-mapping
hibernate-mapping 是 hibernate 映射文件的根元素
schema: 指定所映射的数据库schema的名称。若指定该属性, 则表明会自动添加该 schema 前缀
catalog:指定所映射的数据库catalog的名称。
default-cascade(默认为 none): 设置hibernate默认的级联风格. 若配置 Java 属性, 集合映射时没有指定 cascade 属性, 则 Hibernate 将采用此处指定的级联风格.
default-access (默认为 property): 指定 Hibernate 的默认的属性访问策略。默认值为 property, 即使用 getter, setter 方法来访问属性. 若指定 access, 则 Hibernate 会忽略 getter/setter 方法, 而通过反射访问成员变量.
default-lazy(默认为 true): 设置 Hibernat morning的延迟加载策略. 该属性的默认值为 true, 即启用延迟加载策略. 若配置 Java 属性映射, 集合映射时没有指定 lazy 属性, 则 Hibernate 将采用此处指定的延迟加载策略
auto-import (默认为 true): 指定是否可以在查询语言中使用非全限定的类名(仅限于本映射文件中的类)。
package (可选): 指定一个包前缀,如果在映射文档中没有指定全限定的类名, 就使用这个作为包名。
例如(映射文件中):
///Hibernate/src/wo/News.hbm.xml以下两句该为
<hibernate-mapping package="wo"><class name="News" table="NEWS" dynamic-update="true"> </class>
</hibernate-mapping>
class
class 元素用于指定类和表的映射
name:指定该持久化类映射的持久化类的类名
table:指定该持久化类映射的表名, Hibernate 默认以持久化类的类名作为表名
dynamic-insert: 若设置为 true, 表示当保存一个对象时, 会动态生成 insert 语句, insert 语句中仅包含所有取值不为 null 的字段. 默认值为 false
dynamic-update: 若设置为 true, 表示当更新一个对象时, 会动态生成 update 语句, update 语句中仅包含所有取值需要更新的字段. 默认值为 false
select-before-update:设置 Hibernate 在更新某个持久化对象之前是否需要先执行一次查询. 默认值为 false
batch-size:指定根据 OID 来抓取实例时每批抓取的实例数.
lazy: 指定是否使用延迟加载.
mutable: 若设置为 true, 等价于所有的 元素的 update 属性为 false, 表示整个实例不能被更新. 默认为 true.
discriminator-value: 指定区分不同子类的值. 当使用 元素来定义持久化类的继承关系时需要使用该属性
映射对象标识符
Hibernate 使用对象标识符(OID) 来建立内存中的对象和数据库表中记录的对应关系. 对象的 OID 和数据表的主键对应. Hibernate 通过标识符生成器来为主键赋值
Hibernate 推荐在数据表中使用代理主键, 即不具备业务含义的字段. 代理主键通常为整数类型, 因为整数类型比字符串类型要节省更多的数据库空间.
在对象-关系映射文件中, 元素用来设置对象标识符. 子元素用来设定标识符生成器.
Hibernate 提供了标识符生成器接口: IdentifierGenerator, 并提供了各种内置实现
id
id:设定持久化类的 OID 和表的主键的映射
name: 标识持久化类 OID 的属性名
column: 设置标识属性所映射的数据表的列名(主键字段的名字).
unsaved-value:若设定了该属性, Hibernate 会通过比较持久化类的 OID 值和该属性值来区分当前持久化类的对象是否为临时对象
type:指定 Hibernate 映射类型. Hibernate 映射类型是 Java 类型与 SQL 类型的桥梁. 如果没有为某个属性显式设定映射类型, Hibernate 会运用反射机制先识别出持久化类的特定属性的 Java 类型, 然后自动使用与之对应的默认的 Hibernate 映射类型
Java 的基本数据类型和包装类型对应相同的 Hibernate 映射类型. 基本数据类型无法表达 null, 所以对于持久化类的 OID 推荐使用包装类型
generator
generator:设定持久化类设定标识符生成器
class: 指定使用的标识符生成器全限定类名或其缩写名
主键生成策略generator
Hibernate提供的内置标识符生成器:
increment 标识符生成器
increment 标识符生成器由 Hibernate 以递增的方式为代理主键赋值,Hibernate 会先读取 NEWS 表中的主键的最大值, 而接下来向 NEWS 表中插入记录时, 就在 max(id) 的基础上递增, 增量为 1.
适用范围:
333 由于 increment 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
适用于只有单个 Hibernate 应用进程访问同一个数据库的场合, 在集群环境下不推荐使用它
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
identity 标识符生成器
identity 标识符生成器由底层数据库来负责生成标识符, 它要求底层数据库把主键定义为自动增长字段类型
适用范围:
由于 identity 生成标识符的机制依赖于底层数据库系统, 因此, 要求底层数据库系统必须支持自动增长字段类型. 支持自动增长字段类型的数据库包括: DB2, Mysql, MSSQLServer, Sybase 等
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
sequence 标识符生成器
sequence 标识符生成器利用底层数据库提供的序列来生成标识符.
Hibernate 在持久化一个 News 对象时, 先从底层数据库的 news_seq 序列中获得一个唯一的标识号, 再把它作为主键值
适用范围:
由于 sequence 生成标识符的机制依赖于底层数据库系统的序列, 因此, 要求底层数据库系统必须支持序列. 支持序列的数据库包括: DB2, Oracle 等
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
hilo 标识符生成器
hilo 标识符生成器由 Hibernate 按照一种 high/low 算法*生成标识符, 它从数据库的特定表的字段中获取 high 值.
Hibernate 在持久化一个 News 对象时, 由 Hibernate 负责生成主键值. hilo 标识符生成器在生成标识符时, 需要读取并修改 HI_TABLE 表中的 NEXT_VALUE 值.
适用范围:
由于 hilo 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
native 标识符生成器
native 标识符生成器依据底层数据库对自动生成标识符的支持能力, 来选择使用 identity, sequence 或 hilo 标识符生成器.
适用范围:
由于 native 能根据底层数据库系统的类型, 自动选择合适的标识符生成器, 因此很适合于跨数据库平台开发
OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
Property
property 元素用于指定类的属性和表的字段的映射
name:指定该持久化类的属性的名字
column:指定与类的属性映射的表的字段名. 如果没有设置该属性, Hibernate 将直接使用类的属性名作为字段名.
type:指定 Hibernate 映射类型. Hibernate 映射类型是 Java 类型与 SQL 类型的桥梁. 如果没有为某个属性显式设定映射类型, Hibernate 会运用反射机制先识别出持久化类的特定属性的 Java 类型, 然后自动使用与之对应的默认的 Hibernate 映射类型.
not-null:若该属性值为 true, 表明不允许为 null, 默认为 false
access:指定 Hibernate 的默认的属性访问策略。默认值为 property, 即使用 getter, setter 方法来访问属性. 若指定 field, 则 Hibernate 会忽略 getter/setter 方法, 而通过反射访问成员变量
unique: 设置是否为该属性所映射的数据列添加唯一约束.
index: 指定一个字符串的索引名称. 当系统需要 Hibernate 自动建表时, 用于为该属性所映射的数据列创建索引, 从而加快该数据列的查询.
length: 指定该属性所映射数据列的字段的长度
scale: 指定该属性所映射数据列的小数位数, 对 double, float, decimal 等类型的数据列有效.
formula:设置一个 SQL 表达式, Hibernate 将根据它来计算出派生属性的值.
派生属性: 并不是持久化类的所有属性都直接和表的字段匹配, 持久化类的有些属性的值必须在运行时通过计算才能得出来, 这种属性称为派生属性
使用 formula 属性时
formula=“(sql)” 的英文括号不能少
Sql 表达式中的列名和表名都应该和数据库对应, 而不是和持久化对象的属性对应
如果需要在 formula 属性中使用参数, 这直接使用 where cur.id=id 形式, 其中 id 就是参数, 和当前持久化对象的 id 属性对应的列的 id 值将作为参数传入.
Java 类型, Hibernate 映射类型及 SQL 类型之间的对应关系
例子(在前面的基础上):
//映射文件/Hibernate/src/wo/News.hbm.xml增加<!-- 映射派生属性 --><property name="desc" formula="(SELECT AUTHOR FROM NEWS WHERE NEWS.ID= id)" type="java.lang.String"></property>///////////////////////////////////////////////////////////javabean增加 /Hibernate/src/wo/News.java//该属性值为: title: authorprivate String desc;//及相应的getset
/////////////////////////////////////////////////////////////
//测试
@Testpublic void testPropertyUpdate(){News news = (News) session.get(News.class, 6);news.setTitle("aaaa"); System.out.println(news.getDesc());System.out.println(news.getDate().getClass()); }
Java 时间和日期类型的 Hibernate 映射
在 Java 中, 代表时间和日期的类型包括: java.util.Date 和 java.util.Calendar. 此外, 在 JDBC API 中还提供了 3 个扩展了 java.util.Date 类的子类: java.sql.Date, java.sql.Time 和 java.sql.Timestamp, 这三个类分别和标准 SQL 类型中的 DATE, TIME 和 TIMESTAMP 类型对应
在标准 SQL 中, DATE 类型表示日期, TIME 类型表示时间, TIMESTAMP 类型表示时间戳, 同时包含日期和时间信息.
2). 如何进行映射 ?
I. 因为 java.util.Date 是 java.sql.Date, java.sql.Time 和java.sql.Timestamp 的父类, 所以 java.util.Date可以对应标准 SQL 类型中的 DATE, TIME 和 TIMESTAMP
II. 基于 I, 所以在设置持久化类的 Date 类型是, 设置为 java.util.Date.
III. 如何把 java.util.Date 映射为 DATE, TIME 和 TIMESTAMP ?
可以通过 property 的 type 属性来进行映射:
Java 类型, Hibernate 映射类型及 SQL 类型之间的对应关系
例子:(在前面的基础上)
//为了方便,全局配置文件/Hibernate/src/hibernate.cfg.xml中修改
<!-- 指定自动生成数据表的策略 --><property name="hbm2ddl.auto">create</property>
//////////////////////////////////////////////////////
//映射文件/Hibernate/src/wo/News.java修改<property name="date" type="time"><column name="DATE" />/////////////////////////////////////////////////测试/Hibernate/src/wo/HibernateTest.java@Testpublic void testIdGenerator() throws InterruptedException{News news = new News("AA", "aa", new java.sql.Date(new Date(126506).getTime()));session.save(news); }
Java 大对象类型的 Hiberante 映射
在 Java 中, java.lang.String 可用于表示长字符串(长度超过 255), 字节数组 byte[] 可用于存放图片或文件的二进制数据. 此外, 在 JDBC API 中还提供了 java.sql.Clob 和 java.sql.Blob 类型, 它们分别和标准 SQL 中的 CLOB 和 BLOB 类型对应. CLOB 表示字符串大对象(Character Large Object), BLOB表示二进制对象(Binary Large Object)
Mysql 不支持标准 SQL 的 CLOB 类型, 在 Mysql 中, 用 TEXT, MEDIUMTEXT 及 LONGTEXT 类型来表示长度操作 255 的长文本数据
在持久化类中, 二进制大对象可以声明为 byte[] 或 java.sql.Blob 类型; 字符串可以声明为 java.lang.String 或 java.sql.Clob
实际上在 Java 应用程序中处理长度超过 255 的字符串, 使用 java.lang.String 比 java.sql.Clob 更方便
例子(在前面的基础上):
//全局配置文件<!-- 指定自动生成数据表的策略 --><property name="hbm2ddl.auto">update</property>
//////////////////////////////////
//bean/Hibernate/src/wo/News.java添加//大文本private String content;//二进制数据private Blob image;
//getset方法
////////////////////////////////////////////////
//映射文件添加//映射文件/Hibernate/src/wo/News.java<!-- 映射大对象 --><!-- 若希望精确映射 SQL 类型, 可以使用 sql-type 属性. --><property name="content"><column name="CONTENT" sql-type="mediumtext"></column></property><property name="image"><column name="IMAGE" sql-type="mediumblob"></column></property>//////////////////////////////////////////////////测试测试/Hibernate/src/wo/HibernateTest.java添加@Testpublic void testBlob() throws Exception{
// News news = new News();
// news.setAuthor("cc");
// news.setContent("CONTENT");
// news.setDate(new Date());
// news.setDesc("DESC");
// news.setTitle("CC");
//
// InputStream stream = new FileInputStream("Hydrangeas.jpg");
// Blob image = Hibernate.getLobCreator(session)
// .createBlob(stream, stream.available());
// news.setImage(image);
//
// session.save(news);News news = (News) session.get(News.class, 1);Blob image = news.getImage();InputStream in = image.getBinaryStream();System.out.println(in.available()); }
映射组成关系
建立域模型和关系数据模型有着不同的出发点:
域模型: 由程序代码组成, 通过细化持久化类的的粒度可提高代码的可重用性, 简化编程
在没有数据冗余的情况下, 应该尽可能减少表的数目, 简化表之间的参照关系, 以便提高数据的访问速度
Hibernate 把持久化类的属性分为两种:
值(value)类型: 没有 OID, 不能被单独持久化, 生命周期依赖于所属的持久化类的对象的生命周期
实体(entity)类型: 有 OID, 可以被单独持久化, 有独立的生命周期
显然无法直接用 property 映射 pay 属性
Hibernate 使用 元素来映射组成关系, 该元素表名 pay 属性是 Worker 类一个组成部分, 在 Hibernate 中称之为组件
例子(在前面的基础上):
//bean/Hibernate/src/wo/Pay.java
package wo;public class Pay {private int monthlyPay;private int yearPay;private int vocationWithPay;private Worker worker;public Worker getWorker() {return worker;}public void setWorker(Worker worker) {this.worker = worker;}public int getMonthlyPay() {return monthlyPay;}public void setMonthlyPay(int monthlyPay) {this.monthlyPay = monthlyPay;}public int getYearPay() {return yearPay;}public void setYearPay(int yearPay) {this.yearPay = yearPay;}public int getVocationWithPay() {return vocationWithPay;}public void setVocationWithPay(int vocationWithPay) {this.vocationWithPay = vocationWithPay;}
}
///Hibernate/src/wo/Worker.java
package wo;public class Worker {private Integer id;private String name;private Pay pay;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 Pay getPay() {return pay;}public void setPay(Pay pay) {this.pay = pay;}
}
////////////////////////////////////////////
//映射文件/Hibernate/src/wo/Worker.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 2014-1-2 16:14:33 by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping package="wo"><class name="Worker" table="WORKER"><id name="id" type="java.lang.Integer"><column name="ID" /><generator class="native" /></id><property name="name" type="java.lang.String"><column name="NAME" /></property><!-- 映射组成关系 --><component name="pay" class="Pay"><parent name="worker"/><!-- 指定组成关系的组件的属性 --><property name="monthlyPay" column="MONTHLY_PAY"></property><property name="yearPay" column="YEAR_PAY"></property><property name="vocationWithPay" column="VOCATION_WITH_PAY"></property></component></class>
</hibernate-mapping>
/////////////////////////////////
//全局配置文件最后/Hibernate/src/hibernate.cfg.xml增加
<mapping resource="wo/Worker.hbm.xml"/>
/////////////////////////////////////////
//测试/Hibernate/src/wo/HibernateTest.java增加
@Testpublic void testComponent(){Worker worker = new Worker();Pay pay = new Pay();pay.setMonthlyPay(1000);pay.setYearPay(80000); pay.setVocationWithPay(5);worker.setName("ABCD");worker.setPay(pay);session.save(worker);}
映射一对多关联关系
例子(在前面的基础上):
//全局配置文件/Hibernate/src/hibernate.cfg.xml增加<mapping resource="wo/Order.hbm.xml"/><mapping resource="wo/Customer.hbm.xml"/>
///////////////////////////////////////////////////
//bean/Hibernate/src/wo/Customer.java
package wo;public class Customer {private Integer customerId;private String customerName;public Integer getCustomerId() {return customerId;}public void setCustomerId(Integer customerId) {this.customerId = customerId;}public String getCustomerName() {return customerName;}public void setCustomerName(String customerName) {this.customerName = customerName;}}
///Hibernate/src/wo/Order.javapackage wo;public class Order {private Integer orderId;private String orderName;private Customer customer;public Integer getOrderId() {return orderId;}public void setOrderId(Integer orderId) {this.orderId = orderId;}public String getOrderName() {return orderName;}public void setOrderName(String orderName) {this.orderName = orderName;}public Customer getCustomer() {return customer;}public void setCustomer(Customer customer) {this.customer = customer;}
}///////////////////////////////////////////////////
//配置文件/Hibernate/src/wo/Customer.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping><class name="wo.Customer" table="CUSTOMERS"><id name="customerId" type="java.lang.Integer"><column name="CUSTOMER_ID" /><generator class="native" /></id><property name="customerName" type="java.lang.String"><column name="CUSTOMER_NAME" /></property></class></hibernate-mapping>
//配置文件/Hibernate/src/wo/Order.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping package="wo"><class name="Order" table="ORDERS"><id name="orderId" type="java.lang.Integer"><column name="ORDER_ID" /><generator class="native" /></id><property name="orderName" type="java.lang.String"><column name="ORDER_NAME" /></property><!-- 映射多对一的关联关系。 使用 many-to-one 来映射多对一的关联关系 name: 多这一端关联的一那一端的属性的名字class: 一那一端的属性对应的类名column: 一那一端在多的一端对应的数据表中的外键的名字--><many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one></class>
</hibernate-mapping>
//////////////////////////////////////////////////////
//测试/Hibernate/src/wo/HibernateTest.java增加@Testpublic void testDelete(){//在不设定级联关系的情况下, 且 1 这一端的对象有 n 的对象在引用, 不能直接删除 1 这一端的对象Customer customer = (Customer) session.get(Customer.class, 1);session.delete(customer); }@Testpublic void testUpdate(){Order order = (Order) session.get(Order.class, 1);order.getCustomer().setCustomerName("AAA");}@Testpublic void testMany2OneGet(){//1. 若查询多的一端的一个对象, 则默认情况下, 只查询了多的一端的对象. 而没有查询关联的//1 的那一端的对象!Order order = (Order) session.get(Order.class, 1);System.out.println(order.getOrderName()); System.out.println(order.getCustomer().getClass().getName());session.close();//2. 在需要使用到关联的对象时, 才发送对应的 SQL 语句. Customer customer = order.getCustomer();System.out.println(customer.getCustomerName()); //3. 在查询 Customer 对象时, 由多的一端导航到 1 的一端时, //若此时 session 已被关闭, 则默认情况下//会发生 LazyInitializationException 异常//4. 获取 Order 对象时, 默认情况下, 其关联的 Customer 对象是一个代理对象!}@Testpublic void testMany2OneSave(){Customer customer = new Customer();customer.setCustomerName("BB");Order order1 = new Order();order1.setOrderName("ORDER-3");Order order2 = new Order();order2.setOrderName("ORDER-4");//设定关联关系order1.setCustomer(customer);order2.setCustomer(customer);//执行 save 操作: 先插入 Customer, 再插入 Order, 3 条 INSERT//先插入 1 的一端, 再插入 n 的一端, 只有 INSERT 语句.
// session.save(customer);
//
// session.save(order1);
// session.save(order2);//先插入 Order, 再插入 Customer. 3 条 INSERT, 2 条 UPDATE//先插入 n 的一端, 再插入 1 的一端, 会多出 UPDATE 语句!//因为在插入多的一端时, 无法确定 1 的一端的外键值. 所以只能等 1 的一端插入后, 再额外发送 UPDATE 语句.//推荐先插入 1 的一端, 后插入 n 的一端session.save(order1);session.save(order2);session.save(customer);}
//
双向 1-n
例子(在前面的基础上):
//bean/Hibernate/src/wo/Customer.java增加
/** 1. 声明集合类型时, 需使用接口类型, 因为 hibernate 在获取* 集合类型时, 返回的是 Hibernate 内置的集合类型, 而不是 JavaSE 一个标准的* 集合实现. * 2. 需要把集合进行初始化, 可以防止发生空指针异常*/
private Set<Order> orders = new HashSet<>();
//getset方法
///////////////////////////////////////////////////////
////配置文件/Hibernate/src/wo/Customer.hbm.xml增加<!-- 映射 1 对多的那个集合属性 --><!-- set: 映射 set 类型的属性, table: set 中的元素对应的记录放在哪一个数据表中. 该值需要和多对一的多的那个表的名字一致 --><!-- inverse: 指定由哪一方来维护关联关系. 通常设置为 true, 以指定由多的一端来维护关联关系 --><!-- cascade 设定级联操作. 开发时不建议设定该属性. 建议使用手工的方式来处理 --><!-- order-by 在查询时对集合中的元素进行排序, order-by 中使用的是表的字段名, 而不是持久化类的属性名 --><set name="orders" table="ORDERS" inverse="true" order-by="ORDER_NAME DESC"><!-- 执行多的表中的外键列的名字 --><key column="CUSTOMER_ID"></key><!-- 指定映射类型 --><one-to-many class="Order"/></set>
////////////////////////////////////////////////////////
//测试/Hibernate/src/wo/HibernateTest.java增加@Testpublic void testMany2OneSave(){Customer customer = new Customer();customer.setCustomerName("AA");Order order1 = new Order();order1.setOrderName("ORDER-1");Order order2 = new Order();order2.setOrderName("ORDER-2");//设定关联关系order1.setCustomer(customer);order2.setCustomer(customer);customer.getOrders().add(order1);customer.getOrders().add(order2);//执行 save 操作: 先插入 Customer, 再插入 Order, 3 条 INSERT, 2 条 UPDATE//因为 1 的一端和 n 的一端都维护关联关系. 所以会多出 UPDATE//可以在 1 的一端的 set 节点指定 inverse=true, 来使 1 的一端放弃维护关联关系!//建议设定 set 的 inverse=true, 建议先插入 1 的一端, 后插入多的一端//好处是不会多出 UPDATE 语句session.save(customer);// session.save(order1);
// session.save(order2);//先插入 Order, 再插入 Cusomer, 3 条 INSERT, 4 条 UPDATE
// session.save(order1);
// session.save(order2);
//
// session.save(customer);}
元素的 inverse 属性
在hibernate中通过对 inverse 属性的来决定是由双向关联的哪一方来维护表和表之间的关系. inverse = false 的为主动方,inverse = true 的为被动方, 由主动方负责维护关联关系
在没有设置 inverse=true 的情况下,父子两边都维护父子
关系
在 1-n 关系中,将 n 方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
在 1-N 关系中,若将 1 方设为主控方
会额外多出 update 语句。
插入数据时无法同时插入外键列,因而无法为外键列添加非空约束
cascade 属性
在对象 – 关系映射文件中, 用于映射持久化类之间关联关系的元素, , 和 都有一个 cascade 属性, 它用于指定如何操纵与当前对象关联的其他对象.
在数据库中对集合排序
元素有一个 order-by 属性, 如果设置了该属性, 当 Hibernate 通过 select 语句到数据库中检索集合对象时, 利用 order by 子句进行排序
order-by 属性中还可以加入 SQL 函数
映射一对一关联关系
基于外键映射的 1-1
例子(在前面的基础上):
//javabean /Hibernate/src/wo/Manager.java
package wo;public class Manager {private Integer mgrId;private String mgrName;private Department dept;public Integer getMgrId() {return mgrId;}public void setMgrId(Integer mgrId) {this.mgrId = mgrId;}public String getMgrName() {return mgrName;}public void setMgrName(String mgrName) {this.mgrName = mgrName;}public Department getDept() {return dept;}public void setDept(Department dept) {this.dept = dept;}
}
//////Hibernate/src/wo/Department.java
package wo;public class Department {private Integer deptId;private String deptName;private Manager mgr;public Integer getDeptId() {return deptId;}public void setDeptId(Integer deptId) {this.deptId = deptId;}public String getDeptName() {return deptName;}public void setDeptName(String deptName) {this.deptName = deptName;}public Manager getMgr() {return mgr;}public void setMgr(Manager mgr) {this.mgr = mgr;}
}
////////////////////////////////////////////////////////////////
//映射文件/Hibernate/src/wo/Manager.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping><class name="wo.Manager" table="MANAGERS"><id name="mgrId" type="java.lang.Integer"><column name="MGR_ID" /><generator class="native" /></id><property name="mgrName" type="java.lang.String"><column name="MGR_NAME" /></property><!-- 映射 1-1 的关联关系: 在对应的数据表中已经有外键了, 当前持久化类使用 one-to-one 进行映射 --><!-- 没有外键的一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段--><one-to-one name="dept" class="wo.Department"property-ref="mgr"></one-to-one></class></hibernate-mapping>
/////////////////////////Hibernate/src/wo/Department.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping><class name="wo.Department" table="DEPARTMENTS"><id name="deptId" type="java.lang.Integer"><column name="DEPT_ID" /><generator class="native" /></id><property name="deptName" type="java.lang.String"><column name="DEPT_NAME" /></property><!-- 使用 many-to-one 的方式来映射 1-1 关联关系 --><many-to-one name="mgr" class="wo.Manager" column="MGR_ID" unique="true"></many-to-one> </class>
</hibernate-mapping>
//////////////////////////////////////////////////////
//全局配置文件/Hibernate/src/hibernate.cfg.xml增加<mapping resource="wo/Department.hbm.xml"/><mapping resource="wo/Manager.hbm.xml"/>
////////////////////////////////////////////////////
//测试/Hibernate/src/wo/HibernateTest.java@Testpublic void testGet2(){//在查询没有外键的实体对象时, 使用的左外连接查询, 一并查询出其关联的对象//并已经进行初始化. Manager mgr = (Manager) session.get(Manager.class, 1);System.out.println(mgr.getMgrName()); System.out.println(mgr.getDept().getDeptName()); }@Testpublic void testGet(){//1. 默认情况下对关联属性使用懒加载Department dept = (Department) session.get(Department.class, 1);System.out.println(dept.getDeptName()); //2. 所以会出现懒加载异常的问题.
// session.close();
// Manager mgr = dept.getMgr();
// System.out.println(mgr.getClass());
// System.out.println(mgr.getMgrName()); //3. 查询 Manager 对象的连接条件应该是 dept.manager_id = mgr.manager_id//而不应该是 dept.dept_id = mgr.manager_idManager mgr = dept.getMgr();System.out.println(mgr.getMgrName()); }@Testpublic void testSave(){Department department = new Department();department.setDeptName("DEPT-BB");Manager manager = new Manager();manager.setMgrName("MGR-BB");//设定关联关系department.setMgr(manager);manager.setDept(department);//保存操作//建议先保存没有外键列的那个对象. 这样会减少 UPDATE 语句session.save(department);session.save(manager);}
基于主键映射的 1-1
例子:(在前面的基础上):
//映射文件/Hibernate/src/wo/Manager.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping><class name="com.atguigu.hibernate.one2one.primary.Manager" table="MANAGERS"><id name="mgrId" type="java.lang.Integer"><column name="MGR_ID" /><generator class="native" /></id><property name="mgrName" type="java.lang.String"><column name="MGR_NAME" /></property><one-to-one name="dept" class="wo.Department"></one-to-one></class></hibernate-mapping>
/////////////////////////Hibernate/src/wo/Department.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="wo"><class name="Department" table="DEPARTMENTS"><id name="deptId" type="java.lang.Integer"><column name="DEPT_ID" /><!-- 使用外键的方式来生成当前的主键 --><generator class="foreign"><!-- property 属性指定使用当前持久化类的哪一个属性的主键作为外键 --><param name="property">mgr</param></generator></id><property name="deptName" type="java.lang.String"><column name="DEPT_NAME" /></property><!-- 采用 foreign 主键生成器策略的一端增加 one-to-one 元素映射关联属性,其 one-to-one 节点还应增加 constrained=true 属性, 以使当前的主键上添加外键约束--><one-to-one name="mgr" class="Manager" constrained="true"></one-to-one></class>
</hibernate-mapping>
映射多对多关联关系
单向 n-n
n-n 的关联必须使用连接表
与 1-n 映射类似,必须为 set 集合元素添加 key 子元素,指定 CATEGORIES_ITEMS 表中参照 CATEGORIES 表的外键为 CATEGORIY_ID. 与 1-n 关联映射不同的是,建立 n-n 关联时, 集合中的元素使用 many-to-many. many-to-many 子元素的 class 属性指定 items 集合中存放的是 Item 对象, column 属性指定 CATEGORIES_ITEMS 表中参照 ITEMS 表的外键为 ITEM_ID
双向n-n关联
双向 n-n 关联需要两端都使用集合属性
双向n-n关联必须使用连接表
集合属性应增加 key 子元素用以映射外键列, 集合元素里还应增加many-to-many子元素关联实体类
在双向 n-n 关联的两边都需指定连接表的表名及外键列的列名. 两个集合元素 set 的 table 元素的值必须指定,而且必须相同。set元素的两个子元素:key 和 many-to-many 都必须指定 column 属性,其中,key 和 many-to-many 分别指定本持久化类和关联类在连接表中的外键列名,因此两边的 key 与 many-to-many 的column属性交叉相同。也就是说,一边的set元素的key的 cloumn值为a,many-to-many 的 column 为b;则另一边的 set 元素的 key 的 column 值 b,many-to-many的 column 值为 a.
对于双向 n-n 关联, 必须把其中一端的 inverse 设置为 true, 否则两端都维护关联关系可能会造成主键冲突.
例子(在前面的基础上):
//全局配置文件/Hibernate/src/hibernate.cfg.xml增加
<mapping resource="wo/Category.hbm.xml"/><mapping resource="wo/Item.hbm.xml"/>
//////////////////////////////////////////////
//Javabean wo.Categoryjava
package wo;import java.util.HashSet;
import java.util.Set;public class Category {private Integer id;private String name;private Set<Item> items = new HashSet<>();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 Set<Item> getItems() {return items;}public void setItems(Set<Item> items) {this.items = items;}
}
//wo.Item.java
package wo;import java.util.HashSet;
import java.util.Set;public class Item {private Integer id;private String name;private Set<Category> categories = new HashSet<>();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 Set<Category> getCategories() {return categories;}public void setCategories(Set<Category> categories) {this.categories = categories;}
}
////////////////////////////////////////////////////////////////
//映射文件/Hibernate/src/wo/Category.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping package="wo.n2n"><class name="Category" table="CATEGORIES"><id name="id" type="java.lang.Integer"><column name="ID" /><generator class="native" /></id><property name="name" type="java.lang.String"><column name="NAME" /></property><!-- table: 指定中间表 --><set name="items" table="CATEGORIES_ITEMS"><key><column name="C_ID" /></key><!-- 使用 many-to-many 指定多对多的关联关系. column 执行 Set 集合中的持久化类在中间表的外键列的名称 --><many-to-many class="Item" column="I_ID"></many-to-many></set></class>
</hibernate-mapping>//映射文件/Hibernate/src/wo/Item.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><hibernate-mapping><class name="wo.Item" table="ITEMS"><id name="id" type="java.lang.Integer"><column name="ID" /><generator class="native" /></id><property name="name" type="java.lang.String"><column name="NAME" /></property><set name="categories" table="CATEGORIES_ITEMS" inverse="true"><key column="I_ID"></key><many-to-many class="wo.Category" column="C_ID"></many-to-many></set></class>
</hibernate-mapping>
////////////////////////////////////////////////////////////////
//测试/Hibernate/src/wo/HibernateTest.java@Testpublic void testSave(){Category category1 = new Category();category1.setName("C-AA");Category category2 = new Category();category2.setName("C-BB");Item item1 = new Item();item1.setName("I-AA");Item item2 = new Item();item2.setName("I-BB");//设定关联关系category1.getItems().add(item1);category1.getItems().add(item2);category2.getItems().add(item1);category2.getItems().add(item2);item1.getCategories().add(category1);item1.getCategories().add(category2);item2.getCategories().add(category1);item2.getCategories().add(category2);//执行保存操作session.save(category1);session.save(category2);session.save(item1);session.save(item2);}