什么是Hibernate及其作用
Hibernate是一个数据访问(dao)框架(持久层框架),可以简化数据库操作代码,提升开发效率。Hibernate框架是对JDBC技术的封装,类似的框架有MyBatis、JPA等。
原有使用JDBC+SQL方式对数据库操作时,有以下几点弊端:
1. 表字段多的情况下,需要写复杂的sql语句
2. 不同数据库SQL语句存在一定差异,移植性较差 (一开始用的是sqlserver,换成oracle则需要修改语句)
3. 需要编写大量的代码实现实体对象和表记录之间的转换,非常繁琐。
利用Hibernate框架可以解决上述问题。有以下优点:
1. 可以自动生成sql
2. 可以自动完成实体类和表记录之间的转换(映射)
3. 可以增强数据库的移植性
Hibernate设计原理
Hibernate框架是基于ORM思想对JDBC进行封装设计的。ORM:Object Relation Mapping 被称为对象关系映射。
主要思想是:能够完成程序中Java实体类和关系数据库中表记录之间的转换。可发者可以直接将对象直接写入数据库,查询时可以直接从数据库中以对象的形式取出,中间对象和记录的转换细节由ORM框架负责,开发者对底层细节不用关心。
目前流行的ORM框架(ORM工具)有以下几个:Hibernate、MyBatis 、JPA等。
利用Hibernate框架可简化数据库操作,他将JDBC和SQL语句封装起来,不需要使用者编写.。发者需要了解和使用Hibernate API。
Hibernate框架结构
1. Hibernate框架使用时,需要以下几个重要文件:
a. 实体类(*.java) n个 与数据表对应,用于封装数据表的一行记录。
b. XML映射文件(*.hbm.xml) n个 用于描述实体类与数据表之间的对应关系.类属性与表字段之间的对应关系,一个实体类对应一个XML映射文。
c. 主配置文件(Hibernate.cfg.xml) 1个 用于指定连接数据库的参数,框架参数等。
2. Hibernate编程API
a. Configuration
Configuration conf=new Configuration(); conf.configure("hibernate.cfg.xml");//负责加载hibernate.cfg.xml配置文件和映射文件
b. SessionFactory
SessionFactory factory=conf.buildSessionFactory();//负责生成数据库的连接对象(Session)
c. Session
Session session=factory.openSession();//原Connection对象的封装,代表Hibernate与数据库之间的一次连接.负责执行增删改查操作. session.load() session.get();//查询记录 session.save();// 添加记录--插入 session.update(); //更新记录--更新,一次只能更新1行,按主键做条件 session.delete();//删除记录--删除,一次只能删除1行,按主键做条件
d. Query 用于执行非主键查询的操作
e. Transaction 用于事务控制,将两个或者两个以上的DML操作封装成一个整个操作
Hibernate基本应用
使用步骤
1. 引入hibernate开发包、数据库驱动包
2. 引入hibernate.cfg.xml配置文件(src下)
3. 根据数据表编写一个实体类
4. 编写hbm.xml映射描述文件(描述实体类和表之间的对应关系)
5. 使用hibernate API编程
案例
1. 引入jar包
2. 添加hibernate配置文件(hibernate.cfg.xml)。注意应该放在源文件的src目录下,默认为Hibernate.cfg.xml,文件内容是Hibernate工作时必须用到的基础信息。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- 数据库连接信息--> <property name="connection.url"> jdbc:mysql://localhost:3306/qin?useUnicode=true&characterEncoding=utf8 </property> <property name="connection.username">root</property> <property name="connection.password">sa</property> <property name="connection.driver_class"> com.mysql.jdbc.Driver </property> <!--dialect是方言,用于配置生成生成对哪个数据库的SQL语句--> <property name="dialect"> org.hibernate.dialect.MySQLDialect </property> <!--显示底层生成的SQL语句,将执行SQL打印到控制台,一般用于SQL调优--> <property name="hibernate.show_sql">true</property> <property name="hibernate.format_sql">true</property> <!-- 指定映射描述文件,可以添加多个mapping定义 --> <mapping resource="org/tarena/mapping/User.hbm.xml" /> </session-factory> </hibernate-configuration>
3. 根据数据表,编写实体类,映射文件
User.java
public class User implements Serializable{ private Integer id; private String email;private String nickname; private String password;//get/set方法 }
User.hbm.xml
<?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 package="org.entity"> <!-- name指定实体类 table指定表名 catalog指定是哪个数据库用户--> <class name="User" table="d_user" catalog="testq"> <!-- id仅用于主键字段的映射 --> <id name="id" type="integer"> <column name="id" /> <!---主键生成方式 --> <generator class="identity"></generator> </id> <!-- property用于非主键字段的映射 name指定实体类属性名(大小写敏感) type指定属性类型(大小写不敏感) --> <property name="email" type="string"> <!-- column指定对应的字段名 --> <column name="email" length="50" not-null="true" unique="true" /> </property> <property name="nickname" type="string"> <column name="nickname" length="50" /> </property> <property name="password" type="string"> <column name="password" length="50" not-null="true" /> </property> </class> </hibernate-mapping>
该步骤可以使用注解方式
@Entity @Table(name = "help_question") @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @Proxy(lazy = false) public class Question { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "question_id") private Integer questionId; @Column(name = "question_title") private String questionTitle; @Column(name = "question_content") private String questionContent; @Column(name = "issue_date") private Date issueDate; }
4. 在配置文件中关联映射文件
<mapping resource="entity/User.hbm.xml" />
5. 利用Hibernate API操作实体对象
public void testFindById(){ //加载hibernate配置及hbm.xml映射文件,默认加载src下的文件,如果配置文件关联了映射文件,同时也装载了映射文件 Configuration conf = new Configuration(); conf.configure("/hibernate.cfg.xml"); //获取SessionFactory SessionFactory sf = conf.buildSessionFactory(); //获取Session Session session = sf.openSession(); //按id主键条件查询load(),get()。 get(要查询的类型,主键值);get没记录返回null,load没记录抛异常 User user = (Cost)session.get(User.class, 3); if(user != null){ System.out.println(user.getId()); }else{ System.out.println("没有记录"); } //关闭连接资源
session.close(); } public void testSave(){ Configuration conf = new Configuration(); conf.configure("/hibernate.cfg.xml"); SessionFactory sf = conf.buildSessionFactory(); Session session = sf.openSession()
//开启事务控制,默认情况下,关闭了自动commit功能,如果进行DML操作,必须追加事务控制
Transaction tx = session.beginTransaction();
User user = new User();
user.setEmail("test@qq.com");
session.save(user);//添加一条记录,将user对象信息写入数据表
tx.commit(); //提交事务
session.close(); //关闭连接
}
public void testDelete(){
Configuration conf = new Configuration();
conf.configure("/hibernate.cfg.xml");
SessionFactory sf = conf.buildSessionFactory();
Session session = sf.openSession();
Transaction tx = session.beginTransaction();
User user = new User();
user.setId(91);
session.delete(user); //执行删除
tx.commit(); //关闭
session.close();
}
public void testUpdate(){
Configuration conf = new Configuration();
conf.configure("/hibernate.cfg.xml");
SessionFactory sf = conf.buildSessionFactory();
Session session = sf.openSession();
Transaction tx = session.beginTransaction();
User user = (Cost)session.get(User.class, 101);
user.setEmail("test2@qqcom");
session.update(cost); //执行更新,将cost数据状态更新到数据表
tx.commit(); //提交事务
session.close(); //关闭连接
}
Hibernate映射类型
在hbm.xml中定义属性和字段映射时,通过type属性指定映射类型,其作用是指定属性和字段之间采用哪种类型赋值.可以采用下面两种方法指定:
1. 指定Java类型 java.lang.String java.lang.Integer
2. Hibernate映射类型(推荐)
字符串 string
字符 character
整数 byte,short,integer,long
浮点数 float,double
日期 date,time,timestamp
boolean类型 yes_no,true_false
true_false : 完成实体类boolean属性和表字段char之间的转换。true值转换成T;false值转换成F
yes_no : 完成实体类boolean属性和表字段char之间的转换。true值转换成Y;false值转换成N
其他 big_decimal,big_integer,clob(大对象类型),blob(小对象类型)
Hibernate主键生成方式
在hbm.xml映射描述中,可以指定主键值采用哪种方法生成和管理。(仅适用于添加操作)
<generator class="生成方法"> //.... </generator>
class属性用于指定主键生成方法,Hibernate提供了以下几个预定义的方法:
sequence
采用一个序列生成主键值。只适用于Oracle数据库。
<generator class="sequence"> <param name="sequence"> //指定序列名称 </param> </generator>
identity
Hibernate会利用数据库自动增长机制生成主键。适用于MySQL、SQLSERVER数据库
<generator class="identity"></generator>
注意:建表时需要为主键字段设置自增长功能
关于解决mysql乱码:
a. create database XXX default character set utf8
b. create table qin_emp(....)engine=innodb default charset=utf8;
c. hibernate.cfg.xml中连接字符串
<property name="connection.url">jdbc:mysql://localhost:3306/qin?useUnicode=true&characterEncoding=utf8</property>
native
根据hibernate.cfg.xml中的dialect属性指定主键生成方法。如果dialect是OracleDialect会采用sequence方法;如果是MySQLDialect会identity方法。
如果dialect是oracle
<generator class="sequence"> <param name="sequence"> //指定序列名称 </param> </generator>
如果dialect是mysql
<generator class="identity"></generator>
increment
首先发送一个select max(ID)语句查询当前表中ID最大值,然后将最大值+1给insert语句指定。适用于各种类型数据库。
<!-- 注意:该方式在并发时,有可能产生重复ID,因此并发几率高时,不要使用。 --> <generator class="increment"> </generator>
assigned
Hibernate会放弃主键值的生成和管理。意味着程序员需要在程序中显式指定ID值。
uuid算法
采用UUID算法生成一个主键值(字符串类型的ID)
<generator class="uuid"></generator>
hilo算法
采用高低位算法生成一个主键值。hilo 和 seqhilo生成器给出了两种hi/lo算法的实现
第一种情况
<id name="id" type="id" column="id"> <generator class="hilo"> <param name="table">zhxy_hilo_tbl</param> <param name="column">next_value</param> <param name="max_lo">0</param> </generator> </id>
第二种情况
<id name="id" type="long" column="cat_id"> <generator class="seqhilo"> <param name="sequence">hi_value</param> <param name="max_lo">100</param> </generator> </id>
第二种情况需要sequence的支持,这里只讨论更通用的第一种情况
默认请况下使用的表是hibernate_unique_key,默认字段叫作next_hi。next_hi必须有一条记录否则会出现错误。算法百度
自定义规则生成主键值
如果需要按自定义规则生成主键值,可以自定义一个主键生成器.方法如下:
1. 编写一个生成器类,实现IdentifierGenerator接口,实现约定好的generator方法,该方法返回值就是主键值.
2. 在hbm.xml中通过<generator class="包名.类名">的方式使用
StudentGeneratorId.java
// 添加时,会自动调用该方法获取一个主键值 public class StudentGeneratorId implements IdentifierGenerator { public Serializable generate(SessionImplementor arg0, Object arg1) throws HibernateException { // 根据t_student中id值的状态生成下一个主键值 // 1.查询出t_student表中当前id值 String hql = "select max(id) from Student"; Session session = HibernateUtil.getSession(); Query query = session.createQuery(hql); List list = query.list();// 执行查询,获取结果 String curr_id = (String)list.get(0);// 获取最大的id值 session.close();// 关闭session if(curr_id == null){// 如果没有记录,返回一个0001编号 return "001"; } // 2.根据当前id+1,获取下一个 String classNo = curr_id.substring(0, 7);// 班号 String stuNo = curr_id.substring(7);// 学号 int nextStuNo = Integer.parseInt(stuNo) + 1;// 学号+1 // 将nextStuNo变成XXXX格式 int len = (nextStuNo + "").length(); String tmpNo = "";// 判断该补几个0 for(int i = 1; i <= 4 - len; i++){ tmpNo += "0"; } String no = classNo + tmpNo + nextStuNo; System.out.println("no:" + no); return no; } }
Student.hbm.xml
<id name="id" type="string"> <column name="id"></column> <generator class="id.StudentGeneratorId"></generator> </id>
测试
public void testAdd() { Student stu = new Student(); stu.setName("tom"); stu.setAge(20); stu.setSex("M"); Session session = HibernateUtil.getSession(); Transaction tx = session.beginTransaction(); session.save(stu);// stu处于持久状态,id已分配有值 System.out.println(stu.getId()); tx.commit(); session.close(); }
Hibernate的基本特性
一级缓存
二级缓存
对象持久化
延迟加载
一级缓存(默认启用)
一般将频繁使用的数据放入缓存中,以内存空间换取时间的一种策略。
什么是一级缓存
一级缓存指的是Session级别的缓存,由Session对象负责管理。不同的Session对象都有自己独立一级缓存空间,不能互相访问。
session如何管理一级缓存的:
1. session.get/load方法时,会先去一级缓存查找,没有对象信息才去数据库查找,查找后将返回的对象放入一级缓存。后续再查找该对象会返回缓存中的信息,从而减少了访问数据库的次数。
2. session需要负责实时维护在缓存中的数据,保证缓存中的数据与数据库数据的一致性,一旦用户对缓存中的数据做了修改,当提交时,session负责将数据更新到数据库中。
一级缓存的好处
利用同一个Session多次访问同一个实体对象时,对数据库只查询一次,后续几次从缓存获取。
一级缓存的管理
当使用session.load,session.get方法会将查询出的对象自动放入一级缓存,要移除一级缓存的对象,可以使用
session.clear()//移除缓存中所有对象 session.evict(obj)//移除指定的obj对象 session.flush()//将缓存中对象的状态与数据库同步 rx.commit()//内部会首先调用flush,之后commit提交 session.close()//关闭连接,释放缓存资源。
批量操作,注意及时清理缓存
循环次数很多,每次id不同时
for(;;){ Cost cost = (Cost)session.get(Cost.class,id); //使用cost对象--省略 session.evict(cost);//及时清理缓存的对象 }
为了更好的使用一级缓存,在同一个线程处理中不同组件应使用同一个Session对象.可以使用ThreadLocal技术session对象与处理线程绑定
public class HibernateUtil { private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml"; private static ThreadLocal<Session> threadLocal = new ThreadLocal<Session>(); private static SessionFactory sessionFactory; // SessionFactory一般只有一个,一个SessionFactory可以创建多个Session static{ try{ // 加载hibernate.cfg.xml文件,创建SessionFactory Configuration conf = new Configuration(); conf.configure(CONFIG_FILE_LOCATION); sessionFactory = conf.buildSessionFactory(); } catch(Exception e){ System.err.println("%%%% Error Creating SessionFactory %%%%"); } } // 返回ThreadLocal中的session实例 public static Session getSession() { Session session = threadLocal.get(); // 如果session为空或者session被关闭 if(session == null || !session.isOpen()){ if(sessionFactory == null){ return null; } session = sessionFactory.openSession(); threadLocal.set(session); } return session; } // 关闭session public static void closeSession() { Session session = threadLocal.get(); if(session != null){ session.close(); } threadLocal.set(null); } }
对象持久化
什么是持久化
Hibernate的持久化指的是将程序中Java对象的数据以数据库存储形式保存下来。Hibernate是一个持久层框架。持久层里面都是由持久对象构成,这些对象的持久化操作由Hibernate实现。
对象持久性:当一个对象的数据发生改变,会与数据库记录进行同步修改。垃圾回收器不能回收该对象。
Hibernate对象状态
在使用Hibernate中,java对象有以下3种状态
1. 临时状态---临时对象
使用时,刚new出来的对象。使用new 操作运算符初始化的对象的状态时瞬间的,如果没有任何跟数据表相关联的行为,只要应用程序不再引用这些对象,它们的状态将会消失,并由垃圾回收机制回收。这种状态被称为暂时态。
2. 持久状态--持久对象
使用了session对象的save,update,load等方法后,该对象就处于持久状态.
a. 持久对象存在于Session缓存中,由Session负责管理
b. 持久对象不能被垃圾回收器回收,它的数据状态改变可以与数据库同步。由session负责同步操作。
c. 持久对象数据改变后,在事务commit之后执行update更新。
session.flush();//将缓存中对象与数据库同步 tx.commit();//等价于session.flush+事务提交.
3. 托管或游离状态
当关闭session,或使用session.evict(),clear()方法将对象移除后,该对象脱离了Session管理。表示这个对象不能再与数据库保持同步,它们不再受Hibernate管理。
测试持久性
public void test1() { Foo foo = new Foo(); foo.setValue("foo100");// 现在的foo是暂时态 Session session = HibernateUtil.getSession(); Transaction tx = session.beginTransaction(); Session.save(foo);// 现在的foo是持久态 // 测试:当foo为持久态时,修改value为foo200 foo.setValue("foo200"); foo.setValue("foo300"); // 当执行tx.commit()操作时,事务提交,此时会自动调用session.flush(),再执行commit()操作。而只有当执行了session.flush()操作时,session才会把持久对象的改变更新到数据库。 tx.commit(); Session.close(); }// 只执行一次update()语句 public void test2() { Foo foo = new Foo(); foo.setValue("foo100");// 现在的foo是暂时态 Session session = HibernateUtil.getSession(); Transaction tx = session.beginTransaction(); Session.save(foo);// 现在的foo是持久态 // 测试:当foo为持久态时,修改value为foo200 foo.setValue("foo200"); Session.flush(); foo.setValue("foo300"); tx.commit(); Session.close(); }// 执行两次update()操作
批量操作
// 向COST插入100000条记录 Transaction tx = session.beginTransaction(); // 插入操作 for(int i = 1; i <= 100000; i++){ Cost cost = new Cost(); // 设置cost属性值 session.save(cost); // 分批执行 if(i % 100 == 0){ // 将缓存对象与数据库同步操作 session.flush(); session.clear();// 清除缓存的对象 } } tx.commit(); session.close();
延迟加载
1. 什么是延迟加载
Hibernate在使用时,有些API操作是具有延迟加载机制的。延迟加载机制的特点:当通过Hibernate的API获取一个对象结果后,该对象并没有数据库数据。而是在调用实体对象的getXXX方法时才会发送SQL语句加载数据库数据。
2. 哪些操作会采用延迟加载机制
a. 查询:load()延迟加载查询;get()立即加载
b. 执行HQL:iterator()延迟加载;list()非延迟加载
c. 关联操作:获取关联对象属性值时,采用的延迟加载机制
注意:这些方法返回的对象,只有id属性有值,其他数据库在使用时候(调用getXXX()方法)才去获取。
Query query = session.createQuery("from TestQin"); Iterator it = query.iterate(); while(it.hasNext()){ TestQin qin = (TestQin)it.next(); System.out.println(qin.getName()); }// 先查询所有的id值,再根据id值查询对应的数据 while(it.hasNext()){ TestQin qin = (TestQin)it.next(); System.out.println(qin.getId()); }// 不发送sql语句 TestQin qin = (TestQin)session.load(TestQin.class, 1); System.out.println(qin.getId());// 不发送sql语句 TestQin qin = (TestQin)session.load(TestQin.class, 1); System.out.println(qin.getName());// 发送sql语句
关联映射
关联映射主要是在对象之间建立关系。开发者可以通过关系进行信息查询、添加、删除和更新操作。如果不使用Hibernate关联关系映射,我们也可以取到用户对应的服务。
Account account = (Account)session.get(Account.class,1);//取到用户的信息 String hql = “from Service s where s.accountId=1 ”; Query query=session.createQuery(hql);//取到用户对应的服务 List<Service> list=query.list();
而Hibernate提供的关联映射,更方便一些。
一对多关系 one-to-many
多对一关系 many-to-one
关联操作
多对多关系 many-to-many
继承关系
一对多关系 one-to-many
典型的一对多关系:班级和学生,一个班级可以有很多学生,一个学生就只能属于一个班级。
需求:在操作班级时同时要操作班级的学生信息,这样可以写成noe-to-many映射。Account和Service也是一对多关系,一个客户可以开通多个业务,一个业务只能属于一个客户。
实现:在查询account时,可以把account对应的service信息取出来。
1. 在one方也就是account实体类添加Set集合属性,以及对应的get/set方法
public class Account implements java.io.Serializable { // Fields private Integer id; private Integer recommenderId; private String loginName; private String loginPasswd; //... //追加关系属性,用于存储相关的Service对象信息 private Set<Service> services = new HashSet<Service>(); }
2. 在One方Account.hbm.xml映射文件中,加入Set节点的映射。
<set name=”属性名” > <!--关联条件,column写外键字段,会默认的与account表中的主键相关联--> <key column=”指定关联条件的外键字段”></key> <one-to-many class=”指定要关联和加载的一方many方”/> </set> <!--如果是list集合用<list name=””></list> set 集合用<set name=’”>--> <set name=’”services”> <key column=”ACCOUNT_ID” ></key> <one-to-many class=”entity.Service”/> </set> <!--注意:关联属性数据加载默认采用延迟加载机制,使用中不要过早关闭session。 -->
3. 测试
Session session = HibernateUtil.getSession(); Account account = (Account)session.load(Account.class, 1005); // 延迟加载,第一次发送sql语句查询 from Account where id = 1005 System.out.println(account.getRealName()); // 延迟加载,第二次发送sql语句查询 from Service where account_id = 1005 Set<Service> services = account.getServices(); for(Service s : services){ System.out.println(s.getOsUsername()); } session.close();
多对一关系 many-to-one
多个学生对应一个班级,这是多对一关系。多个Service服务对应一个Account账号,所以是多对一关系。Service是N方
需求:在得到Service信息时,同时得到它所对应的Account信息。
1. 在N方Service实体类添加Account属性,以及对应的get/set方法
public class Service implements java.io.Serializable { private long id; // private long accountId;//注意:Account中已经包含了accountId了,所以原来的accountId属性要删除掉,否则会报错 private String unixHost; private String osUsername; private long costId; private Account account; // 追加属性,用于存储关联的Account信息 }
注意事项:Service实体原来的accountId属性删除了,相应的get/set方法也删除,Service的映射文件对应的描述也要删掉,否则会报错,Repeated column in mapping for entity
2. 在N方Service.hbm.xml映射文件中描述account属性
<many-to-one name="关联属性名" class="要关联和加载的一方Account" column="指定关联条件的外键字段(不写主键)"/> <many-to-one name="account" class="entity.Account" column="ACCOUNT_ID">
3. 测试
Session session = HibernateUtil.getSession(); Service s = (Service)session.load(Service.class, 2001);// hql= "from Service where id=2001"; System.out.println(s.getOsUsername());// 延迟加载,第一次发送sql语句查询 Account account = s.getAccount(); System.out.println(account.getId());// 获得id值时没有去数据库查询,在第一次发送sql语句时查询出account_id值放入account对象的id属性中hql= from Account where id=... System.out.println(account.getRealName());// 延迟加载,在调用get方法后第二次发送sql语句查询
关联操作
关联抓取 join fetch
在建立关联映射后,默认情况下在调用关联属性的getter方法时,会再次发送一个sql语句加载关系表数据。如果需要将关联数据与主对象一起加载(两个SQL查询合成一个SQL查询),可以采用join fetch。
方式一:使用lazy=”false” fetch=”join”
在hbm.xml关联属性映射描述中,使用lazy=”false” fetch=”join”。该方法不推荐采用,因为会影响所有查询操作,建议采用HQL的join fetch写法。不推荐使用,因为影响的映射范围太广,如果不需要用到关联对象,也加载了浪费内存。
lazy属性
可以控制是否延迟加载。lazy="true"采取延迟加载,lazy="false"采取立刻加载。默认为true
fetch属性
可以控制关联属性抓取的策略。fetch="select"单独再发送一个select语句加载关联属性数据(默认值),fetch="join"采取表连接方式将关联属性数据同主对象一同抓取.(一个SQL完成)。
<set name="services" lazy="false" fetch="join"> <key column="ACCOUNT_ID"></key> <one-to-many class="entity.Service"/> </set> <many-to-one name="account" class="entity.Account" column="ACCOUNT_ID" lazy="false" fetch="join" />
Session session = HibernateUtil.getSession(); Account account = (Account)session.load(Account.class, 1011);// lazy=false时,取出account后,在属性services中已经填充了所有的服务项 System.out.println(account.getRealName()); session.close();// 如果Account.hbm.xml中关联属性设置了lazy=false时,在这里关闭能正常运行。 System.out.println(account.getServices().size());// 如果lazy=true,这里会报错
方式二:HQL,关联抓取join fetch(推荐使用)
HQL语法格式:"from 类型 别名 join fetch 别名.关联属性"。如"from Account a join fetch a.services"
默认情况下,关联属性在抓取时,采用单独在发送一条SQL语句实现。采用join fetch可以实现用一条SQL语句抓取主对象和关联属性的数据信息。
提示:如果需要使用主对象和关系属性数据,建议采用join fetch方式,可以减少与数据库的交互次数。
1. 一对多
Session session = HibernateUtil.getSession(); String hql = "from Account a join fetch a.services where a.id=?";// hql语句和在hbm.xml中设置fetch=”join”效果一样 Query query = session.createQuery(hql); query.setInteger(0, 1011); Account account = (Account)query.uniqueResult();// 发送sql语句进行查询 System.out.println(account.getId()); Set<Service> services = account.getServices(); for(Service s : services){ System.out.println(s.getOsUsername()); }
2. 多对一
Session session = HibernateUtil.getSession(); String hql = "from Service s join fetch s.account where s.id=?"; Query query = session.createQuery(hql); query.setInteger(0, 2001); Service s = (Service)query.uniqueResult(); System.out.println(s.getOsUsername()); System.out.println(s.getAccount().getRealName());
级联操作
在建立关联映射之后,可以通过关系实现级联的添加、删除、更新操作。级联操作默认是关闭的,如果需要使用,可以在关联属性映射部分添加cascade属性,
属性值有:
1. none默认值,不支持级联
2. save-update: 级联保存(load以后如果子对象发生了更新,也会级联更新).但它不会级联删除
3. delete: 级联删除, 但不具备级联保存和更新
4. all-delete-orphan: 在解除父子关系时,自动删除不属于父对象的子对象,也支持级联删除和级联保存更新.
5. all: 级联删除, 级联更新,但解除父子关系时不会自动删除子对象.
6. delete-orphan:删除所有和当前对象解除关联关系的对象
<set name="services" cascade="all"> <key column="ACCOUNT_ID"></key> <one-to-many class="entity.Service"/> </set>
测试级联添加
public class Test { /** *整个测试发送了5个sql语句 *1.insert into Account... *2.insert into Service .. *3.insert into Service... *service中有个account,最后更新service_qin表中的account_id值, 值为Service的属性account的id值 *4.update Service ... *5. update Service... */ public void test1() { Session session = HibernateUtil.getSession(); Transaction tc = session.beginTransaction(); Account account = new Account(); account.setLoginName("test1"); account.setLoginPasswd("test1"); account.setTelephone("123456789"); account.setIdcardNo("123456789"); account.setRealName("test1"); Service s1 = new Service(); s1.setOsUsername("test2"); s1.setUnixHost("1.1.0.127"); s1.setLoginPasswd("test2"); s1.setCostId(3); s1.setAccount(account); Service s2 = new Service(); s2.setOsUsername("test3"); s2.setUnixHost("1.1.0.128"); s2.setLoginPasswd("test3"); s2.setCostId(3); s2.setAccount(account); account.getServices().add(s1); account.getServices().add(s2); session.save(account); tc.commit(); session.close(); } }
测试级联删除
当对主对象进行删除时,关联属性的记录也进行相应删除。
public class Test { public void test2() { Session session = HibernateUtil.getSession(); Transaction tc = session.beginTransaction(); Account account = (Account)session.load(Account.class, 115);// account需要采用查询方式获取,不能采用new方式。建议在one-to-many部分添加inverse=true设置,这样可以避免update account_id=null的操作,直接进行delete删除。 session.delete(account); tc.commit(); session.close(); /** * 整个测试发送5个sql语句 * 1.select from account_qin where id=115 * 2.select from service_qin where account_id=115 * 3.delete from service_qin where id=? * 4.delete from service_qin where id=? * 5.delete from service_qin where id=? * 可以看出hibernate级联删除的缺点:delete都是按照id(主键)一条一条删除的,不是按照关系字段删的,当数据量小时可以用hibernate的级联删除,简单方便些。但是当数据量大时,Hibernate的级联删除效率低,则不建议使用Hibernate的级联删除。 * 建议采用HQL语句的方式 * delete from Account where id=? * delete from Service where account.id=? * 注意事项: 级联删除不写inverse=”true”,且数据库中service_qin表中的account_id为not null约束,那么程序最后会执行update,会设置account_id=null,将与数据库冲突!报错,所以应当加上inverse=”true” */ } }
inverse属性
1. 默认情况下,在采用了级联操作时,Hibernate在执行insert update delete基本操作后,还要执行update关系字段的操作(即关系维护工作account表中的id和service表中的account_id字段)。
2. 默认是关联对象双方都要负责关系维护,在级联添加案例中,控制台最后会有两个update语句,因为当前添加了一个Account和两个Service,所以One方要维护两个Service。即两个update语句。如果数据量很大,则要维护N个Service。则有N个update语句,此时会影响性能。
3. 为了使update语句不出现。可以在Account.hbm.xml中的关联属性添加inverse=”true”属性,即当前一方放弃关系维护,这项工作交给对方负责。
<set name="services" cascade="all" inverse="true"> <key column="ACCOUNT_ID"></key> <one-to-many class="entity.Service"/> </set>
4. 注意:当遇到一对多、多对一关系映射时,把inverse=”true”属性加到one-to-many方One方放弃,Many方维护,能起到一定的优化作用。