many-to-many和lazy

本文介绍Hibernate中多对多关联映射方法及懒加载(Lazy)策略的应用。包括User与Role之间的关联模型设计、映射文件配置,以及如何实现双向关联。此外,还详细解释了懒加载的概念、使用场景及其限制。
传智播客:many-to-many和lazy (2009-07-16 11:29:22)

Many-to-many(user--->role)
对象模型:

两者关联需要一个中间表
类的设计
Role:
public class Role {
 private int id;
 private String name;
}
其对应的映射文件,由于这一段不维护关系,所以都是普通属性
<hibernate-mapping>
 <class name="com.itcast.hibernate.Role" table="t_role">
  <id name="id">
   <generator class="native"/>
  </id>
  <property name="name"/>
 </class>
</hibernate-mapping>

User类的设计:
public class User {
 private int id; 
 private String name; 
 private Set roles;
}
注意这里的映射文件:

<hibernate-mapping>
 <class name="com.itcast.hibernate.User" table="t_user">
  <id name="id">
   <generator class="native"/>
  </id>
  <property name="name"/>

table="t_user_role":指明生成中间表的名字
  <set name="roles" table="t_user_role">
column="userid"指明本表主键映射到中间表以后的字段
   <key column="userid"/>
class="com.itcast.hibernate.Role" column="roleid":指明自己内部set中所存放的类型和保存到中间表的字段名
   <many-to-many class="com.itcast.hibernate.Role" column="roleid"/>
  </set>

 </class>
</hibernate-mapping>

如果想把刚才的映射改成双向的,其实数据库中存储结构不会发生任何变化,只是在Role对象中加一个Set集合保存所有对应的user就可以了。

和前面相比,只有Role的类中加了一个
private Set users;
其对应的映射文件,注意:Role的映射文件中的Set标签中的内容和User中的Set标签中的内容有很多必须对应,这里把两个放在一起对比,颜色相同的就是必须相同的


Role中的
<set name="users" table="t_user_role" order-by="userid">
   <key column="roleid"/>
   <many-to-many class="com.itcast.hibernate.User" column="userid"/>
  </set>
User中的
<set name="roles" table="t_user_role">
   <key column="userid"/>
   <many-to-many class="com.itcast.hibernate.Role" column="roleid"/>
</set>


懒加载
Lazy:只有在真正使用的时候才会创建对象或者发出sql语句

hibernate lazy策略可以使用在:
  <class>标签上,可以取值:true/false,,3.0以后的版本都是默认为true
  <property>标签上,可以取值:true/false需要类增强工具,使用较少
  <set><list>等集合标签上,可以取值:true/false/extra
  <one-to-one><many-to-one>单端关联上,可以取值:false/proxy/noproxy
 
lazy概念:只有真正使用该对象时,才会创建,对于hibernate而言,正真使用的时候才会发出sql

hibernate支持lazy策略只有在session打开状态下有效,lazy的生命周期和session一样
Session一旦关闭,再调用lzay控制的类,就会发生lazy的初始化异常。

<class>标签上的lazy特性只对普通属性起作用

hibernate在集合上的lazy策略,可以取值:true/false/extra
<class>标签上的lazy不会影响到集合上的lazy特性

hibernate在单端关联上的lazy策略,可以取值:false/proxy/noproxy
Proxy:代理,也就是使用代理实现lazy
Noproxy:不使用代理实现lazy,通过修改class的字节码来实现。这个需要增强工具,基本很少使用


继承映射的概念和继承映射中的lazy
对于继承映射,Hibernate会处理子类父类间的关系,这个时候需要处理多态的问题。
Load支持Lazy延迟加载,返回的是一个代理对象,这个代理类是持久化的类的子类。比如animal类有子类pig和bird。当我们加载animal的时候,实际返回的是animal的代理类,而Hibernate生成代理的方式是通过继承真实类的方式。所以这个代理类和pig和bird类是平级的,都是animal的子类。所以instanceof方法在这里是不可行的,无法反应出真正的对象类型。
也就是Load的默认方式(lazy加载)是不支持多态查询的。

多态查询:在habernate加载对象的时候可以鉴别出真正的类型。
如果把<class>标签上的lazy设置为false。这样就不会有那个代理类,也就可以支持多态查询了

使用get方法,由于get方法不是Lazy加载,所以可以支持多态查询。

使用hql也可以支持多态查询

对于继承的情况
每颗继承树映射到一张表
类的关系图如下
对应的映射文件是:
 <class name="Animal" table="t_animal" lazy="false">
  <id name="id">
   <generator class="native"/>
  </id>
指明在表中需要添加一个名为“type”的字段,用于区分子类
  <discriminator column="type" type="string"/>
  <property name="name"/>
  <property name="sex"/>

指明这个类是当前类的子类,其区分字段“type”为“p”
  <subclass name="Pig" discriminator-value="P">
   <property name="weight"/>
  </subclass>
  <subclass name="Bird" discriminator-value="B">
   <property name="height"/>
  </subclass>
 </class>

