Hibernate 的配置文件和对象映射关系表
Hibernate 的配置文件
1)Hibernate 配置文件主要用于配置数据库连接和 Hibernate 运行时所需的各种属性
2)每个 Hibernate 配置文件对应一个 Configuration 对象
3)Hibernate配置文件可以有两种格式:
- hibernate.properties
- hibernate.cfg.xml
hibernate.cfg.xml的常用属性
样例代码:
<?xml version="1.0" encoding="UTF-8"?>
<!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.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost/webdemo?serverTimezone=GMT</property>
<property name="connection.username">root</property>
<property name="connection.password">stoneSml@123</property>
<property name="dialect">org.hibernate.dialect.MySQL8Dialect</property>
<property name="show_sql">true</property>
<property name="format_sql">true</property>
<property name="hbm2ddl.auto">update</property>
<mapping resource="com/boode/entity/Stuclass.hbm.xml"/>
</session-factory>
</hibernate-configuration>
JDBC 连接属性
- connection.url:数据库URL
- connection.username:数据库用户名
- connection.password:数据库用户密码
- connection.driver_class:数据库JDBC驱动
- dialect:配置数据库的方言,根据底层的数据库不同产生不同的 sql 语句,Hibernate 会针对数据库的特性在访问时进行优化
C3P0 数据库连接池属性
前置条件,导入C3P0的jar包(hibernate-release-4.2.4.Final\lib\optional\c3p0*.jar)
- hibernate.c3p0.max_size: 数据库连接池的最大连接数
- hibernate.c3p0.min_size: 数据库连接池的最小连接数
- hibernate.c3p0.timeout: 数据库连接池中连接对象在多长时间没有使用过后,就应该被销毁
- hibernate.c3p0.max_statements: 缓存 Statement 对象的数量
- hibernate.c3p0.idle_test_period:表示连接池检测线程多长时间检测一次池内的所有链接对象是否超时.连接池本身不会把自己从连接池中移除,而是专门有一个线程按照一定的时间间隔来做这件事,这个线程通过比较连接对象最后一次被使用时间和当前时间的时间差来和 timeout 做对比,进而决定是否销毁这个连接对象。
其他
- show_sql:是否将运行期生成的SQL输出到日志以供调试。取值 true | false
- format_sql:是否将 SQL 转化为格式良好的 SQL . 取值 true | false
- hbm2ddl.auto:在启动和停止时自动地创建,更新或删除数据库模式。取值 create | update | create-drop | validate
- hibernate.jdbc.fetch_size:实质是调用Statement.setFetchSize()方法设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数。
1)对于Oracle的JDBC驱动来说,是不会1次性把1万条取出来的,而只会取出fetchSize条数,当结果集遍历完了这些记录以后,再去数据库取fetchSize条数据。因此大大节省了无谓的内存消耗。FetchSize设的越大,读数据库的次数越少,速度越快;FetchSize越小,读数据库的次数越多,速度越慢。
2)Oracle数据库的JDBC驱动默认的FetchSize=10,是一个保守的设定,根据测试,当FetchSize=50时,性能会提升1倍之多,当fetchSize=100,性能还能继续提升20%,Fetch Size继续增大,性能提升的就不显著了。
3)并不是所有的数据库都支持Fetch Size特性,例如MySQL就不支持 - hibernate.jdbc.batch_size:设定对数据库进行批量删除,批量更新和批量插入的时候的批次大小,类似于设置缓冲区大小的意思。batchSize 越大,批量操作时向数据库发送sql的次数越少,速度就越快。
测试结果是当BatchSize=0的时候,使用Hibernate对Oracle数据库删除1万条记录需要25秒,BatchSize=50的时候,删除仅仅需要5秒!Oracle数据库 batchSize=30 的时候比较合适。
对象映射关系表
1)映射文件的扩展名为 .hbm.xml。
2)可以理解持久化类和数据表之间的对应关系,也可以理解持久化类属性与数据库表列之间的对应关系
3)在运行时 Hibernate 将根据这个映射文件来生成各种 SQL 语句
样例代码:
<?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.heibernate.helloworld.News" table="NEWS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="assigned" />
</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>
映射文件说明
1)每个Hibernate-mapping中可以同时定义多个类. 但更推荐为每个类都创建一个单独的映射文件
hibernate-mapping标签
1.package (可选)
指定一个包前缀,如果在映射文档中没有指定全限定的类名,就使用这个作为包名。
class标签
<class name="com.boode.entity.Emp" table="emp">
1.dynamic-insert(动态插入)
若设置为 true, 表示当保存一个对象时, 会动态生成 insert 语句,insert语句中仅包含所有取值不为null的字段. 默认值为 false
2.dynamic-update(动态更新)
若设置为 true, 表示当更新一个对象时, 会动态生成 update 语句,update语句中仅包含所有取值需要更新的字段. 默认值为 false
测试样例代码:
News news=session.get(News.class, 8);
news.setAuthor("ABCED");
System.out.println(news);
运行结果:
Hibernate:
select
news0_.ID as ID1_0_0_,
news0_.TITLE as TITLE2_0_0_,
news0_.AUTHOR as AUTHOR3_0_0_,
news0_.DATE as DATE4_0_0_
from
NEWS news0_
where
news0_.ID=?
News [id=8, title=Java, author=ABCED, date=2020-04-09]
Hibernate:
update
NEWS
set
TITLE=?,
AUTHOR=?,
DATE=?
where
ID=?
进行配置
<class name="com.atguigu.heibernate.helloworld.News" table="NEWS" dynamic-update="true">
配置后运行结果,只更新被修改的字段:
Hibernate:
select
news0_.ID as ID1_0_0_,
news0_.TITLE as TITLE2_0_0_,
news0_.AUTHOR as AUTHOR3_0_0_,
news0_.DATE as DATE4_0_0_
from
NEWS news0_
where
news0_.ID=?
News [id=8, title=Java, author=ABCEDEF, date=2020-04-09]
Hibernate:
update
NEWS
set
AUTHOR=?
where
ID=?
映射对象标识符
Hibernate使用对象标识符(OID)来建立内存中的对象和数据库表中记录的对应关系.对象的OID和数据表的主键对应.Hibernate通过标识符生成器来为主键赋值。
Hibernate推荐在数据表中使用代理主键,即不具备业务含义的字段.代理主键通常为整数类型,因为整数类型比字符串类型要节省更多的数据库空间.
id标签
在对象-关系映射文件中, 元素用来设置对象标识符. 子元素用来设定标识符生成器.
Hibernate 提供了标识符生成器接口: IdentifierGenerator, 并提供了各种内置实现
unsaved-value
若设定了该属性, Hibernate 会通过比较持久化类的 OID 值和该属性值来区分当前持久化类的对象是否为临时对象
generator标签
<id name="empno" type="java.lang.Integer">
<column name="empno" ></column>
<!--指定主键值的生成方式-->
<generator class="native"></generator>
</id>
increment 标识符生成器
1)increment 标识符生成器由 Hibernate 以递增的方式为代理主键赋值
2)Hibernate 会先读取 NEWS 表中的主键的最大值, 而接下来向 NEWS 表中插入记录时, 就在 max(id) 的基础上递增, 增量为 1.可能存在并发的问题。
适用范围:
1)由于 increment 生存标识符机制不依赖于底层数据库系统, 因此它适合所有的数据库系统
2)适用于只有单个 Hibernate 应用进程访问同一个数据库的场合, 在集群环境下不推荐使用它
3)OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
identity 标识符生成器
1)identity 标识符生成器由底层数据库来负责生成标识符, 它要求底层数据库把主键定义为自动增长字段类型适用范围:
1)由于identity生成标识符的机制依赖于底层数据库系统,因此,要求底层数据库系统必须支持自动增长字段类型.支持自动增长字段类型的数据库包括: DB2, Mysql, MSSQLServer, Sybase 等
2)OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
sequence标识符生成器
利用底层数据库提供的序列来生成标识符.
Hibernate 在持久化一个 News 对象时, 先从底层数据库的 news_seq 序列中获得一个唯一的标识号, 再把它作为主键值
适用范围:
1)由于 sequence 生成标识符的机制依赖于底层数据库系统的序列, 因此, 要求底层数据库系统必须支持序列. 支持序列的数据库包括: DB2, Oracle 等
2)OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
hilo 标识符生成器
hibernate5不再支持,换成了其他的生成器,现学习阶段,不做讨论。
native 标识符生成器
native 标识符生成器依据底层数据库对自动生成标识符的支持能力, 来选择使用 identity, sequence 或 hilo 标识符生成器.
适用范围:
1)由于 native 能根据底层数据库系统的类型, 自动选择合适的标识符生成器, 因此很适合于跨数据库平台开发
2)OID 必须为 long, int 或 short 类型, 如果把 OID 定义为 byte 类型, 在运行时会抛出异常
property标签
<property name="date" type="java.sql.Date">
<column name="DATE" />
</property>
1.access
指定 Hibernate 的默认的属性访问策略。默认值为 property, 即使用 getter, setter 方法来访问属性. 若指定field,则Hibernate 会忽略 getter/setter 方法, 而通过反射访问成员变量
2.type
指定 Hibernate 映射类型. Hibernate 映射类型是 Java 类型与 SQL 类型的桥梁.如果没有为某个属性显式设定映射类型,Hibernate 会运用反射机制先识别出持久化类的特定属性的 Java 类型, 然后自动使用与之对应的默认的 Hibernate 映射类型.
3.unique
设置是否为该属性所映射的数据列添加唯一约束.
4.update
设置这个值是否可以被修改
5.index
指定一个字符串的索引名称. 当系统需要 Hibernate 自动建表时, 用于为该属性所映射的数据列创建索引, 从而加快该数据列的查询.
<property name="title" type="string" column="TITLE"
unique="true" update="false" index="news_index" length="20">
</property>
6.formula
设置一个 SQL 表达式, Hibernate 将根据它来计算出派生属性的值.
派生属性:并不是持久化类的所有属性都直接和表的字段匹配,持久化类的有些属性的值必须在运行时通过计算才能得出来,这种属性称为派生属性
例子:
在类中添加派生属性:
//该属性值为: title: author
private String desc;
<!-- 映射派生属性 -->
<property name="desc" formula="(SELECT concat(author, ': ', title) FROM NEWS n WHERE n.id = id)"></property>
使用 formula 属性时
1)formula=“(sql)” 的英文括号不能少
2)Sql 表达式中的列名和表名都应该和数据库对应, 而不是和持久化对象的属性对应
3)如果需要在 formula 属性中使用参数, 这直接使用 where cur.id=id 形式, 其中 id就是参数,和当前持久化对象的id属性对应的列的 id 值将作为参数传入.
Java 类型, Hibernate 映射类型及 SQL 类型之间的对应关系
Java 时间和日期类型的 Hibernate 映射
1)在标准 SQL 中, DATE 类型表示日期, TIME 类型表示时间, TIMESTAMP 类型表示时间戳, 同时包含日期和时间信息.
2)在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类型对应
时间映射配置
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 属性来进行映射:
例如:
<property name="date" type="timestamp">
<column name="DATE" />
</property>
<property name="date" type="data">
<column name="DATE" />
</property>
<property name="date" type="time">
<column name="DATE" />
</property>
其中 timestamp, date, time 既不是 Java 类型, 也不是标准 SQL 类型, 而是 hibernate 映射类型.
Java 大对象类型的 Hiberante 映射
在Java中,java.lang.String可用于表示长字符串(长度超过255),字节数组byte[]可用于存放图片或文件的二进制数据.此外,在JDBCAPI中还提供了java.sql.Clob和java.sql.Blob类型,它们分别和标准SQL中的CLOB和BLOB类型对应.CLOB表示字符串大对象(CharacterLargeObject),BLOB表示二进制对象(BinaryLargeObject)
1)Mysql 不支持标准 SQL 的 CLOB 类型, 在 Mysql 中, 用 TEXT, MEDIUMTEXT 及 LONGTEXT 类型来表示长度操作 255 的长文本数据
2)在持久化类中, 二进制大对象可以声明为 byte[] 或 java.sql.Blob 类型; 字符串可以声明为 java.lang.String 或 java.sql.Clob
3)实际上在 Java 应用程序中处理长度超过 255 的字符串, 使用 java.lang.String 比 java.sql.Clob 更方便
测试样例:
1)在持久化类中加入两个属性
private String content;
private Blob image;
2)配置对象关系映射表
<property name="content">
<column name="content" sql-type="mediumtext"></column>
</property>
<property name="image">
<column name="image" sql-type="mediumblob"></column>
</property>
3)测试代码
News news=new News();
news.setAuthor("ABCEDEFG");
InputStream stream=new FileInputStream("1.PNG");
Blob image=Hibernate.getLobCreator(session).createBlob(stream,stream.available());
news.setImage(image);
session.save(news);
session.clear();
News news2=session.get(News.class, 3);
Blob image2=news2.getImage();
InputStream in=image2.getBinaryStream();
System.out.println(in.available());
注意:数据库一般不会采取此种方法存放图片,数据库一般会存放图片的路径。
映射组成关系
建立域模型和关系数据模型有着不同的出发点
1)域模型: 由程序代码组成, 通过细化持久化类的的粒度可提高代码的可重用性, 简化编程
2)在没有数据冗余的情况下, 应该尽可能减少表的数目, 简化表之间的参照关系, 以便提高数据的访问速度
Hibernate 把持久化类的属性分为两种:
1)值(value)类型: 没有 OID, 不能被单独持久化, 生命周期依赖于所属的持久化类的对象的生命周期
2)实体(entity)类型: 有 OID, 可以被单独持久化, 有独立的生命周期
Hibernate 使用 <component>
元素来映射组成关系, 在 Hibernate 中称之为组件
例子:
1)创建持久化类(省略构造和get、set方法)
public class Worker {
private Integer id;
private String name;
private Pay pay;
}
2)创建实体类型(省略构造和get、set方法)
public class Pay {
private int monthlyPay;
private int vocationWithPay;
private int yearPay;
}
3配置对象关系映射表
<?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 2020??4??16?? ????2:43:26 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping package="com.atguigu.heibernate.helloworld">
<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">
<property name="monthlyPay" column="monthlyPay"></property>
<property name="vocationWithPay" column="vocationWithPay"></property>
<property name="yearPay" column="yearPay"></property>
</component>
</class>
</hibernate-mapping>
4配置hibernate配置文件
<mapping resource="com/atguigu/heibernate/helloworld/Worker.hbm.xml"></mapping>
5.测试代码
Worker worker=new Worker();
worker.setName("ABC");
Pay pay=new Pay(100,200,300);
worker.setPay(pay);
session.save(worker);
映射一对多关联关系
单向多对一
示例配置信息:
1)两个关联的实例代码:
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;}
public class Customer {
private Integer customerId;
private String customerName;}
2)对象关系表Order.hbm.xml主要配置
<many-to-one name="customer" class="Customer">
<column name="CUSTOMER_ID" />
</many-to-one>
3)记得在hibernate.cfg.xml中配置映射表
4)测试样例,以及结论
public void test() throws Exception {
Customer customer = new Customer();
customer.setCustomerName("BB");
Order order1 = new Order();
order1.setOrderName("ORDER-3");
Order order2 = new Order();
order2.setOrderName("ORDER-4");
//设定关联关系,执行三个insert语句
order1.setCustomer(customer);
order2.setCustomer(customer);
session.save(customer);
session.save(order1);
session.save(order2);
}
public void test() throws Exception {
Customer customer = new Customer();
customer.setCustomerName("BB");
Order order1 = new Order();
order1.setOrderName("ORDER-3");
Order order2 = new Order();
order2.setOrderName("ORDER-4");
//设定关联关系,先执行三个插入,再执行order的修改
order1.setCustomer(customer);
order2.setCustomer(customer);
session.save(order1);
session.save(order2);
session.save(customer);
}
public 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 对象是一个代理对象!
}
//执行更新
public void testUpdate(){
Order order = (Order) session.get(Order.class, 1);
order.getCustomer().setCustomerName("AAA");
}
public void testDelete(){
//在不设定级联关系的情况下, 且 1 这一端的对象有 n 的对象在引用, 不能直接删除 1 这一端的对象
Customer customer = (Customer) session.get(Customer.class, 1);
session.delete(customer);
}
双向1-n
测试示例
1)永久化类的属性简要代码
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;
}
public class Customer {
private Integer customerId;
private String customerName;
/*
* 1. 声明集合类型时, 需使用接口类型, 因为 hibernate 在获取
* 集合类型时, 返回的是 Hibernate 内置的集合类型, 而不是 JavaSE 一个标准的
* 集合实现.
* 2. 需要把集合进行初始化, 可以防止发生空指针异常
*/
private Set<Order> orders=new HashSet<>();
}
2)配置映射关系表
Order.hbm.xml
<many-to-one name="customer" class="Customer">
<column name="CUSTOMER_ID" />
</many-to-one>
Customer.hbm.xml
<!-- 映射 1 对多的那个集合属性 -->
<!-- set: 映射 set 类型的属性, table: set 中的元素对应的记录放在哪一个数据表中. 该值需要和多对一的多的那个表的名字一致 -->
<!-- inverse: 指定由哪一方来维护关联关系. 通常设置为 true, 以指定由多的一端来维护关联关系 -->
<set name="orders" table="ORDERS" inverse="true" >
<!-- 执行多的表中的外键列的名字 -->
<key column="CUSTOMER_ID"></key>
<!-- 指定映射类型 -->
<one-to-many class="Order"/>
</set>
3)测试代码
增
public void test() throws Exception {
Customer customer = new Customer();
customer.setCustomerName("BB");
Order order1 = new Order();
order1.setOrderName("ORDER-5");
Order order2 = new Order();
order2.setOrderName("ORDER-6");
//设定关联关系,3次insert,两次order更新
order1.setCustomer(customer);
order2.setCustomer(customer);
customer.getOrders().add(order1);
customer.getOrders().add(order2);
session.save(customer);
session.save(order1);
session.save(order2);
}
@Test
public void test() throws Exception {
Customer customer = new Customer();
customer.setCustomerName("BB");
Order order1 = new Order();
order1.setOrderName("ORDER-5");
Order order2 = new Order();
order2.setOrderName("ORDER-6");
//3次insert,2次order更新,2此customer更新
order1.setCustomer(customer);
order2.setCustomer(customer);
customer.getOrders().add(order1);
customer.getOrders().add(order2);
session.save(order1);
session.save(order2);
session.save(customer);
}
读
public void test() throws Exception {
//1. 若查询多的一端的一个对象, 则默认情况下, 只查询了多的一端的对象. 而没有查询关联的
//1 的那一端的对象!
Order order = (Order) session.get(Order.class, 1);
System.out.println(order.getOrderName());
//com.atguigu.hibernate.entities.n21.Customer$HibernateProxy$iFNzbM59
//代理类
//session.close();
//3. 在查询 Customer 对象时, 由多的一端导航到 1 的一端时,
//若此时 session 已被关闭, 则默认情况下
//会发生 LazyInitializationException 异常
System.out.println(order.getCustomer().getClass().getName());
//2. 在需要使用到关联的对象时, 才发送对应的 SQL 语句.
System.out.println(order.getCustomer().getCustomerName());
}
改
@Test
public void test() throws Exception {
Order order = (Order) session.get(Order.class, 1);
//更新两个表
order.getCustomer().setCustomerName("BB2");
order.setOrderName("test21");
}
改
public void test() throws Exception {
Customer customer = (Customer) session.get(Customer.class, 1);
customer.getOrders().iterator().next().setOrderName("GGG");
}
修改外键连接
public void test() throws Exception {
Customer customer = (Customer) session.get(Customer.class, 3);
customer.getOrders().clear();
}
删除
public void testDelete(){
//在不设定级联关系的情况下, 且 1 这一端的对象有 n 的对象在引用, 不能直接删除 1 这一端的对象
Customer customer = (Customer) session.get(Customer.class, 1);
session.delete(customer);
}
###
set元素
inverse属性
inverse=false的为主动方,inverse=true的为被动方,由主动方负责维护关联关系
1)在1-n关系中,将n方设为主控方将有助于性能改善(如果要国家元首记住全国人民的名字,不是太可能,但要让全国人民知道国家元首,就容易的多)
2)在1-N关系中,若将1方设为主控方会额外多出update语句。
插入数据时无法同时插入外键列,因而无法为外键列添加非空约束
<set name="orders" table="ORDERS" inverse="true" >
<!-- 执行多的表中的外键列的名字 -->
<key column="CUSTOMER_ID"></key>
<!-- 指定映射类型 -->
<one-to-many class="Order"/>
</set>
cascade 属性和order-by 属性
<!-- 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>
映射一对一关联关系
基于外键映射的 1-1
1)many-to-one
其外键可以存放在任意一边
增加unique=“true” 属性来表示为1-1关联
2)另一端使用one-to-one元素
使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段
示例代码
1)创建两个类,如下为主要的属性
public class Department {
private Integer deptId;
private String deptName;
private Manager mgr;
}
public class Manager {
private Integer mgrId;
private String mgrName;
private Department dept;
}
2)实体关系配置
Department.hbm.xml
<many-to-one name="mgr" class="Manager" column="MGR_ID" unique="true"></many-to-one>
Manager.hbm.xml
<one-to-one name="dept" class="Department" property-ref="mgr"></one-to-one>
3)配置文件中配置映射
<mapping resource="com/atguigu/hibernate/one2one/foreign/Manager.hbm.xml"/>
<mapping resource="com/atguigu/hibernate/one2one/foreign/Department.hbm.xml"/>
4)测试
public void test() {
Department department=new Department();
department.setDeptName("DEPT-BB");
Manager manager=new Manager();
manager.setMgrName("MGR-BB");
department.setMgr(manager);
manager.setDept(department);
//2条insert语句,一条更新语句
session.save(department);
session.save(manager);
}
public void test() {
Department department=new Department();
department.setDeptName("DEPT-BB");
Manager manager=new Manager();
manager.setMgrName("MGR-BB");
department.setMgr(manager);
manager.setDept(department);
//2条insert语句
session.save(manager);
session.save(department);
}
public 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_id,可以在one-to-one上配置属性property-ref
Manager mgr = dept.getMgr();
System.out.println(mgr.getMgrName());
}
public void test() {
//在查询没有外键的实体对象时, 使用的左外连接查询, 一并查询出其关联的对象
//并已经进行初始化.
Manager mgr = (Manager) session.get(Manager.class, 1);
System.out.println(mgr.getMgrName());
System.out.println(mgr.getDept().getDeptName());
}
基于主键映射的 1-1
指一端的主键生成器使用 foreign 策略,表明根据”对方”的主键来生成自己的主键,自己并不能独立生成主键. 子元素指定使用当前持久化类的哪个属性作为 “对方”。
one-to-one 元素映射关联属性:
其one-to-one属性还应增加 constrained=“true” 属性
另一端增加one-to-one元素映射关联属性。
示例代码
1)实体关系配置
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">
<!-- Generated 2020??4??17?? ????3:05:04 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping package="com.atguigu.hibernate.one2one.foreign">
<class name="Department" table="DEPARTMENTS">
<id name="deptId" type="java.lang.Integer">
<column name="DEPT_ID" />
<generator class="foreign">
<param name="property">mgr</param>
</generator>
</id>
<property name="deptName" type="java.lang.String">
<column name="DEPT_NAME" />
</property>
<one-to-one name="mgr" class="Manager" constrained="true"></one-to-one>
</class>
</hibernate-mapping>
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">
<!-- Generated 2020??4??17?? ????3:05:04 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping package="com.atguigu.hibernate.one2one.foreign">
<class name="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="Department"></one-to-one>
</class>
</hibernate-mapping>
2)测试
public void test() {
Department department=new Department();
department.setDeptName("DEPT-BB");
Manager manager=new Manager();
manager.setMgrName("MGR-BB");
department.setMgr(manager);
manager.setDept(department);
//2条insert语句,一条更新语句
session.save(department);
session.save(manager);
//session.save(manager);
//session.save(department);
}
@Test
public void test() {
//1. 默认情况下对关联属性使用懒加载
Department dept = (Department) session.get(Department.class, 1);
System.out.println(dept.getDeptName());
session.close();
//2. 所以会出现懒加载异常的问题.
Manager mgr = dept.getMgr();
System.out.println(mgr.getMgrName());
}
@Test
public void test() {
//在查询没有外键的实体对象时, 使用的左外连接查询, 一并查询出其关联的对象
//并已经进行初始化.
Manager mgr = (Manager) session.get(Manager.class, 1);
System.out.println(mgr.getMgrName());
System.out.println(mgr.getDept().getDeptName());
}
映射多对多关联关系
单向 n-n
n-n 的关联必须使用连接表
实例代码
1)永久化类
public class Category {
private int id;
private String name;
private Set<Item> items=new HashSet<>();}
public class Item {
private Integer id;
private String name;}
2)实体关系配置
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">
<!-- Generated 2020??4??17?? ????5:22:02 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping package="com.atguigu.hibernate.n2n">
<class name="Category" table="CATEGORYS">
<id name="id" type="int">
<column name="ID" />
<generator class="native" />
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<set name="items" table="CATEGORIES_TIMES">
<key>
<column name="C_ID" />
</key>
<many-to-many class="Item" column="I_ID" />
</set>
</class>
</hibernate-mapping>
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">
<!-- Generated 2020??4??17?? ????5:22:02 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping>
<class name="com.atguigu.hibernate.n2n.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>
</class>
</hibernate-mapping>
3)测试方法
public void test() {
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);
//session.save(category1);
//session.save(category2);
session.save(item1);
session.save(item2);
//8条插入语句
session.save(category1);
session.save(category2);
}
public void test() {
//读取Category表中的数据
Category category=session.get(Category.class,3);
System.out.println(category.getName());
//使用中间连接表进行查询
Set<Item> items=category.getItems();
System.out.println(items.size());
}
双向n-n关联
1)双向 n-n 关联需要两端都使用集合属性
2)双向n-n关联必须使用连接表
3)对于双向 n-n 关联, 必须把其中一端的 inverse 设置为 true, 否则两端都维护关联关系可能会造成主键冲突.
实例代码
1)永久化测试类
public class Category {
private int id;
private String name;
private Set<Item> items=new HashSet<>();}
public class Item {
private Integer id;
private String name;
private Set<Category> categories=new HashSet<>();}
2)实体关系映射表
Category.hbm.xml
<set name="items" table="CATEGORIES_TIMES">
<key>
<column name="C_ID" />
</key>
<many-to-many class="Item" column="I_ID" />
</set>
Item.hbm.xml
<set name="categories" table="CATEGORIES_TIMES">
<key>
<column name="I_ID" />
</key>
<many-to-many class="Category" column="C_ID" />
</set>
映射继承关系
使用subclass进行映射
将域模型中的每一个实体对象映射到一个独立的表中,也就是说不用在关系数据模型中考虑域模型中的继承关系和多态。
1)父类和子类使用同一张表
2)需要一个辨别者列(discriminator)
3)使用 subclass 来映射子类,使用 class 或 subclass 的 discriminator-value 属性指定辨别者列的值
4)所有子类定义的字段都不能有非空约束
示例代码
1)永久化类定义
public class Person {
private Integer id;
private String name;
private int age;}
public class Student extends Person{
private String school;}
Person.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 2020??4??18?? ????3:08:43 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping package="com.atguigu.hibernate.one2one.foreign">
<class name="Person" table="PERSONS" discriminator-value="PERSON">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="native" />
</id>
<discriminator column="TYPE" type="string"></discriminator>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<property name="age" type="int">
<column name="AGE" />
</property>
<subclass name="Student" discriminator-value="STUDENT">
<property name="school" type="string" column="SCHOOL" ></property>
</subclass>
</class>
</hibernate-mapping>
3)配置文件配置
<mapping resource="com/atguigu/hibernate/one2one/foreign/Person.hbm.xml"/>
4)测试代码
/**
* 插入操作:
* 1. 对于子类对象只需把记录插入到一张数据表中.
* 2. 辨别者列有 Hibernate 自动维护.
*/
@Test
public void testSave(){
Person person = new Person();
person.setAge(11);
person.setName("AA");
session.save(person);
Student stu = new Student();
stu.setAge(22);
stu.setName("BB");
stu.setSchool("ATGUIGU");
session.save(stu);
}
/**
* 缺点:
* 1. 使用了辨别者列.
* 2. 子类独有的字段不能添加非空约束.
* 3. 若继承层次较深, 则数据表的字段也会较多.
*/
/**
* 查询:
* 1. 查询父类记录, 只需要查询一张数据表
* 2. 对于子类记录, 也只需要查询一张数据表
*/
@Test
public void testQuery(){
List<Person> persons = session.createQuery("FROM Person").list();
System.out.println(persons.size());
List<Student> stus = session.createQuery("FROM Student").list();
System.out.println(stus.size());
}
使用joined-subclass进行映射
对于继承关系中的子类使用同一个表,这就需要在数据库表中增加额外的区分子类类型的字段。
1)每个子类一张表
2)子类实例由父类表和子类表共同存储
3)每个子类使用 key 元素映射共有主键
4)子类增加的属性可以添加非空约束
实例代码
1)Person.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 2020??4??18?? ????3:08:43 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping package="com.atguigu.hibernate.one2one.foreign">
<class name="Person" table="PERSONS">
<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>
<property name="age" type="int">
<column name="AGE" />
</property>
<joined-subclass name="Student" table="STUDENTS">
<key column="STUDENT_id"></key>
<property name="school" type="string" column="SCHOOL"></property>
</joined-subclass>
</class>
</hibernate-mapping>
2)代码测试
/**
* 插入操作:
* 1. 对于子类对象至少需要插入到两张数据表中.
*/
@Test
public void testSave(){
Person person = new Person();
person.setAge(11);
person.setName("AA");
session.save(person);
Student stu = new Student();
stu.setAge(22);
stu.setName("BB");
stu.setSchool("ATGUIGU");
session.save(stu);
}
/**
* 优点:
* 1. 不需要使用了辨别者列.
* 2. 子类独有的字段能添加非空约束.
* 3. 没有冗余的字段.
*/
/**
* 查询:
* 1. 查询父类记录, 做一个左外连接查询
* 2. 对于子类记录, 做一个内连接查询.
*/
@Test
public void testQuery(){
List<Person> persons = session.createQuery("FROM Person").list();
System.out.println(persons.size());
List<Student> stus = session.createQuery("FROM Student").list();
System.out.println(stus.size());
}
使用union-subclass进行映射
域模型中的每个类映射到一个表,通过关系数据模型中的外键来描述表之间的继承关系。这也就相当于按照域模型的结构来建立数据库中的表,并通过外键来建立表之间的继承关系。
1)将每一个实体对象映射到一个独立的表中,即父类实例的数据保存在父表中,而子类实例的数据保存在子类表中
2)子类增加的属性可以有非空约束
实例代码
1)Person.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 2020??4??18?? ????3:08:43 by Hibernate Tools 3.5.0.Final -->
<hibernate-mapping package="com.atguigu.hibernate.one2one.foreign">
<class name="Person" table="PERSONS">
<id name="id" type="java.lang.Integer">
<column name="ID" />
<generator class="org.hibernate.id.enhanced.TableGenerator">
<param name="table_name">HIBERNATE_PK_TABLE</param>
<param name="value_column_name">GEN_VAL</param>
<param name="segment_column_name">GEN_PK</param>
<param name="segment_value">TEST_TABLE</param>
<param name="increment_size">1</param>
<param name="optimizer">pooled-lo</param>
</generator>
</id>
<property name="name" type="java.lang.String">
<column name="NAME" />
</property>
<property name="age" type="int">
<column name="AGE" />
</property>
<union-subclass name="Student" table="STUDENTS">
<property name="school" column="SCHOOL" type="string"></property>
</union-subclass>
</class>
</hibernate-mapping>
2)测试代码
/**
* 插入操作:
* 1. 对于子类对象只需把记录插入到一张数据表中.
*/
@Test
public void testSave(){
Person person = new Person();
person.setAge(11);
person.setName("AA");
session.save(person);
Student stu = new Student();
stu.setAge(22);
stu.setName("BB");
stu.setSchool("ATGUIGU");
session.save(stu);
}
/**
* 优点:
* 1. 无需使用辨别者列.
* 2. 子类独有的字段能添加非空约束.
*
* 缺点:
* 1. 存在冗余的字段
* 2. 若更新父表的字段, 则更新的效率较低
*/
/**
* 查询:
* 1. 查询父类记录, 需把父表和子表记录汇总到一起再做查询. 性能稍差.
* 2. 对于子类记录, 也只需要查询一张数据表
*/
@Test
public void testQuery(){
List<Person> persons = session.createQuery("FROM Person").list();
System.out.println(persons.size());
List<Student> stus = session.createQuery("FROM Student").list();
System.out.println(stus.size());
}