之前的映射文件配置都是简单的、基础的配置,只涉及到单个javabean,对于单个javabean的增删改查都能很好的、简单的去完成。
但是知道简单配置远远不够,并不能完成很多复杂的情况,比如对象与对象间的关联,对象中的集合属性等,所以还需更近一步的学习hibernate映射文件的配置。
本次映射文件配置涉及到集合、排序、多对一、一对多、多对多、以及一对一的内容等。
1.集合属性(Set、List、Map、数组)
1.1Set集合:
首先,创建一个User实体类,实体类是一个买家账号,包含id、name、addressSet(set集合,存放多个地址,地址不重复)
//该实体是买家实体 public class User { private Integer id;// 买家id private String name;// 买家名字 private Set<String> addressSet;// 买家地址,可以有多个,但是不能重复 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set<String> getAddressSet() { return addressSet; } public void setAddressSet(Set<String> addressSet) { this.addressSet = addressSet; } }
接着配置映射文件
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.hibernate.Collection"> <class name="User" table="user"> <id name="id" type="int" column="id"> <generator class="native"/> </id> <property name="name" type="string" column="name" length="20"/> <!-- addressSet属性,在javabean中是一个set集合 --> <!-- name为javabean中的属性名,table为另一张表user_addr --> <!-- user_addr表主键其实是user表的外键 --> <set name="addressSet" table="user_addr"> <!-- user_addr外键 --> <key column="addrId"/> <!-- user_addr表address列 --> <element type="string" column="address"/> </set> </class> </hibernate-mapping>
表结构如下:
CREATE TABLE `user` ( `id` ) NOT NULL AUTO_INCREMENT, `name` ) NULL DEFAULT NULL, PRIMARY KEY (`id`) ) COLLATE='utf8_general_ci' ENGINE=InnoDB AUTO_INCREMENT; CREATE TABLE `user_addr` ( `addrId` ) NOT NULL, `address` ) NULL DEFAULT NULL, INDEX `FK14340FE5D5E4BA1E` (`addrId`), CONSTRAINT `FK14340FE5D5E4BA1E` FOREIGN KEY (`addrId`) REFERENCES `user` (`id`) ) COLLATE='utf8_general_ci' ENGINE=InnoDB;
测试程序:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { User u = new User(); u.setId(1); u.setName("testName"); Set<String> s = new HashSet<String>(); s.add("BeiJing 12"); s.add("HongKong 35"); s.add("ShangHai 113"); u.setAddressSet(s); Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); session.save(u); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
最终hibernate帮我们执行的sql代码:
Hibernate: insert into user (name) values (?) Hibernate: insert into user_addr (addrId, address) values (?, ?) Hibernate: insert into user_addr (addrId, address) values (?, ?) Hibernate: insert into user_addr (addrId, address) values (?, ?)
上述程序说明:
实体类User是一个买家账号类,该账号的id和name配置不多描述,主要说明addressSet集合,该集合存放多个地址,那么当我们存储数据到数据库中,user表的数据应该是这样子的:
id name testName testName2
由于一个User地址是多个,所以地址没办法在user表中的一个账号中体现出来,唯有创建一张地址表,用来被user表的用户指向,那么地址表的数据就是这样子的:
addrId address 123号 456号 789号
所以我们再次查看配置文件有关于集合的配置:
<set name="addressSet" table="user_addr"> <!-- user_addr外键 --> <key column="addrId"/> <!-- user_addr表address列 --> <element type="string" column="address"/> </set>
上述配置,set name代表的是javabean中的集合名称,而table代表的是地址表user_addr,同时必须指出地址表的外键,以及地址表其他字段。
举一反三,知道怎么存入数据,当然要知道怎么获取数据:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { User u = new User(); Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); u = (User) session.get(User.class, 1); System.out.println(u); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
hibernate帮我们查询的sql代码:
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_ from user user0_ where user0_.id=? Hibernate: select addressset0_.addrId as addrId0_0_, addressset0_.address as address0_ from user_addr addressset0_ where addressset0_.addrId=?
得出结果:
User [id=1, name=testName, addressSet=[BeiJing 12, ShangHai 113, HongKong 35]]
1.2List集合,跟set集合很相似,唯一的不同是在映射文件的集合配置
把实体类属性更改一下:
//该实体是买家实体 public class User { private Integer id;// 买家id private String name;// 买家名字 private List<String> cargo;// 购物车商品,可多个,也可重复 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<String> getCargo() { return cargo; } public void setCargo(List<String> cargo) { this.cargo = cargo; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", cargo=" + cargo + "]"; } }
映射文件配置,相比set集合配置多了一个<list-index>标签:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.hibernate.Collection"> <class name="User" table="user"> <id name="id" type="int" column="id"> <generator class="native"/> </id> <property name="name" type="string" column="name" length="20"/> <list name="cargo" table="cargo_list"> <key column="cargoId"/> <!-- list集合表,同一个用户购物车商品的顺序 --> <list-index column="idx"/> <element type="string" column="cargoName"/> </list> </class> </hibernate-mapping>
测试程序:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { User u = new User(); u.setId(1); u.setName("testName"); List<String> cargoList = new ArrayList<String>(); cargoList.add("pancel"); cargoList.add("apple"); cargoList.add("water"); u.setCargo(cargoList); Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); session.save(u); System.out.println(u); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
查看hibernate帮我们生成的sql语句:
Hibernate: insert into user (name) values (?) User [id=1, name=testName, cargo=[pancel, apple, water]] Hibernate: insert into cargo_list (cargoId, idx, cargoName) values (?, ?, ?) Hibernate: insert into cargo_list (cargoId, idx, cargoName) values (?, ?, ?) Hibernate: insert into cargo_list (cargoId, idx, cargoName) values (?, ?, ?)
表结构和数据:
user表
cargo_list表
1.3数组(跟list集合配置一模一样,除了list集合标签使用<list></list>,而数组使用<array></array>)
依旧是更改一下user实体
//该实体是买家实体 public class User { private Integer id;// 买家id private String name;// 买家名字 private String[] cargo;// 购物车商品,可多个,也可重复 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String[] getCargo() { return cargo; } public void setCargo(String[] cargo) { this.cargo = cargo; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", cargo=" + cargo + "]"; } }
映射文件配置:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.hibernate.Collection"> <class name="User" table="user"> <id name="id" type="int" column="id"> <generator class="native"/> </id> <property name="name" type="string" column="name" length="20"/> <array name="cargo" table="user_addressArray"> <key column="addrId"/> <list-index column="idx"/> <element type="string" column="address"/> </array> </class> </hibernate-mapping>
测试程序:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { User u = new User(); u.setId(1); u.setName("testName"); String[] cargo = {"computer","iphone6s","ipad"}; u.setCargo(cargo); Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); session.save(u); System.out.println(u); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
查看hibernate为我们生成的sql语句:
Hibernate: insert into user (name) values (?) User [id=1, name=testName, cargo=[Ljava.lang.String;@f737a7] Hibernate: insert into user_addressArray (addrId, idx, address) values (?, ?, ?) Hibernate: insert into user_addressArray (addrId, idx, address) values (?, ?, ?) Hibernate: insert into user_addressArray (addrId, idx, address) values (?, ?, ?)
表结构和数据:
user表
user_addressArray表
1.4Map集合
仍然修改一下user实体
//该实体是买家实体 public class User { private Integer id;// 买家id private String name;// 买家名字 private Map<String,String> map = new HashMap<String,String>();// 地址+邮政编码 public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Map<String, String> getMap() { return map; } public void setMap(Map<String, String> map) { this.map = map; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", map=" + map + "]"; } }
映射文件配置:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.hibernate.Collection"> <class name="User" table="user"> <id name="id" type="int" column="id"> <generator class="native"/> </id> <property name="name" type="string" column="name" length="20"/> <map name="map" table="user_map"> <!-- user_map外键 --> <key column="mapId"/> <!-- 由于javabean属性是map集合,所以必须配置key和value --> <map-key type="string" column="mapKey"/> <element type="string" column="mapValue"/> </map> </class> </hibernate-mapping>
测试程序:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { User u = new User(); u.setId(1); u.setName("testName"); Map<String,String> map = new HashMap<String,String>(); map.put("xiaMen", "361100"); map.put("fuZhou", "351100"); u.setMap(map); Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); session.save(u); System.out.println(u); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
hibernate帮我们生成的sql语句:
Hibernate: insert into user (name) values (?) User [id=2, name=testName, map={xiaMen=361100, fuZhou=351100}] Hibernate: insert into user_map (mapId, mapKey, mapValue) values (?, ?, ?) Hibernate: insert into user_map (mapId, mapKey, mapValue) values (?, ?, ?)
表结构和数据:
user表
user_map表
2.排序order-by
说明,这里的orderby指的是查询出来的集合表中数据的排序,只能在映射文件的集合中配置
<!-- orderby排序的是集合表的数据, 由于集合表现在有3个字段(从以下很容易看出mapId、mapKey、mapValue), 所以通过哪个字段排序看你自己 ; order-by内可以指定排序:ASC DESC,默认是ASC,不区分大小写 --> <map name="map" table="user_map" order-by="mapKey DESC"> <!-- user_map外键 --> <key column="mapId"/> <!-- 由于javabean属性是map集合,所以必须配置key和value --> <map-key type="string" column="mapKey"/> <element type="string" column="mapValue"/> </map>
3.多对一和一对多以及一对一和多对多
3.1一对多
所谓一对多,顾名思义,一个东西对应着多个东西,当然这是通俗的说法,实际上,用例子来描述就是:一个部门对应多个员工;一所学校对应多个老师;一个老师对应多个学生等等。。。
在说一对多之前,先来了解一下以下区别:
List<String> list = new ArrayList<String>();
List<Employee> list = new ArrayList<Employee>();
以上两个List的区别是,前者是存放字符串类型的集合,而后者是存放类(实体)的合集。然而,这会导致它们在hibernate映射文件中的配置不同。前者的配置,之前的总结就有,而后者的配置,却是跟前者有点区别,因为后者是两个实体之间建立关联关系。
创建两个实体,一个Department部门实体,一个Employee员工实体,一个部门对应多个员工,反之多个员工对应一个部门:
/**Department*/ public class Department { private Integer deptId;//部门ID private String deptName;//部门名称 //一个部门对应多个员工 private List<Employee> employees = new ArrayList<Employee>(); public Integer getDeptId() { return deptId; } public void setDeptId(Integer deptId) { this.deptId = deptId; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } public List<Employee> getEmployees() { return employees; } public void setEmployees(List<Employee> employees) { this.employees = employees; } }
/**Employee*/ public class Employee { private Integer empId;//员工ID private String empName;//公共姓名 private Department dept;//多个员工对应一个部门 public Integer getEmpId() { return empId; } public void setEmpId(Integer empId) { this.empId = empId; } public String getEmpName() { return empName; } public void setEmpName(String empName) { this.empName = empName; } public Department getDept() { return dept; } public void setDept(Department dept) { this.dept = dept; } }
建立两个实体之间的关系,需要两个映射文件:
Department.hbm.xml <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.hibernate.one2many"> <class name="Department" table="department"> <id name="deptId" type="int" column="deptId"> <generator class="native"/> </id> <property name="deptName" type="string" column="deptName" length="20"/> <!-- 配置一对多关系,由于employees是list集合,所以还是使用<list>标签来配置 --> <list name="employees"> <!-- key指向Employee对应表的外键 departmentId--> <key column="departmentId"/> <list-index column="idx"/> <!-- 指向Employee实体 class为全类名--> <one-to-many class="com.hibernate.one2many.Employee"/> </list> </class> </hibernate-mapping>
Employee.hbm.xml <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.hibernate.one2many"> <class name="Employee" table="employee"> <id name="empId" type="int" column="empId"> <generator class="native"/> </id> <property name="empName" type="string" column="empName" length="20"/> <!-- 多对一配置 --> <!-- name为Employee中属性名,class为与之关联的Department实体全限定名 --> <!-- column为Department所对应表的deptId,即employee表的外键 --> <many-to-one name="dept" class="com.hibernate.one2many.Department" column="departmentId"/> </class> </hibernate-mapping>
测试程序:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { //构建部门实体 Department dept = new Department(); dept.setDeptName("java dept"); //构建员工实体 Employee emp1 = new Employee(); emp1.setEmpName("zhangsan"); Employee emp2 = new Employee(); emp2.setEmpName("lisi"); //建立部门和员工的关系 dept.getEmployees().add(emp1); dept.getEmployees().add(emp2); emp1.setDept(dept); emp2.setDept(dept); Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); session.save(dept); session.save(emp1); session.save(emp2); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
表结构和数据:
department表
employee表
一对多分析:
上方测试程序是两个实体共同来维护表的关系!其实,hibernate允许两个关联的实体的其中一个实体来维护两者的关系,例如将测试程序改为:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { //构建部门实体 Department dept = new Department(); dept.setDeptName("java dept"); //构建员工实体 Employee emp1 = new Employee(); emp1.setEmpName("zhangsan"); Employee emp2 = new Employee(); emp2.setEmpName("lisi"); //建立部门和员工的关系 dept.getEmployees().add(emp1); dept.getEmployees().add(emp2); Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); session.save(dept); session.save(emp1); session.save(emp2); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
查看hibernate帮我们生成的sql语句:
Hibernate: insert into department (deptName) values (?) Hibernate: insert into employee (empName, departmentId) values (?, ?) Hibernate: insert into employee (empName, departmentId) values (?, ?) Hibernate: update employee set departmentId=?, idx=? where empId=? Hibernate: update employee set departmentId=?, idx=? where empId=?
为什么有update语句?
根据sql,很容易看到先往部门表中插入部门数据;在将员工设置给部门之前,员工的数据必须先存在,所以将员工数据先插入到员工表,员工表的外键列(指向哪个部门)这时并没有值,由于是由一方的实体(一方表示一对多的一,多方表示一对多的多)来维护,所以再获得一方的主键值,设置到多方的外键列中(关联两表关系)。
再来看看,由多方实体来维护表的情况,仍然是修改测试程序:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { //构建部门实体 Department dept = new Department(); dept.setDeptName("java dept"); //构建员工实体 Employee emp1 = new Employee(); emp1.setEmpName("zhangsan"); Employee emp2 = new Employee(); emp2.setEmpName("lisi"); //建立部门和员工的关系 emp1.setDept(dept); emp2.setDept(dept); Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); session.save(dept); session.save(emp1); session.save(emp2); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
看看hibernate帮我们生成的sql语句:
Hibernate: insert into department (deptName) values (?) Hibernate: insert into employee (empName, departmentId) values (?, ?) Hibernate: insert into employee (empName, departmentId) values (?, ?)
由上方sql,很清楚的看出,已经没有update语句,为什么?
根据sql,先往部门表中插入部门数据,这时候已经存在部门数据了,再根据实体来看,将部门实体设置给员工,员工实体在插入员工表时就已经拿到了部门数据,很容易就将部门数据插入到员工表的外键列,所以不需要额外的再去更新外键列的值。
总结:关联的实体双方都能维护表关系,但是最好的多方的实体来维护,多方的实体维护表关系,不必额外执行sql语句来更新外键列从而建立两张表的关系,效率比较高。
拓展:inverse属性
我们可以通过给一方(一对多的一,简称一方)的属性设置inverse属性,来表示是否由本方维护,例如:
<list name="employees" inverse="false"> <key column="departmentId"/> <list-index column="idx"/> <one-to-many class="com.hibernate.one2many.Employee"/> </list>
inverse = "false"是默认设置,配置文件中的配置代表了department实体有责任来维护双方的关系
这时候通过部门来维护两个实体间的关系,就和上方由一方(一对多的一,简称一方)来维护两个实体间的关系是一样的!
但是如果inverse = "true",那么department没有责任维护双方的关系了,一旦通过department来维护,那么两个实体间并不会建立关系,看以下sql:
Hibernate: insert into department (deptName) values (?) Hibernate: insert into employee (empName, departmentId) values (?, ?) Hibernate: insert into employee (empName, departmentId) values (?, ?)
上方的sql语句,仅仅是插入部门数据,插入员工数据,而员工表中的外键列却是空的,这是部门没责任维护双方关系的结果。
3.2多对多
以老师和学生为例,一个老师可以有多个学生,同时一个学生也可以有多个老师!
由于是多对多的关系,所以要让两者产生关联,还需要一张中间表。
实体:
public class Student { private Integer stuId; private String stuName; private Set<Teacher> teachers = new HashSet<Teacher>(); public Integer getStuId() { return stuId; } public void setStuId(Integer stuId) { this.stuId = stuId; } public String getStuName() { return stuName; } public void setStuName(String stuName) { this.stuName = stuName; } public Set<Teacher> getTeachers() { return teachers; } public void setTeachers(Set<Teacher> teachers) { this.teachers = teachers; } }
public class Teacher { private Integer teaId; private String teaName; private Set<Student> students = new HashSet<Student>(); public Integer getTeaId() { return teaId; } public void setTeaId(Integer teaId) { this.teaId = teaId; } public String getTeaName() { return teaName; } public void setTeaName(String teaName) { this.teaName = teaName; } public Set<Student> getStudents() { return students; } public void setStudents(Set<Student> students) { this.students = students; } }
配置映射关系文件:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.hibernate.many2many"> <class name="Student" table="student"> <id name="stuId" type="int" column="stuid"> <generator class="native"/> </id> <property name="stuName" type="string" column="stuname" length="20"/> <!-- table中间表,关联teacher和student表之间的关联关系 --> <set name="teachers" table="teacher_student"> <!-- student表在中间表中表示的id --> <key column="studentId"/> <!-- 与之关联的实体类,该实体类在中间表中的id --> <many-to-many class="Teacher" column="teacherId"/> </set> </class> </hibernate-mapping>
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.hibernate.many2many"> <class name="Teacher" table="teacher"> <id name="teaId" type="int" column="teaid"> <generator class="native"/> </id> <property name="teaName" type="string" column="teaname" length="20"/> <set name="students" table="teacher_student"> <key column="teacherId"/> <many-to-many class="Student" column="studentId"/> </set> </class> </hibernate-mapping>
测试程序:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { //构建对象 Teacher t1 = new Teacher(); t1.setTeaName("aaa"); Teacher t2 = new Teacher(); t1.setTeaName("bbb"); Student s1 = new Student(); s1.setStuName("sss"); Student s2 = new Student(); s2.setStuName("ssss"); //老师t1、t2分别有两个学生 t1.getStudents().add(s1); t1.getStudents().add(s2); t2.getStudents().add(s1); t2.getStudents().add(s2); //学生s1、s2分别有两个老师 s1.getTeachers().add(t1); s1.getTeachers().add(t2); s2.getTeachers().add(t1); s2.getTeachers().add(t2); Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); session.save(t1); session.save(t2); session.save(s1); session.save(s2); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
执行sql:
Hibernate: insert into teacher (teaname) values (?) Hibernate: insert into teacher (teaname) values (?) Hibernate: insert into student (stuname) values (?) Hibernate: insert into student (stuname) values (?) Hibernate: insert into teacher_student (teacherId, studentId) values (?, ?) Hibernate: insert into teacher_student (teacherId, studentId) values (?, ?) Hibernate: insert into teacher_student (teacherId, studentId) values (?, ?) Hibernate: insert into teacher_student (teacherId, studentId) values (?, ?) Hibernate: insert into teacher_student (studentId, teacherId) values (?, ?) Hibernate: insert into teacher_student (studentId, teacherId) values (?, ?) Hibernate: insert into teacher_student (studentId, teacherId) values (?, ?) Hibernate: insert into teacher_student (studentId, teacherId) values (?, ?)
通过以上sql,可能看不出问题,实际上执行程序出错:
Caused by: java.sql.BatchUpdateException: Duplicate entry '2-2' for key 1
由于中间表teacher_student中字段teacherId为teacher表的外键,而studentId为student表的外键,两个字段构成复合主键,所以并不允许插入重复的数据,执行上述sql,会插入中间表的数据为:
teacherId , studentId (1 , 1) (1 , 2) (2 , 1) (2 , 2) --------------------------- (1 , 1) (2 , 1) (1 , 2) (2 , 2)
所以并无法插入数据,导致报错。
从上方错误,也许我们应该清楚了,多对多只能限定一个实体维护两个实体间关系(维护中间表)。
所以修改上方的配置,假设让student来维护,那么就需要在teacher.hbm.xml中设定inverse="true",代表不由teacher来维护:
<class name="Teacher" table="teacher"> <id name="teaId" type="int" column="teaid"> <generator class="native"/> </id> <property name="teaName" type="string" column="teaname" length="20"/> <set name="students" table="teacher_student" inverse="true"> <key column="teacherId"/> <many-to-many class="Student" column="studentId"/> </set> </class>
修改测试程序:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { //构建对象 Teacher t1 = new Teacher(); t1.setTeaName("aaa"); Teacher t2 = new Teacher(); t2.setTeaName("bbb"); Student s1 = new Student(); s1.setStuName("sss"); Student s2 = new Student(); s2.setStuName("ssss"); //老师t1、t2分别有两个学生 /*t1.getStudents().add(s1); t1.getStudents().add(s2); t2.getStudents().add(s1); t2.getStudents().add(s2);*/ //学生s1、s2分别有两个老师 s1.getTeachers().add(t1); s1.getTeachers().add(t2); s2.getTeachers().add(t1); s2.getTeachers().add(t2); Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); session.save(t1); session.save(t2); session.save(s1); session.save(s2); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
执行结果成功,查询表结构与数据:
student
teacher
teacher_student
3.3cascade级联属性
什么是级联?
举个很明了的例子就是:假设有两张表,一张是部门表,一张是雇员表,部门表和雇员表为一对多的关系,这时表结构就应该以下设计
tb_dept tb_employee deptId deptName empId empName departmentId | | --------------------------------------------- 主键 外键
现在假设tb_dept有如下数据:
部门id:1,部门名称:开发部
假设tb_employee有如下数据:
员工id:1,员工名称:小吖,部门id:1
员工id:2,员工名称:小撒,部门id:1
假设删除开发部,那么只需将tb_employee表中的部门id置为null,再删除部门即可,并不会删除员工;
而级联,在删除开发部后,那么开发部所相关的所有员工,也一并从tb_employee被清空。
了解了级联,有以下一个问题需要解决-->之前的一对多,多对多没提出,是因为在这说明更有利于理解级联和对象直接的关联:
--->一对多和多对多类似,以一对多来说明,上述已经说明,一般使用多的一方来维护两个实体间的关系:
拿一对多的例子来说,一个部门对应多个员工
public class Department{ private Integer id; private String deptName; private Set<Employee> employees = new HashSet<Employee>(); setter...getter... } public class Employee{ private Integer id; private String empName; private Department department; setter...getter... } Department dept = new Department(); dept.setDeptName("java"); Employee emp1 = new Employee(); emp1.setEmpName("小李"); Employee emp2 = new Employee(); emp2.setEmpName("小陈"); 以上是相关实体,Department是部门,deptName是部门名称; employee是员工,empName是员工名称; 现在将小李和小陈都分配在java部门,那么代码就是 emp1.setDepartment(dept); emp2.setDepartment(dept); 执行保存... session.save(emp1); session.save(emp2); 执行后报错了,原因在于department表并没有数据,employee表外键要引用department表主键id找不到;所以,执行保存时,相关的表都要先得有数据,才能执行关联;所以,执行保存代码为: session.save(dept); session.save(emp1); session.save(emp2);
通过上述更深入的例子说明,也许都知道了级联存在的重要性了,有了级联,上方例子在执行第一次保存时就不会报错,并且hibernate都将帮我们在两张表生成数据。
了解了以上,开始级联的详细说明:
级联,使用属性cascade来设置,由于目的是使两个关联的对象的一个对象根据另一个对象的操作而做相同操作,所以在一对一和多对多中,级联属性是设置在集合上的,看以下示例(以以上的一对多为例):
1.准备部门和员工数据
departmentemployee
2.在department.hbm.xml做如下配置(级联删除:cascade="delete"):
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.hibernate.one2many"> <class name="Department" table="department"> <id name="deptId" type="int" column="deptId"> <generator class="native"/> </id> <property name="deptName" type="string" column="deptName" length="20"/> <!-- 配置一对多关系,由于employees是list集合,所以还是使用<list>标签来配置 --> <list name="employees" cascade="delete"> <key column="departmentId"/> <list-index column="idx"/> <one-to-many class="com.hibernate.one2many.Employee"/> </list> </class> </hibernate-mapping>
测试程序:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { //构建部门实体 /*Department dept = new Department(); dept.setDeptId(1); dept.setDeptName("java");*/ //构建员工实体 /*Employee emp1 = new Employee(); emp1.setEmpName("小李"); Employee emp2 = new Employee(); emp2.setEmpName("小陈");*/ //建立部门和员工的关系 /*dept.getEmployees().add(emp1); dept.getEmployees().add(emp2);*/ Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); Department department = (Department) session.get(Department.class, 1); session.delete(department); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
查看生成的sql:
Hibernate: select department0_.deptId as deptId0_0_, department0_.deptName as deptName0_0_ from department department0_ where department0_.deptId=? Hibernate: select employees0_.departmentId as departme3_0_1_, employees0_.empId as empId1_, employees0_.idx as idx1_, employees0_.empId as empId1_0_, employees0_.empName as empName1_0_, employees0_.departmentId as departme3_1_0_ from employee employees0_ where employees0_.departmentId=? Hibernate: update employee set departmentId=null, idx=null where departmentId=? Hibernate: delete from employee where empId=? Hibernate: delete from employee where empId=? Hibernate: delete from department where deptId=?
除了delete,级联还有save-update,all,none可选择!
当选择save-update,保存数据时,没有级联的情况是:
session.save(emp1);
session.save(emp2);
session.save(dept);
而有级联的情况为session.save(dept);相关联的employee表会自动插入emp1和emp2数据。
如果要让一个实体随着另一个关联实体的操作而做相同操作(不仅仅是删除,还有增加、更新等),那么将级联属性值设置为all即可;
而级联属性值为none,代表不做任何相同操作。
3.4一对一映射
一对一,如何表示两个实体间的关系?
我们有两种方式来表达它们之间的关系:
1.使用两张表来表示两个实体,其中一张表使用一个外键引用另一张表的主键;
注意:有外键的表的这一列,数值是不能重复的(因为一对一)。
2.使用两张表来表示两个实体,其中一张表的主键引用另一张表的主键;
一对一,常见的例子有:一个公民拥有一个身份证,一个国家只能有一个元首,一个村庄只有一个村长等等,这里以公民和身份证为例。
实体类:
public class Person { private Integer id; private String name; private IdCard idCard; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public IdCard getIdCard() { return idCard; } public void setIdCard(IdCard idCard) { this.idCard = idCard; } }
public class IdCard { private Integer id; private String number; private Person person; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } public Person getPerson() { return person; } public void setPerson(Person person) { this.person = person; } }
映射文件:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.hibernate.one2one"> <class name="Person" table="person"> <id name="id" type="int" column="id"> <generator class="native"/> </id> <property name="name" type="string" column="name" length="20"/> <!-- 配置一对多关系,由于employees是list集合,所以还是使用<list>标签来配置 --> <!-- property-ref的值为被关联方idCard的外键所对应的属性 --> <one-to-one name="idCard" class="IdCard" property-ref="person"></one-to-one> </class> </hibernate-mapping>
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.hibernate.one2one"> <class name="IdCard" table="idcard"> <id name="id" type="int" column="id"> <generator class="native"/> </id> <property name="number" type="string" column="'number'" length="20"/> <!-- 不是一对一吗?怎么配置成多对一? --> <!-- 其实也是使用外键表示两者之间的关系,只是和一对多有所区别的是,外键列值是唯一的 --> <many-to-one name="person" class="Person" column="personId" unique="true"/> </class> </hibernate-mapping>
测试程序:
public class App { private static SessionFactory sessionFactory; static { Configuration cfg = new Configuration(); cfg.configure("hibernate.cfg.xml"); sessionFactory = cfg.buildSessionFactory(); } public static void main(String[] args) { //构建对象 Person person = new Person(); person.setName("lucy"); IdCard idCard = new IdCard(); idCard.setNumber("350212199912120001"); //关联对象 person.setIdCard(idCard); idCard.setPerson(person); Session session = sessionFactory.openSession(); Transaction tx = null; try { tx= session.beginTransaction(); tx.begin(); session.save(person); session.save(idCard); tx.commit(); } catch (HibernateException e) { tx.rollback(); e.printStackTrace(); } finally{ session.close(); sessionFactory.close(); } } }
生成的sql:
Hibernate: insert into person (name) values (?) Hibernate: insert into idcard (number, personId) values (?, ?)
表结构和数据:
person
person
注意:在一对多和多对多中,我们可以设置inverse来控制由谁来维护双方的关系,而一对一中,并没有inverse属性可以设置,hibernate要求必须由有外键方的实体来维护双方的关系,即上方必须使用IdCard这个实体来维护和Person实体的关系。
3.其它映射方式
后续...