Hibernate复杂关系级联操作

单向多对一

一方

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 级联操作

级联操作选项

  1. save-update 级联保存获取更新
  2. delete 级联删除
  3. all=save-update + delete
  4. delete-orphan 删除所有和当前对象解除关联关系的对象
  5. 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:建立主键的外键约束

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值