一、Hibernate框架介绍 Hibernate是一种ORM 框架,用于在java对象和关系型数据库之间建立映射,以实现直接存取java对象,以面向对象的方法操作数据库,自动生成SQL语句,简化开发。
ORM :Object Relational Mapping,对象关系映射,用于实现面向对象编程语言里不同类型系统数据之间的转换,简单来说就是对象和数据库表之间通过映射关系联系起来,一个对象对应一张数据库表。
二、Hibernate的使用 1. 导入jar包
2. HIbernate的核心配置文件 将project/etc
下的 hibernate.cfg.xml
配置文件拷贝到src
目录下,并配置如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <!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:///hibernate</property > <property name ="hibernate.connection.username" > root</property > <property name ="hibernate.connection.password" > root</property > <property name ="show_sql" > true</property > <property name ="format_sql" > true</property > <property name ="hibernate.connection.autocommit" > true</property > <property name ="hibernate.hbm2ddl.auto" > update</property > <mapping resource ="com/gyf/hibernate/domain/User.hbm.xml" /> </session-factory > </hibernate-configuration >
3. 编写javaBean与映射文件javaBean.hbm.xml 注意:
XXX.hbm.xml里的xxx与javabean同名,比如模型为User,那么映射文件应该命名为User.hbm.xml,并且放置在同一个包里面,比如都是放在domain包下;
映射文件里,如果javabean的成员变量与数据库表字段不一致,要指定column
4. 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Test01 { @Test public void test1 () { Configuration cfg = new Configuration().configure(); SessionFactory factory = cfg.buildSessionFactory(); Session session = factory.openSession(); Transaction trans = session.beginTransaction(); User user = new User(); user.setUsername("zhuobo" ); user.setPassword("abcdefg" ); session.save(user); trans.commit(); session.close(); factory.close(); } }
三、Hibernate的一些API 1. Configuration配置对象 一般来说Hibernate可是使用两种核心配置文件的格式,一种是xml
格式、另一种是properties
格式,这两个配置文件都是放在project/etc
目录下。由于properties
格式的文件是键值对的形式,有键名必须唯一的局限性,一般来说xml配置文件更加常用,可以配置更多的内容。
在上面的测试中,要先获取Configuration
配置对象,事实上就是在加载配置文件:
1 2 3 4 5 Configuration cfg = new Configuration().configure();
加载映射文件 :
在hibernate.cfg.xml中配置,这是最常用的方法:
1 2 <!-- 2 、配置JavaBean与表的映射文件 --> <mapping resource="com/gyf/hibernate/domain/User.hbm.xml" />
配置文件对象Configuration
调用方法addResource()
:
1 2 3 Configuration cfg = new Configuration().configure(); cfg.addResource("cn\\zhuobo\\web\\domain\\User.hbm.xml" );
配置文件对象Configuration
调用方法addClass()
:
1 2 3 Configuration cfg = new Configuration().configure(); cfg.addClass(User.class);
这三种方法,第一种在核心配置文件hibernate.cfg.xml中配置映射文件时最常使用的方法。
2. SessionFactory SessionFactory是生产session的工厂,就相当于数据库连接池,管理数据库连接。要注意的是SessionFactory不是轻量级的,一般来说一个项目只需要一个SessionFactory就可以了,用来负责管理应用程序和数据库的所有会话(session),SessionFactory是线程安全的 。获取SessionFactory
的方法是Configuration
配置对象调用方法buildSessionFactory()
:
1 2 3 4 Configuration cfg = new Configuration().configure(); SessionFactory factory = cfg.buildSessionFactory();
3. Session Hibernate中的Session与HttpSession的概念有点不一样,HttpSession表示用户与服务器的一次会话,Hibernate的Session表示应用程序与数据库的一次会话(交互)。session是轻量级的,线程不安全的,通常将一个session和一个数据库事务绑定,每次执行一个数据库事务都要创建一个session(从SessionFactory中获取一个session),使用session后还有关闭session(归还session)。
SessionFactory提供了两个方法来获取Session:
factory.openSession():获取一个全新的Session;
factory.getCurrentSession():获取与当前线程绑定的Session,一个线程只有一个Session。
要获取与当前线程绑定的Session,要在hibernate配置文件hibernate.cfg.xml
中配置一个属性:
1 <property name ="hibernate.current_session_context_class" > thread</property >
1 2 3 4 5 6 Session currentSession1 = factory.getCurrentSession(); Session currentSession2 = factory.getCurrentSession(); System.out.println(currentSession1.hashCode()); System.out.println(currentSession2.hashCode());
4. Transaction事务 在hibernate中获取、开启、提交、回滚事务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Transaction transaction = session.getTransaction(); transaction.begin(); User user = new User(); user.setUsername("Alice" ); user.setPassword("alicePassword" ); session.save(user); transaction.commit(); User user = (User) session.get(User.class, 3 ); System.out.println(user); transaction.rollback();
5. Session的方法
get():通过id查询,如果没有返回为null;
1 2 3 4 Session session = factory.openSession(); User user = (User) session.get(User.class, 3 ); System.out.println(user);
load():通过Id查询,如果没没有抛出异常;
get()和load()的区别:get方法执行的时候直接加载数据库,查询数据库,load是一种懒加载 ,也就是当load方法被执行的时候不直接去加载数据库,而是要使用到数据才去加载数据库,比如要访问或者更新获取到的对象的数据,才去查询数据库
save():保存
update():更新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 User user = (User) session.get(User.class, 4 ); user.setPassword("bbbbb" ); session.update(user); transaction.begin(); User user = new User(); user.setUsername("Tom" ); user.setPassword("cccc" ); session.saveOrUpdate(user); transaction.commit();
delete():删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 transaction.begin(); User user = (User) session.get(User.class, 3 ); System.out.println(user); session.delete(user); transaction.commit(); transaction.begin(); User user1 = new User(); user1.setUid(9 ); session.delete(user1); transaction.commit();
6 .Query查询对象 HQL :Hibernate Query Language,hibernate查询语言,是一种面向对象,面向对象的数据的查询语言(SQL 面向数据库,依赖数据库,),实际上HQL被hibernate翻译为SQL来执行。
查询一行数据:
1 2 3 4 5 6 7 Query query = session.createQuery("from User where username = ? and password = ?" ); query.setParameter(0 , "zhuobo" ); query.setParameter(1 , "pass" ); User user = (User) query.uniqueResult(); System.out.println(user);
分页查询:
1 2 3 4 5 6 7 8 9 10 11 Query query = session.createQuery("from User" ); query.setFirstResult(3 ); query.setMaxResults(3 ); List list = query.list(); for (Object o : list) { System.out.println(o); }
7. Criteria查询对象 QBC: query by Criteria,hibernate提供的纯面向对象的查询语言;
查询
1 2 3 4 5 6 7 8 Criteria criteria = session.createCriteria(User.class); criteria.add(Restrictions.eq("username" , "zhuobo" )); criteria.add(Restrictions.eq("password" , "pass" )); User user = (User) criteria.uniqueResult(); System.out.println(user);
模糊查询
1 2 3 4 criteria.add(Restrictions.like("username" , "%bo%" )); List<User> list = criteria.list(); System.out.println(list);
8. SQL查询对象 使用原生的SQL进行查询。
1 2 3 4 5 6 7 8 List<Object[]> list = sqlQuery.list(); for (Object[] objects : list) { for (Object object : objects) { System.out.println(object); } System.out.println("----------" ); }
9. 抽取一个hibernate工具类 为了简化hibernate的书写,抽取一个工具类。该工具类应该提供获取session
的方法,提供关闭会话工厂的方法。
创建一个工具类 cn.zhuobo.web.utils.HibernateUtils.java
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 public class HibernateUtils { private static SessionFactory factory; static { Configuration configure = new Configuration().configure(); factory = configure.buildSessionFactory(); Runtime.getRuntime().addShutdownHook(new Thread(){ @Override public void run () { System.out.println("程序结束了-----------" ); factory.close(); } }); } public static Session openSession () { return factory.openSession(); } public static Session getCurrentSession () { return factory.getCurrentSession(); } }
四、xxx.hbm.xml
映射文件 1. 实体类的编写规则
提供无参数的构造方法;
提供一个标识 属性,映射数据库表的主键 字段,也就是要提供一个ID;
所有的属性都要提供set、get方法;
用到基本数据类型时,尽量使用基本数据类型的包装类 ;
不要使用final
修饰实体,否则该实体无法被继承,无法生成代理对象进行优化;
2. 持久化对象的唯一标识 OID
OID 也就是对象的ID:
java是根据对象的地址 区分同一个类的不同对象的;
关系型数据库根据主键 区分不同的记录;
hibernate使用OID 来建立内存中的对象和数据库中的记录的对应关系:也就是对象的OID 和数据库的主键的对应关系;
为了保证OID 的唯一性,一般来说对象的ID不人为地赋值,让hibernate为OID赋值;
3. 自然主键和代理主键
主键条件:非空、不重复、不可改变;
自然主键:在业务中,某个属性符合主键的三个条件的要求,那么这个属性可以作为主键列(比如用户名);
代理主键:如果在业务中没有符合主键三个条件的属性,那么可以增加一个没有意义的属性作为主键;
4. 基本数据类型和包装类
基本数据类型和包装类对应的hibernate的映射的类型是相同的;
基本数据类型无法表示null ,如数字类型的默认值是0;
包装类型的默认值是null ;
5. 主键的生成策略
native:mysql数据库中,id自动增长;最常用的规则;
increment:也是自动增长,但是会执行select max(id)...
,根据最大的id进行增长;
sequence:一般在Oracle数据库中使用;
hilo:hibernate自己的id规则,一般不使用;
uuid:长字符串,表示ID的类型是字符串类型,需要把模型(实体)的id改为字符串类型,在保存的时候不需要自己设置id,hibernate自动为对象设置id;最常使用
assigned:需要自己设置id;最常使用
1 2 3 <id name ="uid" column ="id" > <generator class ="native" > </generator > </id >
1 2 3 4 5 6 7 8 9 10 11 session.beginTransaction().begin(); User user = new User(); user.setUsername("zhuobodd" ); user.setPassword("zhuobodd_password" ); user.setGender("male" ); user.setUid(UUID.randomUUID().toString().replace("-" , "" )); session.save(user); session.beginTransaction().commit();
6. 普通属性 1. 动态插入 当没有配置动态插入 的时候,插入一个对象的SQL语句会操作这个对象的所有属性,无论该属性是否为null :
配置动态插入:
1 2 3 4 5 6 7 8 9 10 <class name ="cn.zhuobo.web.domain.User" table ="t_user" dynamic-insert ="true" > <id name ="uid" column ="id" > <generator class ="native" > </generator > </id > <property name ="username" > </property > <property name ="password" > </property > <property name ="gender" > </property > </class >
配置动态插入前的SQL:
1 2 3 4 5 6 7 insert into t_user (username, password , gender) values (?, ?, ?)
配置动态插入后的SQL:
1 2 3 4 5 6 7 insert into t_user (username) values (?)
2. 动态更新 与动态插入类似,配置动态更新之前,一旦更新对象的某个属性,就会更新全部属性。比如:现在只更新了一个username属性 ,但是hibernate自动生成的SQL语句还是更新了全部的属性:
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void test3 () { Session session = HibernateUtils.openSession(); session.beginTransaction().begin(); User user = (User) session.get(User.class, 1 ); user.setUsername("zhuoboaa" ); session.beginTransaction().commit(); }
生成的SQL语句:
1 2 3 4 5 6 7 8 9 Hibernate: update t_user set username=?, password =?, gender=? where id =?
在 xxx.hbm.xml
配置动态更新:dynamic-update="true"
1 2 3 4 5 6 7 8 9 10 11 <hibernate-mapping > <class name ="cn.zhuobo.web.domain.User" table ="t_user" dynamic-insert ="true" dynamic-update ="true" > <id name ="uid" column ="id" > <generator class ="native" > </generator > </id > <property name ="username" > </property > <property name ="password" > </property > <property name ="gender" > </property > </class > </hibernate-mapping >
生成的SQL语句只更新需要更新的一个属性值,没有更新的属性就没有必要写入SQL语句:
1 2 3 4 5 6 7 Hibernate: update t_user set username=? where id =?
4. 类型的使用
type:可以设定数据库表字段的类型:
1 2 3 4 5 6 7 8 <property name ="username" type ="java.lang.String" length ="16" > </property > <property name ="password" length ="16" > </property > <property name ="gender" > </property > <property name ="birthday" type ="date" > </property >
length:可以设定数据库表字段的长度:
1 <property name ="password" length ="16" > </property >
五、hibernate实体的状态 1. 实体状态介绍 实体有三种状态:瞬时状态、持久状态、托管状态:
瞬时状态:transient、session中没有缓存、数据库中也没有记录、OID也没有值;
持久状态:persistent、session中有缓存、数据库中有记录、OID也有值;
脱管状态/游离状态:detached、session没有缓存、数据库中有记录、OID有值;
session中可以存储数据(也就是session的缓存),相当于一个id为键、对象为值的键值对的形式;
2. 瞬时状态转为持久状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Test public void test4 () { Session session = HibernateUtils.openSession(); session.beginTransaction().begin(); User user = new User("zhuobo" , "password" ); System.out.println(user); session.save(user); System.out.println(user); session.beginTransaction().commit(); session.close(); }
3. 持久状态转为脱管状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Test public void test5 () { Session session = HibernateUtils.openSession(); session.beginTransaction().begin(); User user = (User) session.get(User.class, 1 ); System.out.println(user); session.clear(); User user1 = (User) session.get(User.class, 1 ); System.out.println(user1); session.beginTransaction().commit(); session.close(); }
六、一级缓存 1. 概念 一级缓存 是session级别的缓存,当获得一次session(会话), hibernate在session中创建多个map集合,用于存放PO对象,为程序优化服务(比如session缓存中有的数就不需要去数据库查询),当需要用到某些数据,hibernate优先从session中查找,再去数据库查询。当session关闭时,一级缓存就被销毁。
2. 优先从一级缓存取数据 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void test5 () { Session session = HibernateUtils.openSession(); session.beginTransaction().begin(); User user = (User) session.get(User.class, 1 ); System.out.println(user); User user1 = (User) session.get(User.class, 1 ); System.out.println(user1); session.beginTransaction().commit(); session.close(); }
3. 清除缓存
session.clear():清除缓存中所有数据;
session.evict(object):清除缓存中指定的对象;
4. 一级缓存快照 快照机制 :hibernate向一级缓存中存放数据时,同时向快照存放一份同样的数据备份,当使用commit()
提交事务时,使用OID判断一级缓存的对象和快照的对象是否一致,如果有缓存中对象的属性发生了变化,那么就执行update
语句,将缓存中的数据更新到数据库,并且更新快照的数据。如果一级缓存的数据和快照的一致,那么就不执行update
语句。即,快照是为了维护一级缓存数据与数据库数据的一致性 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void test6 () { Session session = HibernateUtils.openSession(); session.beginTransaction().begin(); User user = (User) session.get(User.class, 1 ); user.setUsername("abc" ); session.flush(); session.beginTransaction().commit(); session.close(); }
注意: HQL会对数据进行一级缓存(数据存储到session),SQL不会对数据进行一级缓存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Test public void test6 () { Session session = HibernateUtils.openSession(); session.beginTransaction().begin(); Query query = session.createQuery("from User" ); List list = query.list(); User user = (User) session.get(User.class, 1 ); session.beginTransaction().commit(); session.close(); }
七、一些方法的区别 1. save和persist
save
方法和persist
都是用来持久化对象的,保存数据到数据库;
执行save
方法前可以人为地设置id,但是保存时会自己设置的id会被忽略,使用hibernate生成的id;
执行persist
方法前不可以人为地设置id,设置会报错;
2. update 如果OID存在就更新对象,不存在就报错;
3. saveOrUpdate 判断OID是否存在,如果不存在就执行insert语句,否则执行update语句;
八、 hibernate多表关联关系映射 1. 多表关系 一对一:
一对多:主表的主键、从表的外键形成主外键关系 ;
多对多:提供中间表,该中间表提供两个外键(对应两个主表的主键);
2. 一对多、多对一的例子 客户和订单的关系是一个一对多的关系(订单和客户的关系是一个多对一的关系),hibernate要描述这种关系需要按照以下的规则:
编写客户、订单实体类
为实体类编写映射文件
Customer.hbm.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package ="cn.zhuobo.web.domain" > <class name ="Customer" table ="t_customer" > <id name ="id" column ="id" > <generator class ="native" /> </id > <property name ="name" column ="name" length ="20" /> <set name ="orders" > <key column ="customer_id" /> <one-to-many class ="Order" /> </set > </class > </hibernate-mapping >
Order.hbm.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package ="cn.zhuobo.web.domain" > <class name ="Order" table ="t_order" > <id name ="id" column ="id" > <generator class ="native" /> </id > <property name ="name" length ="20" /> <many-to-one name ="customer" column ="customer_id" class ="Customer" /> </class > </hibernate-mapping >
生成的两张表:
测试,先表中存储数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Test public void test7 () { Session session = HibernateUtils.openSession(); session.beginTransaction().begin(); Customer customer = new Customer("Hugh" ); Order order1 = new Order("1984" ); Order order2 = new Order("国富论" ); order1.setCustomer(customer); order2.setCustomer(customer); customer.getOrders().add(order1); customer.getOrders().add(order2); session.save(customer); session.save(order1); session.save(order2); session.beginTransaction().commit(); session.close(); }
设置外键维护的方式
在Customer
的映射文件Customer.hbm.xml
中,set
标签指定属性inverse = "false"
,表示由Customer来维护双发的关系,值为 true
表示由对方来维护关系。
1 2 3 4 5 <set name ="orders" inverse ="true" > <key column ="customer_id" /> <one-to-many class ="Order" /> </set >
指定由谁来维护关系(维护两个表之间的关系),在多对多 的关系中由谁来维护在效率上差别不大,在一对多 的关系中,如果指定了一方 来维护关系,那么但删除或者插入一方 的一条记录,就要更新与这条记录相关的多方 的所有记录;如果指定了多方 来维护关系,那么当删除获取插入一方 的记录,由于关系就是由多方 来维护,就不会有update
操作,就只是删除或者插入多方 的对象即可。即,在一对多的关系中,应当将关系交给多方维护 ,会更加高效、更加直观。
多对一关系中的删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void test9 () { Session session = HibernateUtils.openSession(); session.beginTransaction().begin(); Customer customer = (Customer) session.get(Customer.class, 2 ); for (Order order : customer.getOrders()) { order.setCustomer(null ); } session.delete(customer); session.beginTransaction().commit(); session.close(); }
3. cascade级联 级联,也就是串联,但操作A时,同时也会操作B。
在配置级联之前,如果一个Customer有两个order,那么要存储这三个数据到数据库就要调用3 次save 方法;当配置了级联之后,只需显式地存储一个对象,另外两个对象会自动存储。
配置级联之前: 需要显式地存储3个对象
1 2 3 4 session.save(customer); session.save(order1); session.save(order2);
配置级联 :在映射文件中,set标签内配置
1 2 3 4 5 6 <set name ="orders" inverse ="true" cascade ="save-update" > <key column ="customer_id" /> <one-to-many class ="Order" /> </set >
cascade属性的取值:
save-update:保存、更新数据都采用级联操作的方法;
delete:删除操作采用级联的方法,比如在配置级联删除以前,单独删除Customer会报错,配置会先 删除与之关联的order,后 删除Customer;
delete-orphan:孤儿删除,先解除Customer和与之相关的order的关系,删除B(order)但是还保留A(customer);
这些属性的值可以组合使用,用逗号,
分隔,all(表示save-update
和delete
组合),all-delete-orphan(表示save-update
,delete
,delete-orphan
三个组合);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @Test public void test9 () { Session session = HibernateUtils.openSession(); session.beginTransaction().begin(); Customer customer = (Customer) session.get(Customer.class, 8 ); Set<Order> orders = customer.getOrders(); Iterator<Order> iterator = orders.iterator(); while (iterator.hasNext()) { iterator.next(); iterator.remove(); } session.beginTransaction().commit(); session.close(); }
4. 多对多的例子 学生和课程是一种多对多的关系,一个学生可以选择多门课程、一门课程也可以被多个学生选择,配置过程可一对多类似。
1. 编写实体类
2. 编写映射文件 首先明确多对多的关系需要一张中间表来维护,中间表的两个字段分别关联学生表、课程表,也即是学生表、课程表的两个外键。
Student.hbm.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package ="cn.zhuobo.web.domain" > <class name ="Student" table ="t_student" > <id name ="id" > <generator class ="native" /> </id > <property name ="name" length ="20" /> <set name ="courses" table ="t_student_course" cascade ="all" > <key column ="sid" /> <many-to-many class ="Course" column ="cid" /> </set > </class > </hibernate-mapping >
Course.hbm.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package ="cn.zhuobo.web.domain" > <class name ="Course" table ="t_course" > <id name ="id" > <generator class ="native" /> </id > <property name ="name" length ="20" /> <set name ="students" table ="t_student_course" > <key column ="cid" /> <many-to-many class ="Student" column ="sid" /> </set > </class > </hibernate-mapping >
3. 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Test public void test1 () { Session session = HibernateUtils.openSession(); session.beginTransaction().begin(); Student student1 = new Student("Trumpt" ); Student student2 = new Student("Putin" ); Course java = new Course("Java" ); Course python = new Course("Python" ); student1.getCourses().add(java); student1.getCourses().add(python); student2.getCourses().add(java); student2.getCourses().add(python); session.save(student1); session.save(student2); session.beginTransaction().commit(); session.close(); }
5. 一对一的例子之外键方法 学校和学校的地址是一对一的关系,一个学校一般来说就一个地址,一个地址上只要一个学校。注意: 一对一事实上就是特殊的多对一,多对一是在多的一方 提供联系一的一方 的外键,而这个外键不是唯一的,便形成了多对一。而还是在某一方提供一个联系另一方的外键,而这个外键是唯一的,便可以形成一对一。
1. 编写实体类:
2. 编写映射文件
School.hbm.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package ="cn.zhuobo.web.domain" > <class name ="School" table ="t_school" > <id name ="id" > <generator class ="native" /> </id > <property name ="name" length ="50" /> <one-to-one name ="address" class ="Address" property-ref ="school" /> </class > </hibernate-mapping >
Address.hbm.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package ="cn.zhuobo.web.domain" > <class name ="Address" table ="t_address" > <id name ="id" > <generator class ="native" /> </id > <property name ="name" length ="20" /> <many-to-one name ="school" class ="School" column ="school_id" unique ="true" /> </class > </hibernate-mapping >
3. 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @Test public void test1 () { Session session = HibernateUtils.openSession(); session.getTransaction().begin(); School school1 = new School("Havard University" ); School school2 = new School("Sun Yat-Sen University" ); Address address1 = new Address("Boston" ); Address address2 = new Address("Guangzhou" ); address1.setSchool(school1); address2.setSchool(school2); session.save(school1); session.save(school2); session.save(address1); session.save(address2); session.getTransaction().commit(); session.close(); }
6. 一对一的例子之主键方法 与上一个方法不同,这种方法的重点在于不用再address表额外添加一个外键字段,因为是一对一,而且school表的id也是唯一的,故而可以将school表的主键字段id作为address表的主键,同时也是school表在address表的外键。
1. 编写实体类 实体类和上面的添加一个外键列的实体类一样。
2. 编写映射文件 实体类School的映射文件和上面的一样,主要是修改Address的映射文件,要将Address的主键设置为School的外键:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package ="cn.zhuobo.web.domain" > <class name ="Address" table ="t_address" > <id name ="id" > <generator class ="foreign" > <param name ="property" > school</param > </generator > </id > <property name ="name" length ="50" /> <one-to-one name ="school" class ="School" constrained ="true" /> </class > </hibernate-mapping >
7. 加载策略 加载策略也即是从数据库中加载数据的策略。
1. 类级别的加载策略 类级别指的是映射文件的在class
标签配置lazy
的加载策略,lazy
属性为false表示不要懒加载
,值为true表示要懒加载。
get:即时加载 ,get方法一执行就执行SQL语句查询数据库;
load:懒加载 ,load方法执行后不会立即执行SQL语句查询数据库,当需要用到非OID之外的属性才会去加载数据库;
配置懒加载:在映射文件中的class
节点中配置属性lazy="false"
,表示不懒加载,那么load方法效果与get方法一样。
2. set关联级别的加载策略 set关联级别的加载策略指的是一个对象关联了一个集合,比如一个student关联一个课程的set集合,在set标签配置lazy
的加载策略;默认情况下是懒加载,也就是在默认情况下,加载一个student不会去加载学生拥有的课程,当要使用到课程才会去数据库加载课程。
默认情况,懒加载:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Test public void test3 () { Session session = HibernateUtils.openSession(); Student student = (Student) session.load(Student.class, 2 ); System.out.println(student); System.out.println("------分割线------" ); System.out.println(student.getCourses()); }
在set标签配置lazy属性,值为false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void test3 () { Session session = HibernateUtils.openSession(); Student student = (Student) session.load(Student.class, 2 ); System.out.println(student); System.out.println("------分割线------" ); session.close(); }
3. set中的fetch fetch:可以指定关联的对象获取的方式,默认是select
,此外还有join
、subselect
,以一对多为例:
select:【默认】,普通的先查询对象,再查询关联对象,产生1+n
条SQL语句;
join:使用外连接表的查询,产生1条SQL语句;
subselect:使用子查询;
4. 多对一的加载策略 上面的set标签里面的加载策略在many-to-one
中也you类似的加载策略,但是会略有不同。
5. 批量加载 批量加载指的是可以指定一次加载多少数据,比如在一对多 中,配置了批量加载后就可以指定一次加载多少数据。
九、常用配置 1. 整合c3p0连接池
把c3p0的jar包导入项目
配置c3p0
查看c3p0的默认配置,在文件hibernate.properties
中,查找c3p0
相关默认配置
hibernate.properties
文件中关于c3p0的默认配置:
在hibernate核心配置文件 hibernate.cfg.xml
中配置相关内容
使用,获取session,出现以下说明配置成功
2. 事务的隔离级别 脏读: 一个事务访问数据并修改了数据,另一个事务读取到了没有提交的数据;
不可重复读: 在一个事务内,两个读取的数据不一致(这是因为两次读取之间有另一个事务对数据作出修改),针对insert
和update
操作;
幻读: 一个事务对表中的全部数据作出了修改,而此时还有另一个事务insert
或者delete
一条记录,那么第一个事务就会发现还有数据没有被修改(或者数据少了),就像产生了幻觉一样,针对insert
和delete
操作;
事务的四个隔离级别 :
事务隔离级别
问题
read uncommitted
读未提交,存在脏读、不可重复读、虚读3个问题
read committed
读已提交,解决脏读问题,存在2个问题,
repeatable read
解决脏读、不可重复读问题,还存在1个问题,MySQL默认
serializable
串行化,没有任何问题,就是慢
hibernate中配置事务的隔离级别:在核心配置文件hibernate.cfg.xml
中配置
1 2 3 4 5 6 7 <property name ="hibernate.connection.isolation" > 4</property >
3. 悲观锁 悲观锁和乐观锁是多用户环境并发控制的两种锁机制,悲观锁有强烈的独占和排他性,对数据被外界修改持保守态度,在整个数据处理过程,将数据锁定。悲观锁假定会发生并发冲突 ,屏蔽一切可能违反数据完整性的操作;
悲观锁分为读锁(共享锁)、写锁(排他锁):
读锁(共享锁):读锁可以被线程共享,多个线程都可以用同一把读锁读取数据 ;
MySQL添加读锁:select * from table lock in share mode;
1 2 3 4 5 A 终端开启事务,添加读锁 B 终端开启事务,添加读锁 A 终端更新数据 B 提交事务 A 提交事务
写锁(排他锁):写锁不能被共享,只要有人为数据添加了写锁,其他线程不能为数据添加其他锁,知道该线程提交数据,其他的线程都不能读取数据;
MySQL添加写锁:MySQL可以为一张表 、或者一行数据 添加写锁:
1 2 select * from table for update ; select * from table where id = 9 for update ;
Hibernate添加写锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Test public void test1 () { Session session = HibernateUtils.openSession(); session.beginTransaction(); Customer customer1 = (Customer) session.get(Customer.class, 9 , LockOptions.UPGRADE); System.out.println(customer1); Query query = session.createQuery("FROM Customer where id = ?" ); query.setParameter(0 , 9 ); query.setLockOptions(LockOptions.UPGRADE); Customer customer2 = (Customer) query.uniqueResult(); System.out.println(customer2); }
4. 乐观锁 乐观锁就是在表中添加一个version字段来控制数据的不一致性,也就是通过查看数据版本的方法去判断数据是否是脏数据。在hibernate中,记录每次被更新,version
字段的值都会加1。一个线程当前获取的一条记录的version字段值为a,如果再次查看该字段发现已经比a大了,说明该记录已经被别人修改,是脏数据。乐观锁与悲观锁的不同点在于,对于数据没有被修改很乐观,假设不会发生并发冲突,因此不会上锁,只是在提交数据更新的时候根据version字段判断一下数据是否在自己操作期间被修改了。
为实体类添加version属性
映射文件配置version节点
生成的表格有version字段
注意:
悲观锁是MySQL底层自己实现的,乐观锁只是加了一个version字段,是hibernate实现的;
乐观锁可能出现的问题:如果前端要更新数据,但是可能不会去更新version字段,导致其他的用户不知道数据被更新过了;
悲观锁 适用于写入操作频繁 的场景,因为如果为频繁的读取操作加锁将会增加开销,降低吞吐率;
乐观锁 适用于读取操作频繁 的场景,因为频繁的写入操作会造成冲突的概率增大,不断地尝试重新获取数据,增加大量的查询操作,降低吞吐率;
5. 整合log4f
导入log4j的jar包;
把log4j.properties
导入项目的src目录:
配置日志
log4f.properties
配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ### direct log messages to stdout ### # 数据显示在控制台 log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target=System.out log4j.appender.stdout.layout=org.apache.log4j.PatternLayout # 日志输出布局(格式) log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### direct messages to file hibernate.log ### # 数据写到指定的文件 log4j.appender.file=org.apache.log4j.FileAppender log4j.appender.file.File=C:\\Users\\zhuobo\\Desktop\\test_log\\hibernate.log log4j.appender.file.layout=org.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n ### set log levels - for more verbose logging change 'info' to 'debug' ### ### 日志记录器 ### #log4j 日志级别 : # fatal 致命错误 # error 错误 # warn 警告 # info 信息 # debug 调试信息 # trace 堆栈信息 (由高到底顺序) # 日志级别:info表示比info高级的日志都会输出 # 输出源1:stdout表示输出源是控制台 # 输出源2: file输出到指定的文件 log4j.rootLogger=info, stdout, file
十、二级缓存 在hibernate中,一级缓存是Session级别的缓存、在一次会话(一次和数据库的交互)中共享数据。二级缓存是SessionFactory级别的缓存 ,SessionFactory是非轻量级的,一个应用程序共享一个会话工厂,也就是共享一个二级缓存。
SessionFactory缓存分为两个部分:
内置缓存 :使用一个Map,用于存放配置信息,预定义的HQL语句等,提供给Hibernate框架自己使用的,对外只读 ,不能被修改数据;
外置缓存 :使用另一个Map,用于存放用户自定义数据 。默认是不开启的。Hibernate框架值提供外置缓存的规范(接口),需要第三方来实现。
1. 二级缓存结构
2. 缓存并发访问策略(Cache Concurrency Strategy) 4个Hibernate内置的并发策略:
■ transactional —Available in a managed environment only. It guarantees full transactional isolation up to repeatable read, if required. Use this strategy for read-mostly data where it’s critical to prevent stale data in concurrent transactions, in the rare case of an update. ■ read-write —Maintains read committed isolation, using a timestamping mechanism. It’s available only in non-clustered environments. Again, use this strategy for read-mostly data where it’s critical to prevent stale data in concurrent transactions, in the rare case of an update. ■ nonstrict-read-write —Makes no guarantee of consistency between the cache and the database. If there is a possibility of concurrent access to the same entity, you should configure a sufficiently short expiry timeout. Otherwise, you may read stale data in the cache. Use this strategy if data rarely changes(many hours, days or even a week) and a small likelihood of stale data isn’t of critical concern. Hibernate invalidates the cached element if a modified object is flushed, but this is an asynchronous operation, without any cache locking or guarantee that the retrieved data is the latest version. ■ read-only —A concurrency strategy suitable for data which never changes.Use it for reference data only.
摘自《Hibernate in Action》
一般来说,最经常使用的是read-only的策略,因为二级缓存的应用场景本来就是很少被修改的数据。
3. 应用场景
适合存储到二级缓存的数据:很少被修改的数据,不是很重要的数据,允许偶尔出现并发访问问题,比如国家、地区信息等长时间不变的;
不适合存储到二级缓存的数据:经常被修改的数据,绝对不允许出现并发访问问题的,比如财务数据,与其他应用共享的数据。
4. 二级缓存的提供商 Hibernate框架只是提供了二级缓存的规范(接口),有提供商来实现接口,有一下二级缓存提供商:
EHCache:最常使用;
OpenSymphony
SwarmCache
JBossCache
这四个提供商实现的二级缓存对并发策略的支持(打叉标志是表示支持):
5. 整合二级缓存EHCache 1. 导入jar包
2. 配置 hibernate.cfg.xml
3. 配置类缓存、集合缓存
4. ehcache.xml
配置文件 将ehcache.xml
配置文件复制到src
目录下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <ehcache xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation ="../config/ehcache.xsd" > <diskStore path ="java.io.tmpdir" /> <defaultCache maxElementsInMemory ="10000" eternal ="false" timeToIdleSeconds ="120" timeToLiveSeconds ="120" overflowToDisk ="true" maxElementsOnDisk ="10000000" diskPersistent ="false" diskExpiryThreadIntervalSeconds ="120" memoryStoreEvictionPolicy ="LRU" /> </ehcache >
6 . 测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Test public void test2 () { Session session1 = HibernateUtils.openSession(); Session session2 = HibernateUtils.openSession(); Customer customer1 = (Customer) session1.get(Customer.class, 9 ); System.out.println(customer1.getName()); session1.clear(); System.out.println("--------------------" ); Customer customer2 = (Customer) session1.get(Customer.class, 9 ); System.out.println(customer2.getName()); session1.close(); Customer customer3 = (Customer) session2.get(Customer.class, 9 ); System.out.println(customer3.getName()); session2.close(); }
类级别的缓存: 一级缓存存储的是PO
(持久化对象的引用),也就是在同一个session中获取同一个OID指向的对象获得的是同一个对象(同一个内存地址的对象);二级缓存存储的是对象的散装数据 ,不同的事务要获取同一个OID指向的对象是从二级缓存中获取对象的散装数据,再组装为对象,因此获得的是不同的对象。(相当于重新new一个对象,再分别根据散装数据设置不同的属性)。
集合级别的缓存 :存储的是关联的对象的OID的值 ,如果需要数据,就会从类级别缓存中获取,如果类级别缓存中没有数就查询数据库。
查询缓存 :查询缓存不是将OID作为key,而是将HQL查询语句作为key,将查询结果的ID列表作为value(存储的只是结果的id列表)。一个HQL第一次执行时,查询数据库,将HQL作为key、结果的OID列表作为value放到查询缓存,同时将结果集存储到二级缓存。当再次执行同样的HQL时,根据查询缓存的OID列表去二级缓存(类级别的缓存)中去获取数据,封装为对象。
查询缓存的问题:
两个HQL稍微不同(比如第一个查询id在10-20之间,第二个查询id在15-20之间),那么就会被认为斯不同的key,导致缓存的利用率不高;
查询缓存必须配置二级缓存使用,如果二级缓存设置了超时时间,哪怕冲查询缓存中获得了要查询的对象的id,也会因为到二级缓存中查询时超时就必须根据获得的id一个一个去数据库查询(有多少id就查询多少次数据库),反而使得性能下降;
查询缓存配置 :
开启查询缓存
1 2 <property name ="hibernate.cache.use_query_cache" > true</property >
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 @Test public void test2 () { Session session1 = HibernateUtils.openSession(); Query query = session1.createQuery("from Customer" ); query.setCacheable(true ); List<Customer> customers1 = query.list(); System.out.println(customers1); session1.close(); Session session2 = HibernateUtils.openSession(); query = session2.createQuery("from Customer" ); query.setCacheable(true ); List<Customer> customers2 = query.list(); System.out.println(customers2); Customer customer = (Customer) session2.get(Customer.class, 9 ); System.out.println(customer); session2.close(); }