与MyBatis类似,Hibernate也有关联查询,数据库中表与表之间的关联关系使用主外键的形式体现,实体对象之间的关联关系体现在对象与对象的引用。在Mybatis中,当涉及到复杂查询并且表之间存在关联关系时,查询的返回类型如果是resultType不能满足要求,需要使用ResultMap并在其中进行配置,当查询的结果为一个实体类时,使用association标签,当查询的结果是一个集合时,使用collection标签,具体参考自己写的博客。本文将关联查询user和book,来学习Hibernate关联查询。
关联查询步骤
(1)创建实体类user/book,建立两张表t_user和t_book
实体类user和book:
package Entity; import java.io.Serializable; import java.util.HashSet; import java.util.Set; public class user implements Serializable{ private static final long serialVersionUID = 2697475450509781362L; private int id; private String name; private Set<book> books=new HashSet<book>();//一个用户对应多个书名 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 user() { super(); } public Set<book> getBooks() { return books; } public void setBooks(Set<book> books) { this.books = books; } @Override public String toString() { return "user [id=" + id + ", name=" + name + ", books=" + books + "]"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; user other = (user) obj; if (id != other.id) return false; return true; } }
package Entity; import java.io.Serializable; public class book implements Serializable{ private static final long serialVersionUID = -5148507992206736063L; private int id; private String name; private user user;//多个书名对应一个用户 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 book() { super(); } public user getUser() { return user; } public void setUser(user user) { this.user = user; } public book(int id, String name, Entity.user user) { super(); this.id = id; this.name = name; this.user = user; } @Override public String toString() { return "book [id=" + id + ", name=" + name + ", user=" + user.getName() + "]";//切断死循环使用 } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; book other = (book) obj; if (id != other.id) return false; return true; } }
(2)配置映射文件user.hbm.xml和book.hbm.xml
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- 配置映射文件,名字一般用实体类类名.hbm.xml --> <hibernate-mapping> <!-- 创建表和实体类的映射关系 --> <class name="Entity.user" table="t_user"> <!-- 配置主键id --> <id name="id" column="id" type="int"> <!-- MySql中设置了主键自增,使用identity生成策略 --> <generator class="identity"></generator> </id> <!-- 配置name属性 --> <property name="name" column="name" type="string"></property> <!-- 配置book属性 --> <!-- user和book的关系是一对多,采用one-to-many标签 --> <!-- lazy设置为false,代表不懒惰加载,默认是懒惰加载 --> <set name="books" lazy="false"><!-- 实体类对应属性名 --> <key column="user_id"></key><!-- 通过user_id关联查询,可以查到用户 --> <one-to-many class="Entity.book"></one-to-many> </set> </class> </hibernate-mapping>
配置user.hbm.xml,配置id和name很好理解,数据库和表中一一对应就好,但是当需要查询得到user的books属性时,就变得稍微复杂一点,因为user实体类对应的表t_user并没有books相关的字段,需要解决得到book就需要使用关联查询得到,并且关联查询的条件是t_user.id=t_book.user_id,因此配置中column属性就为user_id,代表用它关联查询得到book,里面需要使用one-to-many关键字,代表一个user可能对应多个book。
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <!-- 配置映射文件,名字一般用实体类类名.hbm.xml --> <hibernate-mapping> <!-- 创建表和实体类的映射关系 --> <class name="Entity.book" table="t_book"><!-- 注意类名要写包名.类名 --> <!-- 配置主键id --> <id name="id" column="id" type="int"> <!-- MySql中设置了主键自增,使用identity生成策略 --> <generator class="identity"></generator> </id> <!-- 配置name属性 --> <property name="name" column="name" type="string"></property> <!-- 配置user属性 --> <!-- user和book的关系是多对一的关系,使用many-to-one标签,此外要想得到user,需要使用表中的字段user_id关联 --> <!-- 如果返回的类型是一个实体类,使用class属性来指定 --> <many-to-one name="user" column="user_id" class="Entity.user"></many-to-one> </class> </hibernate-mapping>
配置book.hbm.xml,与上面类似,配置id和name容易理解,配置user属性时,也需要使用user_id来关联查询。
hibernate.cfg.xml配置:
<?xml version="1.0" encoding="GBK"?> <!-- 指定Hibernate配置文件的DTD信息 --> <!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="dialect">org.hibernate.dialect.MySQLDialect</property> <!-- 测试连接oracle --> <!-- <property name="dialect">org.hibernate.dialect.Oracle10gDialect</property> --> <!-- 连接数据库 --> <!-- 驱动,url,用户名和密码 --> <property name="connection.driver_class">com.mysql.jdbc.Driver</property> <property name="connection.url">jdbc:mysql://localhost:3306/ssh</property> <property name="connection.username">root</property> <property name="connection.password">2688</property> <!-- 连接oracle测试 --> <!-- <property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property> <property name="connection.url">jdbc:oracle:thin:@localhost:1521:orcl</property> <property name="connection.username">SCOTT</property> <property name="connection.password">Yang2688</property>--> <!-- 显示Hibernate持久层操作所使用的SQL --> <property name="show_sql">true</property> <!-- 将脚本格式化后再进行输出,sql developer中的ctrl+F7就是快速格式化 --> <property name="hibernate.format_sql">true</property> <!-- 设置Mapping映射资源文件位置 --> <mapping resource="user.hbm.xml"/> <mapping resource="book.hbm.xml"/> </session-factory> </hibernate-configuration>
(3)实现测试方法,查询id=1的user用户数据
package TestCase; import java.util.List; import java.util.Set; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.junit.Test; import Entity.book; import Entity.user; /** * 测试Hibernate关联查询 * @author yangchaolin * */ public class testHibernate { public static Session getSession() { //读取hibernate.cfg.xml配置文件 Configuration cfg=new Configuration(); cfg.configure("hibernate.cfg.xml"); //创建sessionfactory SessionFactory factory=cfg.buildSessionFactory(); //创建session Session session=factory.openSession(); return session; } //测试获取user id为1的用户的信息 @Test public void test() { //读取hibernate.cfg.xml配置文件 Configuration cfg=new Configuration(); cfg.configure("hibernate.cfg.xml"); //创建sessionfactory SessionFactory factory=cfg.buildSessionFactory(); //创建session Session session=factory.openSession(); //创建一个事务并开启事务 Transaction trans=session.getTransaction(); trans.begin(); //开始执行持久层操作 user user=(user) session.get(user.class, 1);//1代表user的id属性值 System.out.println(user.getId()); System.out.println(user.getName()); Set<book> books=user.getBooks(); for(book book:books) { System.out.println(book); /** * 这里输出book信息时,会报StackOverFlowError,为内存溢出,主要原因为陷入了死循环。 * (1)输出book,会调用book类的toString方法,发现toString方法里需要输出user * (2)为了输出user,会调用user类的toString方法,发现需要输出books,其中books是一个集合 * (3)为了输出集合books,会调用集合类底层的toString方法,其中集合底层是用ArrayList保存数据,调用ArrayList的toString方法还是会调用book * (4)为了输出底层集合的book,又会继续跳回到第一步,如此反复执行造成栈溢出。 * 解决办法:切断死循环 * */ } //关闭session session.close(); } }
在查询打印出book的时候,刚开始报警栈溢出,原因为陷入了死循环,打印book时调用其toString方法需要输出user,然后继续调用user类的toString方法,user类的toString方法里要输出books集合,集合底层使用的是ArrayList保存对象,输出集合的时候也会调用其下面的toString方法,输出单个的book对象,然后又陷入了需要打印user老路,因此陷入了死循环造成栈溢出。需要切换死循环只需要任意切断其中一个线路,本例就打印user时只打印user的name来切断。
测试结果
可以看出来是一次性查询出所有结果,这是因为将与懒惰加载相关的lazy属性设置了false,因此没有懒惰加载一次性查询出所有数据。
结论
与MyBatis类似,Hibernate中也有关联查询,只是使用的关键字不太一样,Hibernate中常用就是one-to-many和many-to-one标签,column属性中写上关联查询的关键字。