多对多关系并不是两张表的关系,而是三张表的关系,而且要求必须有一个中间表,中间表必须只有两个字段,必须为复合主键,必须包含外键。
如果以上条件都满足,MyEclipse在三张表一起选择时,就可以生成多对多关系映射。
类一般只生成两个类,例如:学生选课中,只生成学生和课程类,关系使用以下方式描述:
1) 学生类中保存着该学生选择的所有课程对象,使用Set集合来保存。
2) 课程类中保存着所有选择此课程的学生对象,使用Set集合来保存。
建立数据表
CREATE TABLE user_course ( |
使用t_user,course和user_course表,完成映射:
many to many 前面打勾:
由于t_user使用assigned,course使用increment,因此,这里不统一选择方式。
而是在后面单独选择。
同时,注意将允许使用多对多的多选框选中。
可以在这里单独选择方式。
public class TUser implements java.io.Serializable {
private String userid; private String realName; private String password; private Date registDate; private Date lastLoginDate; private Set courses = new HashSet(0); |
public class Course implements java.io.Serializable {
private Integer id; private String title; private Set TUsers = new HashSet(0); |
类中确定了集合关系 |
映射文件里也描述了多对多关系:
<?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="org.liky.pojo.TUser" table="T_USER" schema="SUNXUN"> <id name="userid" type="java.lang.String"> <column name="USERID" length="40" /> <generator class="assigned"></generator> </id> <property name="realName" type="java.lang.String"> <column name="REAL_NAME" length="20" not-null="true" /> </property> <property name="password" type="java.lang.String"> <column name="PASSWORD" length="32" not-null="true" /> </property> <property name="registDate" type="java.util.Date"> <column name="REGIST_DATE" length="7" /> </property> <property name="lastLoginDate" type="java.util.Date"> <column name="LAST_LOGIN_DATE" length="7" /> </property> <!-- 在User中包含一个名称为courses的Set集合,数据是根据中间表USER_COURSE来关联取得的. --> <set name="courses" table="USER_COURSE" schema="SUNXUN"> <!-- 中间表中通过USERID与当前类(User)关联 --> <key> <column name="USERID" length="40" not-null="true" /> </key> <!-- 中间表还通过COURSE_ID与Course对象关联,联系起来出现的是多对多关系。 --> <many-to-many entity-name="org.liky.pojo.Course"> <column name="COURSE_ID" precision="8" scale="0" not-null="true" /> </many-to-many> </set> </class> </hibernate-mapping>
|
<?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"> <!-- Mapping file autogenerated by MyEclipse Persistence Tools --> <hibernate-mapping> <class name="org.liky.pojo.Course" table="COURSE" schema="SUNXUN"> <id name="id" type="java.lang.Integer"> <column name="ID" precision="8" scale="0" /> <generator class="increment"></generator> </id> <property name="title" type="java.lang.String"> <column name="TITLE" length="50" not-null="true" /> </property> <set name="TUsers" inverse="true" table="USER_COURSE" schema="SUNXUN"> <key> <column name="COURSE_ID" precision="8" scale="0" not-null="true" /> </key> <many-to-many entity-name="org.liky.pojo.TUser"> <column name="USERID" length="40" not-null="true" /> </many-to-many> </set> </class> </hibernate-mapping>
|
可以发现有一边的配置中出现了inverse=”true”,这个配置表示关联关系由对方进行维护。
多对多关系中,实际上,关联关系指的就是中间表,需要根据业务想清楚谁来维护中间表,这里实现的功能是学生选课,也就是说学生是主动的一方,应该由学生添加自己所选的课程,也就是说中间表数据应该由学生来维护的。
因此可以说,关联关系应该由学生来维护,对于课程来说,关联关系就应该由对方(学生)来进行维护。
public static void main(String[] args) { // 学生选课操作实际上是对学生的修改操作,而不是添加. // 先取得一个学生 TUser user = (TUser) HibernateSessionFactory.getSession().get( TUser.class, "zhangsan"); // // 以下int值就是该学生选择的课程编号 // int[] courseIds = { 2, 4, 5 }; // // 将课程id关联到学生对象中 // for (int i = 0; i < courseIds.length; i++) { // int id = courseIds[i]; // Course c = new Course(); // c.setId(id); // // 把课程加入到学生选择的课程集合里 // user.getCourses().add(c); // }
// 如果想取消这些课程,直接将集合清空 user.getCourses().clear();
// 取消一部分,再添加一部分 int[] courseIds = { 2, 3,5 }; // 将课程id关联到学生对象中 for (int i = 0; i < courseIds.length; i++) { int id = courseIds[i]; Course c = new Course(); c.setId(id); // 把课程加入到学生选择的课程集合里 user.getCourses().add(c); }
// 调用学生的修改 HibernateSessionFactory.getSession().update(user);
Transaction tx = HibernateSessionFactory.getSession() .beginTransaction(); tx.commit(); HibernateSessionFactory.closeSession();
}
|
多对多关系应用——选课功能
首先,先要实现用户的登陆功能,当用户登陆后,列出所有课程供用户选择。
登陆功能之前已经实现过了,因此直接将后台代码拷贝过来即可。
登陆同时,注意,需要将之前选择的课程内容取得,并保存到user里。
public class TUserDAOImpl implements ITUserDAO {
public boolean isLogin(TUser user) throws Exception { String hql = "FROM TUser AS u WHERE u.userid = ? AND u.password = ?"; Query query = HibernateSessionFactory.getSession().createQuery(hql); query.setString(0, user.getUserid()); query.setString(1, user.getPassword()); List<User> allUser = query.list(); if (allUser != null && allUser.size() > 0) { // 登陆成功 // 取得查询的结果 TUser result = (TUser) allUser.get(0); // 根据引用传递,将值设置到传入的user中 user.setRealName(result.getRealName()); user.setRegistDate(result.getRegistDate()); user.setLastLoginDate(result.getLastLoginDate());
// 将用户之前选择过的课程取得 user.setCourses(result.getCourses());
return true; }
return false; }
}
|
登陆成功后,用户可以看到选课的连接,点这个连接,进入选课页面,选课前,需要先查询出所有课程,列表显示。
<a href="tuser!selectCoursePre.action">选课</a> |
编写Action的方法,在这里查询所有课程信息。
public String selectCoursePre() throws Exception { allCourse = ServiceFactory.getITUserServiceInstance().selectCoursePre();
return "select"; } |
<result name="select">/pages/user/user_select_course.jsp</result> |
页面显示所有课程列表,通过多选框让用户选择。
<center> 用户登陆成功,当前登陆用户为: ${user.realName} <br /> <hr /> <br /> <s:form action="tuser!selectCourse.action" method="post" namespace="/" theme="simple"> 可选课程:<s:checkboxlist list="allCourse" listKey="id" listValue="title" name="courseIds"></s:checkboxlist> <br/> <s:submit value="提交"></s:submit> </s:form> </center> |
提交后,完成User的修改操作,注意,需要将课程信息合并到User对象里。
public String selectCourse() throws Exception { // 先取得当前登陆用户信息 TUser loginUser = (TUser) ServletActionContext.getRequest() .getSession().getAttribute("user");
// 设置该用户选择的课程 loginUser.getCourses().clear();
for (int i = 0; i < courseIds.length; i++) { int id = courseIds[i]; Course c = new Course(); c.setId(id); // 将课程合并到User对象中 loginUser.getCourses().add(c); }
ServiceFactory.getITUserServiceInstance().selectCourse(loginUser);
// 将用户信息保存回Session ServletActionContext.getRequest().getSession().setAttribute("user", loginUser);
return "forward"; } |
<result name="forward">/forward.jsp</result> |
测试时,提示懒汉式异常,也需要通过设置lazy解决。
<set name="courses" lazy="false" table="USER_COURSE" schema="SUNXUN"> |
使用时发现用户体验不好,用户无法看到当前选了哪些课程,而且没有默认选中已经选择过的课程。
在suc.jsp中显示出当前的课程。
当前选择的课程有: <br/> <font color="red"> <c:forEach var="c" items="${user.courses}"> ${c.title } <br/> </c:forEach> </font> <hr /> |
注意,在映射文件中,最好加入order-by,防止顺序改变
<set name="courses" lazy="false" order-by="course_id"table="USER_COURSE" schema="SUNXUN"> |
最后,设置默认选中已经选择的课程。
需要在选择课程之前,先取得之前选的课程信息的id值,传递到页面上。
public String selectCoursePre() throws Exception { allCourse = ServiceFactory.getITUserServiceInstance().selectCoursePre();
// 取得当前已经选择的课程信息 TUser loginUser = (TUser) ServletActionContext.getRequest() .getSession().getAttribute("user"); Set<Course> allSelectedCourse = loginUser.getCourses(); if (allSelectedCourse != null) { Iterator<Course> iter = allSelectedCourse.iterator(); courseIds = new int[allSelectedCourse.size()]; int index = 0; while (iter.hasNext()) { Course c = iter.next(); courseIds[index++] = c.getId(); } }
return "select"; } |
如果使用普通标签,则必须使用下面两层循环的形式完成
<form action="tuser!selectCourse.action" method="post" > 可选课程: <c:forEach var="c" items="${allCourse}"> <input type="checkbox" name="courseIds" value="${c.id }" <c:forEach var="id" items="${courseIds}"> ${id == c.id?"checked":"" } </c:forEach> /> ${c.title } </c:forEach> <br/> <input type="submit" value="提交"> </form> |