记录1:在双向1-N(一对多)的关联关系中的映射文件配置问题
例:学生(多端)和班级(一端)的持久化类的配置问题:这里我们定义持久化类:Student.java和StuClass,java。同时设置Student.hbm.xml和StuClass.hbm.xml文件。项目的整体结构如下:
在Studentjava文件中的属性设置如下(包含定义是属性的时候需要注意的一些事项):
private int StuId;
private String StuName;
private String Sex;
private String StuNum;
private Date Birthday;
private String Policital;
private String Nation;
private String Phone;
private Map<String,String> School = new HashMap<String,String>();//使用集合的时候我们需要注意的问题是:Map集合在声明的时候我们必须要声明为接口(HashMap接口)
//一定要使用它的实现类来进行初始化
private StuClass stuClass;//在多端持久类中创建的class属性
public StuClass getStuClass() {
return stuClass;
}
同时,我们在一端的StuClass.java文件中的定义属性如下(这里需要注意的是在设置Student属性的时候我们尽量使用的是set集合(当然,list和map集合也可以):
private int ClassId;
private String ClassName;
private String ClassNum;
//在一端创建的student集合属性
private Set<Student> student;
接着我们在Student.hbm,xml中的配置如下:
<class name="Bean.Student" table="student" lazy="false">
<!-- 主键映射 -->
<id name="StuId" type="java.lang.Integer">
<!--表示对应表中的列名-->
<column name="StuId"/>
<!--选择native表示的是根据底层数据库自动选择主键的自增策略-->
<generator class="native"/>
</id>
<!-- 非主键(属性)设置 -->
<property name="StuName" column="StuName" type="string"/>
<property name="Sex" column="Sex" type="string"/>
<property name="StuNum" column="StuNum" type="string"/>
<property name="Birthday" column="Birthday" type="java.util.Date"/>
<property name="Policital" column="Policital" type="string"/>
<property name="Nation" column="Nation" type="string"/>
<property name="Phone" column="Phone" type="string"/>
<!--下面是map集合映射-->
<map name="School" table="education" cascade="save-update" lazy="false">
<key column="StuId" not-null="true"/>
<map-key type="string" column="SchoolType"/>
<element type="string" column="SchoolName"/>
</map>
<!--在多端增加的外键属性配置-->
<many-to-one name="stuClass" column="ClassId" cascade="save-update" lazy="false"/>
</class>
注意:我们在使用<many-to-one>标签的时候,其中的name属性代表的是在多端实体中的定义的一端对象的名称,也就是stuClass;其中的column属性是我们在一端实体类中定义的主键,这里就是再StuClass类中所谓的ClassId属性。(ps:其他属性后边会有详细的解释。)
下面是在StuClass.hbm.xml中的配置信息:
<hibernate-mapping>
<class name="Bean.StuClass" table="stuclass" lazy="false">
<!-- 主键映射 -->
<id name="ClassId" type="java.lang.Integer">
<!--表示对应表中的列名-->
<column name="ClassId"/>
<!--选择native表示的是根据底层数据库自动选择主键的自增策略-->
<generator class="native"/>
</id>
<!-- 非主键(属性)设置 -->
<property name="ClassName" column="ClassName" type="string"/>
<property name="ClassNum" column="ClassNum" type="string"/>
<!--在一端设置的set集合属性映射-->
<set name="student" cascade="delete" inverse="true" lazy="false">
<key column="ClassId"/>
<one-to-many class="Bean.Student"/>
</set>
</class>
</hibernate-mapping>
注意:在设置set集合的时候,其中的name属性指的是在该一端实体类中定义的student集合属性;key属性指定的是本持久化类的主键名称;<one-to-many>属性指定的是在其对应的多端的实体类的名称。
其他要点总结:
在<set></set>集合中{
Cascade:级联属性表示的是再删除数据库中对应的列的时候相关联的数据库中的字段的操作情况。其中的:delete,表示级联删除;save表示级联保存;save-update表示的是在进行级联的时候自动查询是否存在相关的对象,存在的话进行比较并进行更新,如果没有存在,进行保存;evict,表示的是当清除session缓存中的相关对象的时候,与之相关的所有对象都得删除。
}
同时对于<many-too-one/>属性其中的cascade属性参考上述;lazy属性表示的是是否懒加载(默认是true),当设置:lazy=true,此时表示在进行相关的对象操作时并没有得到实际的对象而是一个代理对象,只有当真正访问的时才得到;当设置;lazy=false时,表示直接得到相关对象。fetch属性指定为:join时,表示在hibernate执行数据库操作的时候多条sql语句是否合并查询。
在使用多对一得关系的时候我们需要特别注意的是inverse属性。
inverse属性可以用在一对多和多对多双向关联上,inverse属性默认为false,为false表示本端维护关系,如果inverse为true,则本端不能维护关系,会交给另一端维护关系,本端失效。所以一对多关联映射我们通常在多的一端维护关系,让一的一端失效,所以设置inverse属性为true。(PS:尽量设置多端维护关系的原因是如果设置一端维护关系,那么在执行数据库插入的时候会额外增加几条update语句,会影响执行效率,这一点是必须要注意的。
记录2:报错:Illegal attempt to associate a collection with two open sessions.Collection :[XXX.xxx.xxx#x]
分析原因:这句话的意思是:“非法尝试将集合与两个打开的会话关联。集合:[x x x.XXX.XXX#x]”,分析我的代码,这段报错是在我的StudentAction类中的Add方法中进行数据库插入的时候出现的(我个人观点:应该是在插入学生信息的时候,出现了两个session共用一个collection的情况,具体的深层原因还不明确)
解决办法:将Add方法中的session.save(Object o)改为session.merge()。这样改的目的是,merge执行的时候,Hibernate首先会查询在后端的数据库中是否存在该对象,如果存在那么进行对比后的更新操作,如果不存在,会进行插入数据库的操作。从而避免了相关错误的出现。
记录3:报错:A different object with the same identifier value was already associated with XXX.
分析原因:这句话的意思是:“具有相同标识符值的其他对象已与XXX关联。”也就是说明,在session流中,已经出现了相关的对象。这一报错产生的地方是我的StudentAction中的Edit()方法中。下面是修改前的代码:
Configuration configuration = new Configuration();
configuration.configure("hibernate.cfg.xml");
SessionFactory sessionFactory = configuration.buildSessionFactory();
Session session = sessionFactory.openSession();
//创建事务对象
Transaction transaction = session.beginTransaction();
//得到相关的实体对应的序号
int id = (int) application.getAttribute("id");
//根据序号得到相关的实体信息
Student stutemp = session.get(Student.class, id);
//更新得到的实体信息
stutemp.setStuName(student.getStuName());
stutemp.setBirthday(student.getBirthday());
stutemp.setPhone(student.getPhone());
stutemp.setNation(student.getNation());
stutemp.setPolicital(student.getPolicital());
stutemp.setSex(student.getSex());
stutemp.setStuNum(student.getStuNum());
stutemp.setSchool(student.getSchool());
stutemp.setStuClass(student.getStuClass());
//判断是否到这一步停止了
System.out.println("stu-edit-stop-here-before-merge!");
//使用的是session的update方法(这里使用的是merge方法主要是因为在sessionh中有两个实体通用一个标志位
session.merge(stutemp);
经过分析可知,在执行session.merge()方法之前,我已经通过session.get()方法得到了一个持久化类的对象(load是懒加载,get是直接得到),那么,在执行merge()方法的时候,根据它的原理可知,会先进行查询,然后根据查询结果进行操作。那么如此看来问题出现的原因也就明了了,就是在merge()方法查询的时候发现了一个有一样标志放入session中的对象,所以报错。
解决办法:只需要在执行的merge()方法之前加一个session.clear()即可。表示清空所有缓存(不管是相关还是不相关都清除)。