hibernate多对多映射之增删改查
本文是hibernate多对多双向映射的增删改查实践总结笔记,全部基于注解。文中的多对多关系指的是老师有多个学生,学生也有多个老师。
环境
-
JDK1.8.92
-
Idea 2016.1
-
Hibernate 5.2.5
-
Junit4
实体类
Student.java:
package com.engchen.pojo;
import javax.persistence.*;
import java.util.Set;
/**
* Created by chenxin on 09/02/2017.
*/
@Entity
@Table(name="student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int sid;
@Column(name="name")
private String name;
@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(
name="teas_stus",
joinColumns = {@JoinColumn(name = "sid")},
inverseJoinColumns = {@JoinColumn(name = "tid")}
)
private Set<Teacher> teas;
public Student() {
}
public Student(String name) {
this.name = name;
}
public Set<Teacher> getTeas() {
return teas;
}
public void setTeas(Set<Teacher> teas) {
this.teas = teas;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
}
Teacher.java:
package com.engchen.pojo;
import javax.persistence.*;
import java.util.Set;
/**
* Created by chenxin on 10/02/2017.
*/
@Entity
@Table(name="teacher")
public class Teacher {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int tid;
private String name;
public Teacher(){
}
public Teacher(String name) {
this.name = name;
}
@ManyToMany(mappedBy = "teas")
private Set<Student> stus;
public int getTid() {
return tid;
}
public void setTid(int tid) {
this.tid = tid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Student> getStus() {
return stus;
}
public void setStus(Set<Student> stus) {
this.stus = stus;
}
}
关于一些注解的说明:
-
@Entity,@Table,@Id,@ManyToMany,@JoinTable等都是JPA2.1规范中提供的标准注解。
-
在多对多双向映射中,实际上关系也是由其中一方负责维护的,则该方为主控方(owner)同时另一方为被控方(inverse)。
-
多对多主控方的Set属性上一般用@ManyToMany和@JoinTable同时注解。被控方的Set属性一般用@ManyToMany(mappedBy="")注解。
-
mappedBy:双向的关联关系中必须在其中一方使用,表示放弃关系的控制权,一般用在被控方,比如多对一中的@OneToMany.双向的关联必须设置mappedBy属性。也就是说双向关联只能交给一方去控制,不可能在双方都设置外键保存关联关系,否则都无法保存。比如在学生和省份证的一对一双向关联关系中,学生表包含身份证id,同时身份证表也包含学生id,互为对方的外键。如果互为主控方,那么先保存谁呢?如果要保存学生,则应该先保存身份证,而身份证也是主控方,要保存身份证必须先保存学生……死循环发生。
-
多对多关系实际执行上要拆分成2个一对多关系,共需要3张表,student,teacher,teas_stus。第三张表(中间表)一般只需要前2张表的主键sid和tid。学生和老师表都是一方,对应多个teas_stus.
-
@JoinTable用于生成第三张表,配置表名和需要加入表中的字段,joinColumns用于配置主控方(Student)需要加入到中间表的字段,示例中只加入了sid,也可以加入Student的其它字段。inverseJoinColumns用于配置被控方(Teacher)需要加入到中间表的字段,示例中只加入了tid,也可以加入Teacher的其它字段。
-
非注解方式添加第三张表是在xml文件的<set table=""></set>标签的table属性指定表名,对应注解的@JoinTable,具体参考这篇文章:Hibernate多对多删除问题的解决
-
@JoinColumn表示加入外键(在多对多关系中一般不用@JoinColumn,因为多对多关系中不靠其中一方加入一个外键维护和另一方的关系,而是靠第三张表),一般用在需要维护关系的一方(比如一对一中的任意一方,多对一中通常是多方),该方是主控方(owner)。与此同时,mappedBy一般用在被控方(inverse).更多关于@JoinColumn和mappedBy的疑惑请参考stackoverflow上的这个问答: JPA JoinColumn vs mappedBy
-
关于单向和双向:Student中有个teas的Set集合指向Teacher,同时Teacher中也有个stus的Set集合指向Student,这就是双向映射。如果Teacher中没有stus的Set集合指向Student,这就是单向。
工具类
HibernateUtil.java:
package com.engchen.util;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class HibernateUtil {
private static SessionFactory factory;
static {
factory=new Configuration().configure().buildSessionFactory();
}
public static Session openSession() {
return factory.openSession();
}
}
hibernate.cfg.xml配置
classpath路径下的hibernate.cfg.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 连接数据库的信息 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate?autoReconnect=true&useUnicode=true&createDatabaseIfNotExist=true&characterEncoding=utf8</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">123</property>
<!-- 其他的配置 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.show_sql">true</property>
<!-- 加载映射文件 -->
<mapping class="com.engchen.pojo.Student"></mapping>
<mapping class="com.engchen.pojo.Teacher"></mapping>
</session-factory>
</hibernate-configuration>
测试添加数据
新建测试类TestHibernate.java,添加以下测试方法:
@Test
public void add(){
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Teacher tea1=new Teacher("wang");
Teacher tea2=new Teacher("zhang");
Set<Teacher> teas=new HashSet<>();
teas.add(tea1);
teas.add(tea2);
Student stu1=new Student("aa");
Student stu2=new Student("bb");
Set<Student> stus=new HashSet<>();
stus.add(stu1);
stus.add(stu2);
tea1.setStus(stus);
tea2.setStus(stus);
stu1.setTeas(teas);
stu2.setTeas(teas);
session.save(stu1);
session.save(stu2);
tx.commit();
session.close();
}
使用navicat连接数据库可以看到新生成的3张表:student,teacher,teas_stus.
测试检索数据
hql检索student表中的全部学生:
@Test
public void hqlQuery(){
Session s=HibernateUtil.openSession();
String hql="from Student";
Query query=s.createQuery(hql);
List<Student> stus=query.list();
for (Student stu:stus
) {
System.out.println(stu.getName());
}
s.close();
}
使用hql检索name为wang的老师的全部学生:
这种类型的检索涉及多表联结查询。
@Test
public void multiHqlQuery(){
Session s = HibernateUtil.openSession();
String hql="select s from Student as s,Teacher as t where t.name='wang' and s.sid in elements(t.stus) ";
Query query = s.createQuery(hql);
List<Student> stus = query.list();
for (Student stu :
stus) {
System.out.println(stu.getName()+" 学号:"+stu.getSid());
}
s.close();
}
更新关系
现在要把1号学生和名字为wang的老师解除关系,对应方法如下:
@Test
public void update(){
Session session = HibernateUtil.openSession();
Transaction tx = session.beginTransaction();
Student stu=(Student)session.load(Student.class,1);
Set<Teacher> teas=stu.getTeas();
CopyOnWriteArraySet<Teacher> cs=new CopyOnWriteArraySet(teas);
for (Teacher tea :
cs) {
if (tea.getName().equals("wang")) {
cs.remove(tea);
}
}
stu.setTeas(cs);
session.update(stu);
tx.commit();
session.close();
}
关于CopyOnWriteArraySet的说明:普通的HashSet不支持边遍历边删除,详情参考:Java删除List和Set集合中元素