Hibernate

来源:互联网 发布:网络安全法宣传图片 编辑:程序博客网 时间:2024/06/06 13:05
  1. Hibernate的工作原理—ORM 
    对象-关系映射(Object-Relationship Mapping)

    在我们的应用程序(App)中,数据用对象来体现,而在数据库中,数据是使用表的形式保存。
    Hibernate用于应用程序中的对象(Object)与表中的数据关系(Relationship)之间的映射
    (Mapping),即把对象保存到关系表中或者把关系表中数据取出映射为对象。

    可以这样理解,当我们使用Hibernate框架技术,就可以直接从数据库中取出Java对象,或者把
    Java对象直接保存于数据库中,中间写sql语句等繁琐的步骤被Hibernate封装,对我们是透明的。
  2. Hibernate HelloWorld
    核心步骤
    1)  导入Jar包
    2)  Hibernate配置文件(只有1个)
      hibernate.cfg.xml
    用于数据库连接信息及Hibernate的一些配置信息
    3)  Hibernate映射文件(可以有n个)
       用来指明类和表之间的对应关系,Hibernate根据该文件生成SQL语句
    比如POJO类名为Emp.java,对应的映射文件就名为Emp.hbm.xml 
  3.  新建配置文件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>
    <!-- 1. 数据库连接信息 -->
    <property name="connection.url">
      jdbc:mysql://localhost:3306/test
    </property>
    <property name="connection.username">root</property>
    <property name="connection.password">root</property>
    <property name="connection.driver_class">
      com.mysql.jdbc.Driver
    </property>
     
    <!-- 2. Hibernate配置信息 -->
    <!-- dialect是方言,用于配置生成针对哪个数据库的SQL语句-->
    <property name="dialect">
      <!--Hibernate提供的方言类,用于封装某种特定数据库的方言-->
      org.hibernate.dialect.MySQLDialect
    </property>
    <!--将执行sql打印到控制台,一般用于sql调优-->
    <property name="hibernate.show_sql">true</property>
    </session-factory>
    </hibernate-configuration> 
  4. 主键生成方式
    常用的主键生成方式有如下几种:
    1)  identity   用于自动生成主键方式,除了Oracle不支持,其他数据库一般都支持
    (较常用)
    2)  sequence  Oracle中使用,用序列生成ID主键
    3)  native  主键生成方式如果是native,那就看配置文件hibernate.cfg.xml中
    方言<property name="dialect">是什么,
    如果方言是Mysql,相当于identity,如果方言是Oracle,相当于sequence
    4)  increment 不常用
    5)  assigned  不常用,手动生成id 
  5. Hibernate映射类型  
    案例描述
    常用Hibernate映射类型有如下几种:
      string 
      interger 
      double 
      date   日期,只表示年月日
      datetime  日期,只表示年月日
      timestamp   时间戳,存放年月日时分秒
      yes_no    将在数据库中存一个字符Y或者N
      true_false  将在数据库中存放一个字符T或者F,功能同yes_no是相同的 
  6. 持久化对象和一级缓存机制  
1.1. 对象的三种状态
对于Hibernate而言,对象的状态分为3种:
1)  暂时态
当对象刚创建,和Session没有发生任何关系时,当程序运行完就立刻消失,被称为暂时态。
2)  持久态
当执行如下代码时,对象变为持久态
Emp e = new Emp();
session.save();
持久态的对象和Session发生了关系,如执行了save、get、query等方法
  Session中会缓存该对象(Session的缓存叫一级缓存)
  Session再获取对象时,首先去查找一级缓存,如果没有才查询数据库
  Session要负责将持久态对象的变化更新到数据库
(在是flush()的时候更新,tx在提交的时候会自动调用session的flush())  
3)  游离态
调用了session.evict(Object obj)方法,和Session解除了关系 
     1.2. 一级缓存机制  