优点:效率高
缺点:有冗余字段,而且当某些可能冗余的字段设置为not null的时候表就不能用了。
多数情况下使用这种形式。

1、理解如何映射
  因为类继承树肯定是对应多个类,要把多个类的信息存放在一张表中,必须有某种机制来区分哪些记录是属于哪个类的。
 这种机制就是,在表中添加一个字段,用这个字段的值来进行区分。用hibernate实现这种策略的时候,有如下步骤:
 父类用普通的<class>标签定义
 在父类中定义一个discriminator,即指定这个区分的字段的名称和类型
 如:<discriminator column=”XXX” type=”string”/>
 1.子类使用<subclass>标签定义,在定义subclass的时候,需要注意如下几点:
 2.Subclass标签的name属性是子类的全路径名
 3.在Subclass标签中,用discriminator-value属性来标明本子类的discriminator字段(用来区分不同类的字段)
 4.的值Subclass标签,既可以被class标签所包含(这种包含关系正是表明了类之间的继承关系),也可以与class标签平行。当subclass标签的定义与class标签平行的时候,需要在subclass标签中,添加extends属性,里面的值是父类的全路径名称。子类的其它属性,像普通类一样,定义在subclass标签的内部。

2、理解如何存储
 存储的时候hibernate会自动将鉴别字段值插入到数据库中,在加载数据的时候,hibernate能根据这个鉴别值正确的加载对象

