文章目录
单向多对一
一方
public class ProductDir {
private Long id;
private String name;
}
多方,外键在那边,那边就是多方
public class Product {
private Long id;
private String name;
// 多Product对一ProductDir
// 多个产品属于一个产品类型
// 外键在那个表,这个表就是多
private ProductDir dir;
}
xml配置
<hibernate-mapping package="cn.itsource.hibernate.day2.manytoone">
<!--一方-->
<class name="ProductDir" table="t_product_dir">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
</class>
<!--多方-->
<class name="Product" table="t_product">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<!-- class一方的类名,不写也可以,Hibernate通过反射获取,只有在多对一class可以不写 -->
<!-- column外键的列名,也可以不写,用name属性的值 -->
<many-to-one name="dir" class="ProductDir" column="dir_id" lazy="true"/>
</class>
</hibernate-mapping>
many-to-one标签说明
name: java的属性
class: 唯一一个地方可以不配置class,反射获取
column: 外键的列名,可以不配置,默认使用name属性值
lazy: 延迟加载,为false时立即加载
保存
先保存一方,再保存多方,否则出现多个update语句
// 一方
ProductDir dir = new ProductDir();
dir.setName("类型1");
// 多方
Product product = new Product();
product.setName("产品1");
Product product2 = new Product();
product2.setName("产品2");
// 设置外键,多方到一方的关系
product.setDir(dir);
product2.setDir(dir);
Session session = HibernateUtils.getSession(); session.beginTransaction();
session.save(dir);
session.save(product);
session.save(product2);
//提交事务时,执行sql
session.getTransaction().commit();
session.close();
错误用法(先保存多方,再保存一方)
,commit提交时发现多方出现脏数据,会发出额外的update(脏数据更新)
//错误用法
session.save(product);
session.save(product2);
session.save(dir);
查询
正确查询顺序:先获取多方再获取一方
默认是延迟加载一方(只会发sql查询多方,不会发sql查询一方;延迟加载查询出了一方的主键,所以当获取一方主键时不会发sql,获取非主键时会马上发sql查询一方),可以配置xml中的lazy
@Test
public void get() throws Exception {
Session session = HibernateUtils.getSession();
Product product = (Product) session.get(Product.class, 1L);
System.out.println(product);
System.out.println("多方的外键==一方主键:" + product.getDir().getId()) ;
// 延迟加载
// 通过javassist-3.18.1-GA.jar代理模式:ProductDir_$$_jvst830_0代理对象
System.out.println("一方的class:" + product.getDir().getClass());
System.out.println("一方非主键属性:" + product.getDir().getName());
session.close();
}
通过获取多方,再获取一方,判断一方是否为null,确定外键是否为null
@Test
public void get2() throws Exception {
Session session = HibernateUtils.getSession();
Product product = (Product) session.get(Product.class, 1L);
if (product.getDir() == null) {
System.out.println("当前产品没有类型");
} else {
System.out.println("当前产品有类型");
}
session.close();
}
报错
org.hibernate.LazyInitializationException
延迟加载异常
原因:提前关闭了session
session.close();
System.out.println(product.getDir());
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: cn.i tsource.hibernate.day2.manytoone.ProductDir
保存了一个临时(瞬时)对象
原因:在多方实例化了一方,因为实例化之后一方没有主键,也就是多方没有外键值,保存会出错
public class Product
private Long id;
private String name;
//错误用法,hibernate处理时认为有id,但是本质没id
private ProductDir dir = new ProductDir();
//ProductDir dir = new ProductDir()建立了关联,所以注销下面2行
//product.setDir(dir);
//product2.setDir(dir);
session.save(product);
session.save(product2);
正确用法
private ProductDir dir;
@ManyToOne
单向一对多(一般不用)
一方
public class ProductDir {
private Long id;
private String name;
//一对多:一个产品类型包含多个产品
//必须实例化集合
private Set<Product> products = new HashSet<Product>();
}
多方
public class Product {
private Long id;
private String name;
}
xml配置
<hibernate-mapping package="cn.itsource.hibernate.day2.manytoone">
<!--一方-->
<class name="ProductDir" table="t_product_dir">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<!--//一对多:一个产品类型包含多个产品-->
<!--private Set<Product> products = new HashSet<Product>();-->
<set name="products">
<key column="dir_id">
<one-to-many class="Product">
</set>
</class>
<class name="Product" table="t_product">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
</class>
</hibernate-mapping>
set标签说明
name: java的属性
column: 外键的列名,在一方配置,生成时也在多方的表中
class: 多方的类名
保存
必定会产生额外update语句,因为是一方维护外键值
// 一方
ProductDir dir = new ProductDir();
dir.setName("类型1");
// 多方
Product product = new Product();
product.setName("产品1");
Product product2 = new Product();
product2.setName("产品2");
// 建立一方到多方的关系(保存外键的值 )
dir.getProducts().add(product);
dir.getProducts().add(product2);
Session session = HibernateUtils.getSession();
session.beginTransaction();
//多方映射文件管理不了dir_id字段,所以一开始插入,没有插入该字段
session.save(product);
session.save(product2);
//出现额外update语句,持久数据,脏数据更新
session.save(dir);
session.getTransaction().commit();
session.close();
查询
正确查询:先获取一方再获取多方
默认是延迟加载一方(只会发sql查询多方,不会发sql查询一方;延迟加载查询出了一方的主键,所以当获取一方主键时不会发sql,获取非主键时会马上发sql查询一方),可以配置xml中的lazy
@Test
public void get() throws Exception {
Session session = HibernateUtils.getSession();
ProductDir dir = (ProductDir) session.get(ProductDir.class, 1L);
System.out.println(dir);
System.out.println(dir.getProducts().getClass()) ;
System.out.println(dir.getProducts()) ;
session.close();
}
易错点
dir.getProducts() 不会为 null
@Test
public void get2() throws Exception {
Session session = HibernateUtils.getSession();
ProductDir dir = (ProductDir) session.get(ProductDir.class, 1L);
if (dir.getProducts() == null) {
System.out.println("当前产品没有类型");
} else {
System.out.println("当前产品有类型");
}
session.close();
}
原因:只会发一条sql查询productDir,不会查询product表,所以dir.getProducts() 永远不为null
正确用法:dir.getProducts().size() > 0发两条sql
if (dir.getProducts().size() > 0) {
@OneToMany
双向一对多/双向多对一
一方
public class ProductDir {
private Long id;
private String name;
//一对多:一个产品类型包含多个产品
private Set<Product> products = new HashSet<Product>();
}
多方
public class Product {
private Long id;
private String name;
// 多对一:多个产品属于一个产品类型
private ProductDir dir;
}
xml配置
<hibernate-mapping package="cn.itsource.hibernate.day2.manytoone">
<!--一方-->
<class name="ProductDir" table="t_product_dir">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<set name="products" cascade="save-update" inverse="true">
<key column="dir_id">
<one-to-many class="Product">
</set>
</class>
<!--多方-->
<class name="Product" table="t_product">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<many-to-one name="dir" class="ProductDir" column="dir_id" lazy="true"/>
</class>
</hibernate-mapping>
set标签说明
name: java的属性
cascade: 级联操作
inverse: 反转,默认是false不反转,自己管理外键的关系(插入或者更新外键的权力)
column: 外键的列名,在一方配置,生成时也在多方的表中
class: 多方的类名
many-to-one标签说明
name: java的属性
class: 唯一一个地方可以不配置class,反射获取
column: 外键的列名,可以不配置,默认使用name属性值
lazy: 延迟加载,为false时立即加载
cascade 级联操作
级联操作选项
- save-update 级联保存获取更新
- delete 级联删除
- all=save-update + delete
- delete-orphan 删除所有和当前对象解除关联关系的对象
- all-delete-orphan 最全配置,只有在组合关系菜使用
inverse 反转
控制反转,默认是false不反转,自己管理外键的关系(插入或者更新外键的权力)
set有该属性,默认inverse为false,即配置了set的类管理外键;两边inverse都为false时,两边都可以管理
级联保存
级联保存,保存1方的 同时保存2个多方
// 一方
ProductDir dir = new ProductDir();
dir.setName("类型1");
// 多方
Product product = new Product();
product.setName("产品1");
Product product2 = new Product();
product2.setName("产品2");
// 设置外键,多方到一方的关系
product.setDir(dir);
product2.setDir(dir);
//建立一方到多方的关系
dir.getProducts().add(product);
dir.getProducts().add(product2);
Session session = HibernateUtils.getSession(); session.beginTransaction();
session.save(dir);
//提交事务时,执行sql
session.getTransaction().commit();
session.close();
级联删除
必须先删除多方,再删除一方
<set name="products" cascade="delete" inverse="true">
<key column="dir_id">
<one-to-many class="Product">
</set>
@Test
public void deleteAll() throws Exception {
Session session = HibernateUtils.getSession();
session.getTransaction().begin();
ProductDir dir = (ProductDir) session.get(ProductDir.class, 1L);
session.delete(dir);
session.getTransaction().commit();
session.close();
}
删除多方
先获取多方,再删除多方
@Test
public void deleteAll() throws Exception {
Session session = HibernateUtils.getSession();
session.getTransaction().begin();
Product product = (Product) session.get(Product.class, 1L);
session.delete(product);
session.getTransaction().commit();
session.close();
}
从一方删除多方
先获取一方,通过一方来删除多方
<!-- cascade="delete-orphan" 删除所有和当前对象解除关联关系的对象。 -->
<set name="products" cascade="delete-orphan" inverse="true">
<key column="dir_id">
<one-to-many class="Product">
</set>
@Test
public void delete2() throws Exception {
Session session = HibernateUtils.getSession();
session.getTransaction().begin();
ProductDir dir = (ProductDir) session.get(ProductDir.class, 1L);
Product product = (Product) session.get(Product.class, 1L);
// 和当前对象解除关联关系,并删除
dir.getProducts().remove(product);
//删除多条时
//dir.getProducts().clear();
session.getTransaction().commit();
session.close();
}
不删数据,解除多方和一方关系
不删除数据,解除多方和一方关系(dir_id字段值为null)
方式一
多方管理
<set name="products" inverse="true">
ProductDir dir = (ProductDir) session.get(ProductDir.class, 1L);
Set<Product> products = dir.getProducts();
for (Product product : products) {
product.setDir(null);
}
方式二 (不建议)
一方管理
<set name="products" >
ProductDir dir = (ProductDir) session.get(ProductDir.class, 1L);
dir.getProducts().clear();
方式三
直接写hql
//下面p.dir 和 p.dir.id 都是指的外键
String hql = "update from Product p where p.dir=?";
//hql="update from Product p where p.dir.id=?";
集合映射
常用list 和 set
set 一般用在多对多,也有一对多
list一般只是用在组合关系
public class ProductDir {
private Long id;
private String name;
//一对多:一个产品类型包含多个产品
private List<Product> products = new ArrayList<Product>();
}
list会发update修改索引
product表增加了listIndex列,维护了索引,增加了开销,性能差
list-index标记list集合在数据库的索引
<list name="products" >
<key column="dir_id">
<!--list 可以重复,有序的集合,必须配置集合的索引-->
<list-index column="listIndex"/>
<one-to-many class="Product">
</list>
bag代替list,同时没有维护索引,性能高
order-by 排序
<bag name="products" inverse="true" cascade="save-update" order-by="price desc">
<key column="dir_id">
<one-to-many class="Product">
</bag>
单向多对多
public class Role {
private Long id;
private String name;
}
public class User {
private Long id;
private String name;
// 多对多:一个用户拥有多个角色
private Set<Role> roles = new HashSet<Role>();
}
xml配置
<hibernate-mapping package="cn.itsource.hibernate.day3.manytomany">
<class name="Role">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
</class>
<class name="User">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<!-- // 多对多:一个用户拥有多个角色 -->
<!-- private Set<Role> roles = new HashSet<Role>(); -->
<!-- 中间表双主键,双外键 -->
<set name="roles" table="user_role">
<!-- 当前类在中间表的外键列名 -->
<key column="user_id" />
<!-- class="关联类的类名" column="关联类在中间表的外键列名 " -->
<many-to-many class="Role" column="role_id" />
</set>
</class>
</hibernate-mapping>
set标签说明
name: java的属性
table: 中间表名
column: 当前类在中间表的外键列名
many-to-many标签说明
class: 关联类的类名
column: 关联类在中间表的外键列名
保存
public void save() throws Exception {
User user1 = new User("user1");
User user2 = new User("user2");
Role role1 = new Role("role1");
Role role2 = new Role("role2");
Role role3 = new Role("role3");
// 保存中间表:建立用户到角色关系
user1.getRoles().add(role1);
user1.getRoles().add(role2);
user2.getRoles().add(role1);
user2.getRoles().add(role2);
user2.getRoles().add(role3);
Session session = HibernateUtils.getSession();
session.beginTransaction();
session.save(user1);
session.save(user2);
session.save(role1);
session.save(role2);
session.save(role3);
session.getTransaction().commit();
session.close();
}
查询
延迟加载
默认是延迟加载(延迟加载查询出了关联类的主键,所以当获取关联类主键时不会发sql,获取非主键时会马上发sql查询关联类)
@Test
public void get() throws Exception {
Session session = HibernateUtils.getSession();
User user = (User) session.get(User.class, 1L);
System.out.println(user);
System.out.println(user.getRoles().getClass());
System.out.println(user.getRoles());
session.close();
}
@ManyToMany
双向多对多
public class Role {
private Long id;
private String name;
//多对多:一个角色下有多个用户
private Set<User> users = new HashSet<>();
}
public class User {
private Long id;
private String name;
// 多对多:一个用户拥有多个角色
private Set<Role> roles = new HashSet<>();
}
xml配置
<hibernate-mapping package="cn.itsource.hibernate.day3.manytomany">
<class name="Role">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<!--多对多:一个角色下有多个用户-->
<set name="users" table="user_role">
<key column="role_id" />
<many-to-many class="User" column="user_id" />
</set>
</class>
<class name="User">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<!-- // 多对多:一个用户拥有多个角色 -->
<!-- private Set<Role> roles = new HashSet<Role>(); -->
<!-- 中间表双主键,双外键 -->
<set name="roles" table="user_role" cascade="save-update">
<!-- 当前类在中间表的外键列名 -->
<key column="user_id" />
<!-- class="关联类的类名" column="关联类在中间表的外键列名 " -->
<many-to-many class="Role" column="role_id" />
</set>
</class>
</hibernate-mapping>
set标签说明
name: java的属性
table: 中间表名
column: 当前类在中间表的外键列名
many-to-many标签说明
class: 关联类的类名
column: 关联类在中间表的外键列名
级联保存
cascade=“save-update”
public void save() throws Exception {
User user1 = new User("user1");
User user2 = new User("user2");
Role role1 = new Role("role1");
Role role2 = new Role("role2");
Role role3 = new Role("role3");
// 多对多的级联保存,只能一边建立关系
user1.getRoles().add(role1);
user1.getRoles().add(role2);
user2.getRoles().add(role1);
user2.getRoles().add(role2);
user2.getRoles().add(role3);
Session session = HibernateUtils.getSession();
session.beginTransaction();
session.save(user1);
session.save(user2);
session.getTransaction().commit();
session.close();
}
级联删除
cascade=“delete”
Session session = HibernateUtils.getSession();
session.beginTransaction();
User user = (User) session.get(User.class, 1L);
session.delete(user);
session.getTransaction().commit();
session.close();
删除user1
删除了user表 和 user_role表
user必须要管理中间表的权限,inverse=false
Session session = HibernateUtils.getSession();
session.beginTransaction();
User user = (User) session.get(User.class, 1L);
session.delete(user);
session.getTransaction().commit();
session.close();
删除user1的所有角色
删除了 user_role表
user必须要管理中间表的权限,inverse=false
Session session = HibernateUtils.getSession();
session.beginTransaction();
User user = (User) session.get(User.class, 1L);
user.getRoles().clear();
session.getTransaction().commit();
session.close();
删除user1的一个角色
删除了 user_role表
user必须要管理中间表的权限,inverse=false
Session session = HibernateUtils.getSession();
session.beginTransaction();
User user = (User) session.get(User.class, 1L);
Role role = (Role) session.get(Role.class, 1L);
user.getRoles().remove(role);
session.getTransaction().commit();
session.close();
修改user1的角色
修改了 user_role表
user必须要管理中间表的权限,inverse=false
Session session = HibernateUtils.getSession();
session.beginTransaction();
User user = (User) session.get(User.class, 1L);
Role role1 = (Role) session.get(Role.class, 1L);
Role role2 = (Role) session.get(Role.class, 1L);
user.getRoles().remove(role1);
user.getRoles().add(role2);
session.getTransaction().commit();
session.close();
唯一外键一对一
建议使用,因为可以修改为一对多
主一
public class QQ {
private Long id;
private String number;
// 一对一,一个qq号码对应一个qq空间
private QQZone zone;
}
从一
public class QQZone {
private Long id;
private String name;
// 一对一,一个qq空间输入一个qq号码
private QQ qq;
}
xml配置
<hibernate-mapping package="cn.itsource.hibernate.day2.manytoone">
<class name="QQ">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<!--private QQZone zone; -->
<one-to-one name="zone"/>
</class>
<class name="QQZone">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<!--private QQ qq; -->
<!--就是一对多的特列:在外键列名添加唯一约束-->
<many-to-one name="qq" class="QQ" colume="qq_id" unique="true"/>
</class>
</hibernate-mapping>
one-to-one标签说明
name: java的属性
many-to-one标签说明
name: java的属性
class: 唯一一个地方可以不配置class,反射获取
column: 外键的列名,可以不配置,默认使用name属性值
unique: 控制唯一外键
保存
先保存主一,再保存从一
public void save() throws Exception {
QQ qq = new QQ();
qq.setNumber("123456");
QQZone zone = new QQZone();
zone.setName("风口上的猪");
// 建立关系
qq.setZone(zone);
zone.setQq(qq);
Session session = HibernateUtils.getSession();
session.beginTransaction();
// 先保存主一
session.save(qq);
session.save(zone);
session.getTransaction().commit();
session.close();
}
查询
没有延迟加载
Session session = HibernateUtils.getSession();
QQ qq = (QQ)session.get(QQ.class, 1L);
session.close();
@OneToOne
共享主键一对一
从一的主键同时是外键,主一和从一id相同
主一
public class QQ {
private Long id;
private String number;
// 一对一,一个qq号码对应一个qq空间
private QQZone zone;
}
从一
public class QQZone {
private Long id;
private String name;
// 一对一,一个qq空间输入一个qq号码
private QQ qq;
}
xml配置
<hibernate-mapping package="cn.itsource.hibernate.day2.manytoone">
<class name="QQ">
<id name="id">
<generator class="native" />
</id>
<property name="name" />
<!--private QQZone zone; -->
<one-to-one name="zone"/>
</class>
<class name="QQZone">
<!--id列名即是主键又是外键 -->
<id name="id">
<!--主键生成方式:foregin外键,值来自于当前qq属性对应的主键 -->
<generator class="foreign" >
<!--qq对应到<one-to-one name="qq"/> -->
<param name="property">qq</param>
</generator>
</id>
<property name="name" />
<!--private QQ qq; -->
<!--constrained="true"建立QQZone的主键有外键约束 -->
<one-to-one name="qq" constrained="true"/>
</class>
</hibernate-mapping>
one-to-one标签说明
name: java的属性
constrained:建立主键的外键约束