其一,如果session被查询,session将先到缓存中查找是否有被查询的对象,找到则直接取出,
否则才查询数据库;
其二,session需要负责实时维护在缓存中的数据,保证缓存中的数据与数据库中数据的一致性,
一旦用户对缓存中的数据做了修改,session立刻将数据更新到数据库中。 
7.Hibernate延迟加载机制 
2.1. 基本原理
延迟加载机制的基本原理
当访问实体对象时,并不是立即到数据库中查找。而是在真正要使用实体对象的时候,才去数据库
查询数据。
具备这样功能的方法
  session.load(...)
  query.iterator()
注意:这些方法返回的对象,里面没有数据,数据在使用的时候(调用getXXX()方法时)才取。
2.2. 实现原理
1)  load方法、iterator方法返回的对象不是实体类,而是该实体类动态子类对象,
该子类重写了getXXX方法,在该方法中触发了对数据库的访问。
2)  当调用load方法或iterator方法时,具体Hibernate调用了GBLIB的功能实现了
动态生成子类。
2.3. OpenSessionInView和ThreadLocal 
1)  OpenSessionInView技术把Session的关闭延迟到View组件运行完之后
2)  如果用延迟加载必须使用OpenSessionInView技术,否则在取数据时,session已经关闭了
3)  实现OpenSessionInView可以采用很多技术:
  Servlet——过滤器
  Struts2——拦截器
  Spring —— AOP
4)  使用OpenSessionInView必须满足Session的线程单例
  一个线程分配一个Session,在该线程的方法中可以获得该Session,
  具体使用ThreadLocal——其实是一个线程为KEY的Map,
5)  Hibernate的支持
      配置文件中:
  <property name="current_session_context_class">thread</property>
  然后调用:
  essionFactory.getCurrentSession();
  自动实现线程单例 
8.什么是动态生成一个类? 
一般情况下,我们想创建并使用一个类的流程如下:
a.  编译Java源文件 -> 在硬盘上生成一个二进制.class文件
b.  JVM加载.class文件,将类读入一块内存(方法区)中
c. 应用程序调用方法区中的类及其方法。

而动态生成技术,是应用程序直接在内存中创建了一个类。就像当我们调用load方法,
我们并没有创建Foo$$EnhancerByCGLIB$$b3a0560c 这个类,
该类是由Hibernate动态生成的。
严格来讲,动态生成类技术也不是由Hibernate完成的,是由其他组件生成的,
asm.jar的作用就是在内存中生成类;
cglib-2.1.3.jar是在调用asm.jar的基础上动态的生成子类。因为asm.jar非常底层,
cglib-2.1.3.jar对其做了封装,用于生成某个类的子类。
于是,Hibernate调用了cglib-2.1.3.jar实现延迟加载。
9.线程单例 
回到服务器中,只要是服务器,每一个浏览器访问服务器时,服务器会为每个浏览器创建一个线程。
假设Some就是Session,如果使用这种机制获取Session,当同一个用户浏览器不论怎么调用
session都是同一个(只要在相同的线程中)。
这种机制就叫做线程单例。
线程单例的实现原理就是如上SomeFactory做的。

ThreadLocal
接下来继续回到ThreadLocal
ThradLocal就相当于Map,只不过key是固定的,就是当前线程号。 
10.many-to-one关联映射  
3.1. 数据表的关联
数据表单的关联(只有一种情况)和Hibernate关联映射。
Hibernate关联映射是在数据表关联的基础上,根据业务需求,为了存取数据的方便高效而设计的。
数据表的关联不一定导致Hibernate关联映射。

3.2. many-to-one 
基础表
t_emp (t_id,...t_dept_id)
t_dept (t_id)
需求
在取出Emp(many)的时候Dept(one)关联的取出
步骤1
class Emp {
   ...
   private Dept dept;
}
步骤2
<many-to-one name="dept" class="Dept" column="t_dept_id"/>

3.3. many-to-one 需求下的常见操作 
1) 保存Emp
   Emp中有已经存在的Dept
2) 取出Emp(带着Dept)
  Emp emp = (Emp)session.get(Emp.class,empId);
  关联属性默认是延迟加载
  <many-to-one ... lazy="false"/>
  但是:取Dept还是用单独的SQL,可以设置fetch方式
  <many-to-one ... fetch="join"/>
