假设有角色(trole)和用户组(tgroup)两个表,是多对多的关系,即一个角色可以多个用户组拥有,一个用户组也可以拥有多个角色。这里需要增加一个角色-组的对应表tgroup_role,用来记录多对多的对应关系。
首先建立三个对应表格:
create table tgroup(
group_id int(11) not null auto_increment,
name varchar(16) not null default '',
primary key(group_id)
)engine=innodb default charset=gbk;
create table trole(
role_id int(11) not null auto_increment,
name varchar(16) not null default '',
primary key(role_id)
)engine=innodb default charset=gbk;
create table tgroup_role(
group_id int(11) not null,
role_id int(11) not null,
primary key(group_id)
)engine=innodb default charset=gbk;
建立角色的实体类Role.java:
package relation.unidirectional.manytomany;
import java.util.HashSet;
import java.util.Set;
public class Role {
private int id;
private String name;
private Set groups = new HashSet();
public Role() {}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set getGroups() {
return groups;
}
public void setGroups(Set groups) {
this.groups = groups;
}
}
注解:控制方需要建立一个Set集合类型属性,用来保存多个不重复的Group对象。
Role是多对多的控制方,其映射文件Role.hbm.xml:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="relation.unidirectional.manytomany.Role" table="trole">
<id name="id" column="role_id" unsaved-value="0">
<generator class="native" />
</id>
<property name="name" type="string" />
<!--配置set,指定中间表及其对应键-->
<set name="groups" table="tgroup_role" cascade="save-update">
<key column="role_id" />
<!--单向多对多的主动方配置-->
<many-to-many class="relation.unidirectional.manytomany.Group"
column="group_id">
</many-to-many>
</set>
</class>
</hibernate-mapping>
注解:在此映射文件中,配置set元素。set的name属性对应Role类的groups变量,table指明其对应的表;子标签key指定了集合中对象同Role主键关联的键值。
多对多关系通过many-to-many配置,many-to-many关联表映射为组合元素的集合(A mapping like this allows you to map extra columns of amany-to-many association table to the composite element class.) 其属性 class(必须):关联类的名称,column
(可选):这个元素的外键关键字段名;它也可以通过嵌套的<column>
元素指定。要注意,毕竟是嵌套于<setname="groups" table="tgroup_role"></set>中的标签,故其作用范围就限定在为实体类Role的Set集合类型属性groups关联服务。
这里的多对多是单向的,当反向添加Group对象时,对role并没有影响,这从本例可看出。
关于<set>标签中属性cascade="save-update",详见: Hibernate级联操作Cascade学之---save-update
关于<id>标签中属性unsaved-value="0",详见: unsaved-value的经典解释
Group是被控制方,Group.hbm.xml:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--多对多的被动一方配置-->
<class name="relation.unidirectional.manytomany.Group" table="tgroup">
<id name="id" column="group_id" unsaved-value="0">
<generator class="native" />
</id>
<property name="name" type="string" />
</class>
</hibernate-mapping>
将上面的两个映射文件加入到Hibernate配置文件中,建立一个测试类Test.java:
package relation.unidirectional.manytomany;
import java.util.Iterator;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
public class Test {
public static void main(String[] args) {
//创建对象
Role role1 = new Role();
role1.setName("Role1");
Role role2 = new Role();
role2.setName("Role2");
Role role3 = new Role();
role3.setName("Role3");
Group group1 = new Group();
group1.setName("group1");
Group group2 = new Group();
group2.setName("group2");
Group group3 = new Group();
group3.setName("group3");
//相互赋值
group1.getRoles().add(role1);
group1.getRoles().add(role2);
group2.getRoles().add(role2);
group2.getRoles().add(role3);
group3.getRoles().add(role1);
group3.getRoles().add(role3);
role1.getGroups().add(group1);
role1.getGroups().add(group3);
role2.getGroups().add(group1);
role2.getGroups().add(group2);
role3.getGroups().add(group2);
role3.getGroups().add(group3);
// 定义主键变量
Integer pid;
// Configuration管理Hibernate配置
Configuration config = new Configuration().configure();
// 根据Configuration建立 SessionFactory
// SessionFactory用来建立Session
SessionFactory sessionFactory = config.buildSessionFactory();
// 正向添加数据
Session session = sessionFactory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
pid=(Integer)session.save(role1);
session.save(role2);
session.save(role3);
tx.commit();
} catch (RuntimeException e) {
if (tx != null)
tx.rollback();
throw e;
} finally {
session.close();
}
// 修改role1数据
Group group4=new Group();
group4.setName("group4");
session = sessionFactory.openSession();
tx = null;
try {
tx = session.beginTransaction();
role1 = (Role)session.get(Role.class, pid);
//role1增加group4
role1.getGroups().add(group4);
session.update(role1);
tx.commit();
} catch (RuntimeException e) {
if (tx != null)
tx.rollback();
throw e;
} finally {
session.close();
}
// 查询数据
session = sessionFactory.openSession();
role1 = (Role)session.get(Role.class, pid);
System.out.println("role name:" + role1.getName());
System.out.println("groups:");
Iterator iter = role1.getGroups().iterator();
while (iter.hasNext()) {
group1 = (Group) iter.next();
System.out.println("group name:" +group1.getName());
}
session.close();
// 删除role1数据
session = sessionFactory.openSession();
tx = null;
try {
tx = session.beginTransaction();
role1 = (Role) session.get(Role.class, pid);
session.delete(role1);
tx.commit();
} catch (RuntimeException e) {
if (tx != null)
tx.rollback();
throw e;
} finally {
session.close();
}
// 反向添加数据,只增加了group数据,对role没有影响
session = sessionFactory.openSession();
tx = null;
try {
tx = session.beginTransaction();
session.save(group1);
session.save(group2);
tx.commit();
} catch (RuntimeException e) {
if (tx != null)
tx.rollback();
throw e;
} finally {
session.close();
}
// 关闭sessionFactory
sessionFactory.close();
}
}
关于异常:Duplicate entry ‘1’ for key ‘PRIMARY’,即主键重复。
数据库提供的主键生成机制,往往是通过在一个内部表中保存当前主键状态,如对于自增型主键而言,此内部表中就维护着当前的最大值和递增量,之后每次插入数据会读取这个最大值,然后加上递增量作为新记录的主键,之后再把这个新的最大值更新回内部表中,这样,一次insert操作可能导致数据库内部多次表读写操作,同时伴随的还有数据的加解锁操作,这对性能产生了较大影响。
对于多对多连接表使用的异常:链接表的如果不是使用双主键,必须确定主键列不能重复,而Hibernate插入多对多时链接表的操作很可能会使主键重复,如本例有连接表tgroup_role连接表tgroup和trole,有两个字段group_id、role_id分别引用tgroup表和trole表,如果连接表将group_id设为主键(本例都没有设主键),当Hibernate插入group对象时,就会产生类似以上的异常,提示主键插入重复。故这时就有必要使用双主键了,以便使一列主键重复的同时,另一列主键可以作为辨别键,如将book_id、image_id都设为主键。当然对于本例都没有设置主键是正确的,或者两个都设置主键(复合主键)也正确,但只设置其中一个有主键运行时就会出现主键重复异常。
运行结果:
控制台:
23:14:56,080 DEBUG SQL:346 -insert into trole (name) values (?)
23:14:56,099 DEBUG SQL:346 -insert into tgroup (name) values (?)
23:14:56,100 DEBUG SQL:346 -insert into tgroup (name) values (?)
23:14:56,102 DEBUG SQL:346 -insert into trole (name) values (?)
23:14:56,105 DEBUG SQL:346 -insert into tgroup (name) values (?)
23:14:56,107 DEBUG SQL:346 -insert into trole (name) values (?)
23:14:56,114 DEBUG SQL:346 -insert into tgroup_role (role_id, group_id) values (?, ?)
23:14:56,115 DEBUG SQL:346 -insert into tgroup_role (role_id, group_id) values (?, ?)
23:14:56,116 DEBUG SQL:346 -insert into tgroup_role (role_id, group_id) values (?, ?)
23:14:56,116 DEBUG SQL:346 -insert into tgroup_role (role_id, group_id) values (?, ?)
23:14:56,116 DEBUG SQL:346 -insert into tgroup_role (role_id, group_id) values (?, ?)
23:14:56,117 DEBUG SQL:346 - insert intotgroup_role (role_id, group_id) values (?, ?)
注意:上面输出中tgroup_role(role_id,group_id)中”role_id”和”group_id”的顺序是反的,但这只是表示插入到对应的数据库表中去,并不表示严格的SQL查询语句,故此处不用纠结。
数据库:
添加模块:
添加、修改模块:(修改role1数据,role1增加group4)
控制台修改部分:
00:15:53,159 DEBUG SQL:346 -select role0_.role_id as role1_0_0_, role0_.name as name0_0_ from trole role0_where role0_.role_id=?
00:15:53,167 DEBUG SQL:346 -select groups0_.role_id as role1_1_, groups0_.group_id as group2_1_,group1_.group_id as group1_2_0_, group1_.name as name2_0_ from tgroup_rolegroups0_ left outer join tgroup group1_ on groups0_.group_id=group1_.group_idwhere groups0_.role_id=?
00:15:53,170 DEBUG SQL:346 -insert into tgroup (name) values (?)
00:15:53,172 DEBUG SQL:346 - insert intotgroup_role (role_id, group_id) values (?, ?)
添加、删除模块:(删除role1数据)
控制台删除部分:
00:17:24,585 DEBUG SQL:346 -select role0_.role_id as role1_0_0_, role0_.name as name0_0_ from trole role0_where role0_.role_id=?
00:17:24,594 DEBUG SQL:346 -delete from tgroup_role where role_id=?
00:17:24,595 DEBUG SQL:346 - delete fromtrole where role_id=?
添加、反向添加模块:(反向添加数据,只增加了group数据,对role没有影响)
控制台反向添加部分:
00:19:37,423 DEBUG SQL:346 -insert into tgroup (name) values (?)
00:19:37,424 DEBUG SQL:346 - insert intotgroup (name) values (?)