<think>我们正在处理一个关于Hibernate中`<many-to-one>`映射错误的问题。根据引用内容,用户可能遇到配置错误或运行时异常。以下将分析可能的原因并提供解决方案。 ### 常见错误及解决方案 #### 1. **配置错误:缺少必需属性** - **错误示例**:未指定`column`属性或`class`属性。 ```xml <!-- 错误配置 --> <many-to-one name="department" /> ``` - **解决方案**:必须指定`column`(外键列)`class`(目标实体类)属性。 ```xml <!-- 正确配置 --> <many-to-one name="department" class="com.example.Department" column="department_id" /> ``` #### 2. **延迟加载导致`LazyInitializationException`** - **原因**:当会话关闭后尝试访问未初始化的代理对象(引用[3])。 ```java // 错误示例:在会话外访问关联对象 Employee emp = session.get(Employee.class, 1); session.close(); Department dept = emp.getDepartment(); // 抛出LazyInitializationException ``` - **解决方案**: - **立即加载**:使用`fetch="join"`或HQL的`JOIN FETCH`。 ```xml <many-to-one name="department" fetch="join" ... /> ``` ```java // HQL示例 Employee emp = session.createQuery( "FROM Employee e JOIN FETCH e.department WHERE e.id = :id", Employee.class) .setParameter("id", 1) .getSingleResult(); ``` - **在会话内访问**:确保在会话关闭前访问关联对象。 #### 3. **外键约束问题** - **现象**:插入或更新时因外键无效失败。 - **解决方案**: - 确保被引用的实体已持久化。 - 检查数据库外键约束是否匹配Hibernate配置。 #### 4. **类型不匹配** - **错误**:关联字段类型与目标实体ID类型不一致。 - 例如:`Department`的ID为`Long`,但`Employee`的`department_id`字段配置为`Integer`。 - **解决方案**:统一类型,或使用`type`属性指定转换器。 #### 5. **集合映射冲突** - **错误**:同时配置双向关联时,`<many-to-one>`与`<set>`/`<list>`的`inverse`设置冲突(引用[2])。 - **解决方案**: - 在"一"方(`Department`)设置`inverse="true"`: ```xml <!-- Department.hbm.xml --> <set name="employees" inverse="true"> <key column="department_id"/> <one-to-many class="Employee"/> </set> ``` - 代码中从"多"方(`Employee`)维护关联: ```java Employee emp = new Employee(); emp.setDepartment(dept); // 只需设置多对一关系 ``` ### 高级调试技巧 - **启用SQL日志**:在`hibernate.cfg.xml`中添加: ```xml <property name="show_sql">true</property> <property name="format_sql">true</property> ``` - **检查代理初始化**:使用`Hibernate.initialize()`强制加载: ```java Employee emp = session.get(Employee.class, 1); Hibernate.initialize(emp.getDepartment()); // 显式初始化 ``` ### 示例配置 ```xml <!-- Employee.hbm.xml --> <class name="Employee" table="employees"> <id name="id" column="id"> <generator class="native"/> </id> <many-to-one name="department" class="Department" column="department_id" not-null="true" /> </class> <!-- Department.hbm.xml --> <class name="Department" table="departments"> <id name="id" column="id"> <generator class="native"/> </id> <set name="employees" inverse="true"> <key column="department_id"/> <one-to-many class="Employee"/> </set> </class> ``` > **关键原则**: > - 双向关联中由"多"方维护关系可提高性能[^2]; > - 避免在分离状态下访问延迟加载的关联对象[^3]。 --- §§相关问题§§ 1. Hibernate中如何配置单向一对多关系? 2. 什么是Hibernate的`inverse`属性?它如何影响性能? 3. 如何解决Hibernate的`org.hibernate.MappingException`? 4. Hibernate 6中`<many-to-one>`映射有哪些变化?</think>### Hibernate `<many-to-one>` 映射错误解决方案 以下是针对 Hibernate 中 `<many-to-one>` 映射常见错误的完整解决方案: --- #### 1. **基本配置错误** **错误示例**:缺少必需属性或类型不匹配 ```xml <!-- 错误配置 --> <many-to-one name="department" /> ``` **解决方案**: ```xml <!-- 正确配置 --> <many-to-one name="department" class="com.example.Department" <!-- 必须指定目标实体类 --> column="department_id" <!-- 明确外键列名 --> not-null="true" <!-- 按需设置空值约束 --> cascade="save-update" /> <!-- 级联操作配置 --> ``` **关键点**: - `class` 属性必须使用完全限定类名 - `column` 需与数据库外键列名一致 - 使用 `cascade` 控制级联操作行为[^2] --- #### 2. **延迟加载问题 (LazyInitializationException)** **错误现象**: ```java Employee emp = session.get(Employee.class, 1); session.close(); Department dept = emp.getDepartment(); // 抛出LazyInitializationException ``` **解决方案**: ```xml <!-- 方法1:关闭延迟加载 --> <many-to-one name="department" fetch="join" lazy="false"/> <!-- 方法2:在会话内初始化 --> Employee emp = session.createQuery( "FROM Employee e JOIN FETCH e.department WHERE e.id = :id", Employee.class) .setParameter("id", 1) .getSingleResult(); ``` **最佳实践**: - 在视图层使用 `Hibernate.initialize(emp.getDepartment())` 初始化代理 - 使用 Open Session in View 模式(需谨慎)[^3] --- #### 3. **双向关联不一致** **错误配置**(单向关联导致数据不一致): ```xml <!-- Employee.hbm.xml --> <many-to-one name="department" class="Department" column="dept_id"/> <!-- Department.hbm.xml 缺少反向关联 --> ``` **正确配置**: ```xml <!-- Department.hbm.xml 添加反向关联 --> <set name="employees" inverse="true"> <key column="dept_id"/> <one-to-many class="Employee"/> </set> ``` **代码维护**: ```java // 正确维护双向关联 Department dept = new Department(); Employee emp = new Employee(); emp.setDepartment(dept); // 设置多对一 dept.getEmployees().add(emp); // 设置一对多 ``` --- #### 4. **类型转换错误** **常见场景**:JSON 类型映射 ```java // 实体类 public class Employee { @Type(type = "json") // 自定义类型 private Department department; } ``` **解决方案**: 1. 添加依赖: ```xml <dependency> <groupId>com.vladmihalcea</groupId> <artifactId>hibernate-types-52</artifactId> <version>2.14.0</version> </dependency> ``` 2. 注册自定义类型: ```java @TypeDef(name = "json", typeClass = JsonType.class) public class Employee { @Type(type = "json") private Department department; } ``` Hibernate 会自动处理 JSON 列的映射转换[^1] --- #### 5. **性能优化 (N+1 问题)** **错误现象**:循环访问关联对象时产生大量查询 ```java List<Employee> employees = session.createQuery("FROM Employee").list(); for (Employee emp : employees) { emp.getDepartment().getName(); // 每个循环触发新查询 } ``` **解决方案**: ```sql <!-- 使用批量抓取 --> <many-to-one name="department" batch-size="10"/> <!-- 或使用HQL连接抓取 --> List<Employee> employees = session.createQuery( "SELECT e FROM Employee e JOIN FETCH e.department", Employee.class).list(); ``` --- #### 完整配置示例 **实体关系**: ```java public class Employee { private Long id; private Department department; // getters/setters } public class Department { private Long id; private Set<Employee> employees = new HashSet<>(); // getters/setters } ``` **映射文件**: ```xml <!-- Employee.hbm.xml --> <class name="Employee" table="employees"> <id name="id" column="id"> <generator class="identity"/> </id> <many-to-one name="department" class="Department" column="dept_id" cascade="save-update" fetch="select" lazy="proxy"/> </class> <!-- Department.hbm.xml --> <class name="Department" table="departments"> <id name="id" column="id"> <generator class="identity"/> </id> <set name="employees" inverse="true"> <key column="dept_id"/> <one-to-many class="Employee"/> </set> </class> ``` --- **调试建议**: 1. 启用 Hibernate SQL 日志: ```properties hibernate.show_sql=true hibernate.format_sql=true ``` 2. 使用 `hibernate.hbm2ddl.auto=validate` 验证映射 3. 检查数据库外键约束是否匹配 > **关键原则**:保持数据库外键、实体关联 Hibernate 映射三者一致[^2] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值