此时用join的方式生成SQL
3)查询Emp(带着Dept)
      HQL: from Emp
      关联属性默认是延迟加载
  <many-to-one ... lazy="false"/>
  但是:取Dept还是用单独的SQL
  HQL : from Emp e left outer join fetch e.dept
4)根据特定条件查询Emp(带着Dept)
      HQL : from Emp e where e.name='...'
      HQL : from Emp e left outer join fetch e.dept where e.name='...'
5)根据Dept的属性查询Emp(带着Dept)
      HQL : from Emp e where e.dept.name='...'
      HQL : from Emp e left outer join fetch e.dept where e.dept.name='...' 
11.当两个表有关联,并且有many-to-one的需求时,我们使用Hibernate提供的<many-to-one>
关联关系映射。

Hibernate提供的<many-to-one>关联关系映射实现步骤:
a.  many方(Emp)添加one方(Dept)的属性
b.  many方配置文件(Emp.hbm.xml)中添加<many-to-one>信息
c. <many-to-one>要提供One方的class名,对应的列名 
12. one-to-many关联映射   
one-to-many关联映射
1)  one-to-many基本概念
基础:
t_order(t_id,...);
t_item(t_id,...t_order_id);
需求:
操作Order的时候一般都需要操作Item
2)  基本配置:
类:
class Order {
      Set<Item> items;
}
配置文件:
<set name="items">
       <key column="t_order_id"/>
       <one-to-many class="Item"/>
</set>
3)  基本操作
  保存Order,同时关联的保存Item
session.save(order); 生成的SQL: 
insert into t_order ... 
(因为save(order))insert into t_item....  
(因为配置了级联保存<set .. cascade="save-update">)
(但是,存入的数据没有t_order_id,该字段不能为非空)
如何改变?many一方反向关联,Item里面关联Order
所以:one-to-many一般都是双向关联
update t_item set t_order_id...
(因为order的Set中关联了item,所以要将关联关系更新到数据库)
如何改变?many一方已经维护了关联,one这一方没有必要维护
<set ... inverse="true"/>      
删除Order,同时级联删除与之关联的Item
<set cascade="delete"/>或者<set cascade="all"/>
更新Order,Order中解除了和一个Item的关系,
希望在数据库中删掉对应的记录
<set ... cascade="delete-orphan"/> 或者
<set ... cascade="all-delete-orphan"/>
查询Order,同时带出Item
from Order 用select的方式取Item
select distinct(o) from Order o join fetch o.items 用join的方式叏Item
主要重复的问题!!!
查询出包含特定Item的Order
select i.order from Item i where i.productName='...'
select distinct(o) from Order o join fetch o.items i where i.productName='...'
查询Order中包含Item的数目
from Order o;
order.getItems().size()....
Formula的方式:
class Order {
private int itemsNum;
}
< property name="itemsNum" type="integer"
formula="(select count(*) from t_item i where i.t_order_id=t_id"/> 
13.加入inverse="true",inverse表示“反转”。 
如果不加该属性,Hibernate会自动在One方(Order这方)维护关联关系;
写inverse="true"表示关联关系由对方(或Many方,Item)维护,我(Order)就不维护了。
一般由Many方维护关联关系就够了。
one-to-many是一个难点,一定要弄清楚。
对比加属性inverse="true"之前和之后:
在加该属性之前,关联关系是由Order和Item双方来维护的,相当于这样

在加该属性之后,就是告诉Hibernate,关联关系由Many方(Item)来维护

注意:为了保证效率,一般one-to-many关系映射我们还是使用默认,双向来维护关联关系。
many-to-one有时有单向维护关系,比如说员工Emp关联部门Dept,部门Dept中不需要所有
员工的信息。 
14.cascade="delete"表示级联删除 
如果想保存、更新、删除都有级联操作,那么使用cascade="all"
cascade="delete-orphan"  orphan意为“孤儿”
所以,当删除Order中的Item时,如果想数据库同步,那么使用cascade="delete-orphan"

那么,如果我这些操作都想要:save-update、delete、delete-orphan,那么该写什么?
cascade="all-delete-orphan"表示既有“all”+“delete-orphan”

