hibernate问题之多对多注解
1 项目中遇到的问题
场景:学生(StudentBean)与科目(SubjectBean)多对多关系
数据库表设计:
//学生表
DROP TABLE IF EXISTS `t_stu`;
CREATE TABLE `t_stu` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`stu_name` varchar(20) COLLATE utf8_bin DEFAULT NULL,
`fk_class_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
//学生与科目关系表
DROP TABLE IF EXISTS `t_stu_subj`;
CREATE TABLE `t_stu_subj` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`fk_stu_id` bigint(20) DEFAULT NULL,
`fk_subj_id` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
//科目表
DROP TABLE IF EXISTS `t_subj`;
CREATE TABLE `t_subj` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`subj_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
在注解中配置了:
cascade=cascadeType.ALL//级联关系
测试:
//测试根据id,查询学生没有问题。
//但是在测试删除学生的方法等时候,会抛出异常:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException:
Table 'hibernate.t_subj_t_stu' doesn't exist
//结果hibernate告诉我'hibernate.t_subj_t_stu'中间表不存在,what?
//我的中间表名本来是:t_stu_subj,结果hibernate给我拼接成了t_subj_t_stu???
异常信息截图:
2 问题分析与解决
2.1 分析运行流程
测试代码:
// @Ignore
@Test
public void testSelectStudentById() {
//查询学生
StudentBean stu = studentServiceImpl.selectStudentById(8L);
System.out.println(stu);
//删除学生
studentServiceImpl.deleteStudent(stu);
}
程序流程:
- 1.根据学生id,查询学生。执行ok
- 2.查询出学生后,执行删除学生动作,会先查询学生里面的科目(因为在注解中配置了级联关系为ALL)。执行ok
- 3.科目查询出来后,就要删除科目,那么删除科目,就会再查询科目里面是否还有学生。
那么问题出来了:hibernate框架在根据科目查询科目里面的学生的时候,把中间表表名拼错了
2.2 定位问题
问题极有可能是在学生实体(SudentBean)、科目实体(SubjectBean),的注解里配置manytomany的地方。
学生实体:
@Entity//实体
@Table(name="t_stu")//对应数据库中的表
public class StudentBean {
@Id
@GenericGenerator(name="hibernate.identity",strategy="identity")//表示hibernate的主键增长策略
@GeneratedValue(generator="hibernate.identity")//引用上面的name
private Long id;
@Column(name="stu_name",length=20)
private String stuName;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="fk_class_id")
private ClassBean cls;
@ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
//cascade:级联关系
//fetch:抓取机制,懒加载
@JoinTable(name="t_stu_subj",joinColumns=@JoinColumn(name="fk_stu_id"),inverseJoinColumns=@JoinColumn(name="fk_subj_id"))
//name:中间表表名
//joinColumns:本类(StudentBean)在中间表上的外键
//inverseJoinColumns:另外一个类(SubjectBean)在中间表上的外键
private Set<SubjectBean> subjs;
public Set<SubjectBean> getSubjs() {
return subjs;
}
public void setSubjs(Set<SubjectBean> subjs) {
this.subjs = subjs;
}
public StudentBean() {
super();
// TODO Auto-generated constructor stub
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public ClassBean getCls() {
return cls;
}
public void setCls(ClassBean cls) {
this.cls = cls;
}
@Override
public String toString() {
return "StudentBean [id=" + id + ", stuName=" + stuName + "]";
}
}
科目实体:
扫描二维码关注公众号,回复:
2576165 查看本文章
@Entity//实体
@Table(name="t_subj")//对应数据库中的表
public class SubjectBean implements Serializable {
@Id
@GenericGenerator(name="hibernate.identity",strategy="identity")//表示hibernate的主键增长策略
@GeneratedValue(generator="hibernate.identity")//引用上面的name
private Long id;
@Column(name="subj_name")
private String subjName;
@ManyToMany(cascade=CascadeType.ALL,fetch=FetchType.LAZY)
//在这里科目实体中没有指定中间表。
private Set<StudentBean> stus;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getSubjName() {
return subjName;
}
public void setSubjName(String subjName) {
this.subjName = subjName;
}
public Set<StudentBean> getStus() {
return stus;
}
public void setStus(Set<StudentBean> stus) {
this.stus = stus;
}
@Override
public String toString() {
return "SubjectBean [id=" + id + ", subjName=" + subjName + "]";
}
}
可以看到学生实体在注解中指定了中间表,科目实体在注解中并没有指定中间表。
因此hibernate在根据科目查询学生的时候,不知道中间表,所以发生中间表表名拼接错误
2.3 解决方案
解决方案自然是在科目实体中指定中间表。
3 结论
那么得出结论:以后在多对多的注解中,有必要在两个实体Bean中都指定出中间表
另外一点体会:在我们配置了级联关系为ALL的时候,两个实体的耦合度极高,例如:我在删除学生的时候,还要先查学生里面的科目有没有,有就要删科目,那么删除科目,就要先查询科目里面的学生有没有,如果有,那么就又要删学生…如此循环,真的是牵一发而动全身,而且这种循环的删除如果数据量过大,好像就没有尽头一样……那么最后就会变成,我本来只是想删除一条学生记录,结果,学生表所有记录以及科目表所有记录,全都没了。
基于上面的体会:建议我自己,以后cascade属性,尽量不去配置,所有的级联关系在业务中由自己去控制。以便于好灵活处理。
以上都是我初略总结的。如果您感觉有不同的观点,那么肯定你是对的。仅供参考,还望多多指点。