总结:
cascade属性的可选值共6个:
1.save-update
2.delete
3.delete-orphan
4.all
5.all-delete-orphan
6.none     默认为none,不做级联操作 
fetch属性取值为:
  fetch="select"   默认,Sql分开写,用于延迟加载
  fetch="subselect"   子查询方式
  fetch="join"    连接方式
15.many-to-many关联映射  
1)  many-to-many基本概念
基础:
t_student(t_id,...);
t_couse(t_id,...);
t_student_couse(t_id, t_student_id,t_couse_id)
需求:
操作Student的时候一般都需要操作Course
操作Course的时候一般都需要操作Student
2)  基本配置:
class Student {
    private Set<Course> courses;
}
Student.hbm.xml:
... ...
<set name="courses" table="t_student_course">
<key column="t_student_id"/>
<many-to-many class="Course" column="t_course_id"/>
</set> 
16.继承关系映射到多个表
1)  基础
class Product
class Book extends Product
class Computer extends Product

t_product (t_id...)

t_book (t_product_id....)
t_computer (t_product_id...)

2)  配置
<class name="Product"....>
   ....
   <joined-subclass name="" table="">
        <key column="t_product_id"/>
       <property ... /> 
   </joined-subclass>
</class>

3)  基本操作
  保存: Hibernate会根据具体的类型(Book或者Computer)
       来确定插入哪个子表,父表一定会插入
  删除:Hibernate会根据具体的类型(Book或者Computer)
       来确定删除哪个子表,父表一定会删除
  查询子类 from Book:Hibernate会关联查询t_product和t_book
  查询父类 from Product:Hibernate会关联查询t_product和所有的子表
       返回的对象是具体的子类类型(可以强制转型)
  只查询父表数据,不关联子表
       select new Product(p.id, p.name, p.price) from Product p 
17.继承关系映射到1个表 
1)  基础
class Question
class ChoiceQuestion extends Question
class EssayQuestion extends Question

t_question(t_id,...t_type,......)

2)  配置
<class name="Question"....>
  
   <discriminator type="string" column="t_type"/>
   ....
   
   <subclass name="ChoiceQuestion" discriminator-value="c">
       <property ... /> 
   </subclass>
</class>

3)  操作
  存:   Hibernate会根据类型(哪一个子类)
来插入与之对应的区分器字段的值
  取:  Hibernate会根据区分器字段的值来选择
      用哪一个子类来封装并返回数据 
18.log4j.appender 
定义一个追加器,用于写明我们的日志要写入文件还是打印到控制台。
一般记录到文件中,使用org.apache.log4j.FileAppender
此处,我们为了方便查看,使用输出到控制台
  log4j.appender.abc
其中“abc”是自定义的追加器的名字,名字随便起
  log4j.appender.abc.layout  
用于定义日志输出的格式
org.apache.log4j.PatternLayout  
是“模式匹配的格式”的意思
  log4j.appender.abc.layout.ConversionPattern
在这里定义了”模式匹配的格式“
%d{yyyy-MM-dd hh:mm:ss} %5p %c{1}:%L - %m%n
这样的数据是定义好的“模式匹配”
%d  
表示日志输出的日期
{yyyy-MM-dd hh:mm:ss}
表示日期的格式
%p
表示日志的级别
%5p
表示这个日志的级别占5个空格,用于“对齐”
%c{1};
表示输出的是哪个类
%L 
表示行
%m
表示日志的信息
%n
表示“回车符号”
这么一大串,不用记,只要知道这是输出日志的格式即可。

log4j.rootLogger
表示
log4j.logger.com.tarena.tts = error , abc
log4j.logger
是固定写法
com.tarena.tts
表示这个包里的类
log4j.logger.com.tarena.tts = error , abc
表示在之前定义的名为abc的追加器上输出日志,只输出Error级别以上的日志,
如果在调试程序时,我们可以将之改为debug,abc,这样所有debug级别的日志就可以
看到了
log4j.rootLogger=debug, abc
表示默认的,其它所有没有定义的包中类的日志使用abc追加器,debug级别以上的日志。
所以,如果定义了log4j.rootLogger=debug, abc
那举log4j.logger.com.tarena.tts = error , abc可以省略为
log4j.logger.com.tarena.tts = error
log4j.logger.org.hibernate=info
表示Hibernate中的日志,info级别以上的都输出
在Hibernate中调用了log4j的API来记录日志,那么我们自己能不能调用?可以

如果想在程序运行时输出日志,有固定的写法
首先,为该类写一个静态私有成员
其次,在方法中调用,
Log4j记录日志的方法共有5种,从上向下严重程度依次增加,
Log4j定义了5种级别的日志,由用户自行选择在什么情况下调用何种级别的记录方式 
19.新建TestLog4j 
package com.tarena.tts.test;

import org.apache.log4j.Logger;
public class TestLog4J {
private static Logger logger = 
  Logger.getLogger(TestLog4J.class);

public static void main(String[] args) {
  //log4j记录日志的方式共5种
  logger.debug("debug message"); // 输出一条DEBUG级别日志  debug
  logger.info("info message");// 输出一条INFO级别日志      提示信息
  logger.warn("warn message");// 输出一条WARN级别日志      警告
  logger.error("error message");// 输出一条ERROR级别日志      错误
  logger.fatal("fatal message");// 输出一条FATAL级别日志     致命错误
}
}
举例:
如果在catch块中记录,最起码也记录一条ERROR级别的日志,
又如tomcat的启劢,提示tomcat正在启动的信息,那么可以使用INFO级别
连接数据库时,提示用户没有使用默认配置,那么可以使用WARN级别
Debug和INFO差不多,用于程序出错时进行调错,给程序员用的。  
20.one-to-many(List)

1)  基础
class Team{
    List<Person> persons;
}
class Person {}
 
t_team(t_id,...,)
 
t_person(t_id,...,t_team_id,t_turn,...)
 
2)  配置
 
<list name="persons" cascade="all">
  <key column="t_team_id"/>
  <list-index column="t_turn" base="0"/>
  <one-to-many class="Person"/>
</list>
 
3)  操作
同Set的one-to-many;但是,不要inverse="true",也就不需要反向关联 
21.Ant工具:
<list-index column="" base=""> column中填写的是决定person排老几的字段
base="0" 表示排序的起始计数,一般我们写0
basedir="."表示“相对路径相对于谁”,此处相对于当前路径

"*.jar"表示当前路径下的所有jar文件都引入
"**/*.jar"表示除当前路径下的所有Jar文件,当前路径下的子路径及孙子路径下的所有Jar文件都引入
todir表示拷贝到哪个目录;
dir表示要拷贝的文件所在目录
Include表示拷贝的文件包括src_dir目录下的所有“**/*.java”文件

encode表示源文件的编码形式
src表示源文件在哪里
desc表示目标文件放到哪
22. destdir="."    表示生成数据库表的sql文件放在当前目录
  <classpath refid="cp" /> 表示工作时使用的类路径
  configurationfile="${dir.classes}/hibernate.cfg.xml"  
用于指明Hibernate配置文件的位置
  <hbm2ddl >   表明根据映射文件生成表
  drop="true"    表示如果数据库中有同名表,先删除
  create="true"  表示创建新表
  console="true" 表示将生成的sql语句显示到控制台
  export="true"  表示生成sql的同时,就要用JDBC连接数据库进行建表,
如果为false表示只是生成该sql语句,并不会运行该sql
  outputfilename="schema.sql"  表示生成sql文件的名字
  delimiter=";"   表示建表之间的分号“;”
  format="true"  表示格式化显示这些sql 
23. HQL
  tst5()
不同的HQL语句返回的List结果集中的内容不同 
  from Emp 
返回 List<Emp>
  select emp.id from Emp emp
返回 List<Integer>
  select emp.id , emp.name from Emp emp
返回 List<Object[]>
  select new Emp(emp.id, emp.name) from Emp emp
返回的仍然是 List<Emp>,此时的Emp对象中只有id和name,并且不是持久态对象 
24. Hibernate 二级缓存
缓存的意义
缓存机制就是将数据库中常用的数据取出放入内存中,程序调用时直接从内存中取,不用每次使用
数据都访问数据库,这样提高了效率。

缓存需要关注的问题
1)  缓存的更新
缓存中的数据必须是同数据库中数据保持一致。
2)  缓存的命中率
提高缓存数据的利用率,缓存中存放的是用户常用的数据,如果缓存中存放的是用户不常用的,
那么就说缓存的命中率不高。
有些时候,是某些缓存数据在某个时刻使用率高,某个时刻使用率低,所以需要时刻更新,
以提高缓存命中率。
ehcache
缓存机制如果做好了,是比较困难的,有一些专门的组件就是用于专门解决缓存问题的。
比如ehcache

Hibernate的缓存机制
Hibernate提供了二级缓存机制。
首先,Hibernate中的一级缓存机制(也叫做事务内的缓存)是与Session绑定在一起的。
当一个Session开启,一级缓存创建;当一个Session关闭,一级缓存销毁。
若使用一级缓存机制(Session的缓存,每个用户线程对应一块Session缓存)
现在有5个用户(5个线程)访问Hibernate,
那么Hibernate会为5个用户创建5个不同的Session(一个线程分配一个Session)。
25.参数的含义分别是 
  maxElementInMemory 
表示该缓存中可以放如多少个对象,此处为10000个,根据内存的多少可以配置
eternal  
表示是否设置这些放入二级缓存的数据对象为永久的(即放入即保存,不再清除)
一般都为false
timeToIdleSeconds=120
表示如果120秒内,放入的对象没有被再次访问到,就清除出去
timeToLiveSeconds=120
表示对象在缓存中存活的时间,一个对象进入到本缓存中120秒后,就会自动被清除(一般
设置的时间会比timeToIdleSeconds时间长),设置此属性是为了让更多活跃的对象进入到
缓存中来。
overflowToDisk="true"
表示如果活跃对象已经超出maxElementInMemory设置的最大值时,超出的对象要被写入
到硬盘上保存下来,用于缓解活跃用户较多的情况。
26.region属性 表示指定使用哪个二级缓存 
usage属性 表示二级缓存的使用方式
有两种:read-only和read-write
read-only 如果值为read-only,那么就不能修改。
这样ehcache就不用考虑修改和更新的操作。
read-write 设置为read-write,ehcache还需要考虑更新和修改。
这样会降低效率。
所以,设置usage属性是很重要的,需要根据实际情况判断存入的对象使用二级缓存的方式。
27.使用二级缓存的步骤 
1)  导入Jar包
2)  Hibernate配置文件中指明使用二级缓存ehcache
3)  配置ehcache配置文件,定制缓存策略,共需指定5个属性。
4)  选择哪个对象使用二级缓存机制
在该对象的配置文件*.hbm.xml中配置如何使用二级缓存机制:
首先,通过region属性指定要使用的二级缓存;
其次,通过usage属性指定使用二级缓存的方式 
28.Hibernate 查询缓存
和二级缓存不同的是,有时候我们需要取出的并不是Emp对象,比如
select emp.name from Emp emp;
该查询取出的数据结果不是Emp对象,而是字符串。

或者是数组
select emp.name , emp.salary from Emp emp;
二级缓存默认是不会将他们保存到二级缓存中的。
二级缓存只管保存对象,而不会管诸如取字符串、取数组之类的其它数据。

但是如果这条语句使用也很频繁,我们有需要将它保存到二级缓存中的需求,那该怎么做?
使用查询缓存。
通过配置查询缓存后,Hibernate会将这条语句及查询结果封装为一个对象保存,
当再有用户调用该语句时,就直接返回该对象中封装好的查询结果。 

查询缓存适用于查询结果数据量巨大,查询结果一般不轻易改变的查询。  
如果查询数据总改变,维护查询缓存的系统开销也会很大。
比如查询商品信息Product的语句就可以设置为查询缓存,因为商品信息一般不会改变。
0 0
原创粉丝点击