`
cpf1985
  • 浏览: 76816 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

hibernate总结

阅读更多



--hibernate.cfg.xml
 *该文件一般位于src下面,当然可以任意放
 *文件具体内容
 <hibernate-configuration>//根节点
  <session-factory>//熟悉session-factory这个词
   <property name="key">value</property>//属性
   <mapping resource="xxx/xxx/xxx"/>//映射
  </session-factory>
 </hibernate-configuration>
--xxx.hbm.xml
 *该文件一般和实体类放在一起,其中hbm是hibernate mapping的简写形式
 *文件具体内容
 <hibernate-mapping>
  <class name="类完整路径" table="表名">//映射表
   <id name="属性名" colunm="列名">//主键列
    <generator class="主键生成策略"/>
   <id>
   <property name="属性名" colunm="列名"/>//普通列
  </class>
 </hibernate-mapping>
--使用hbm2ddl工具导出数据表
 //首先加载hibernate.cfg.xml文件
 org.hibernate.cfg.Configuration cfg=new org.hibernate.cfg.Configuration().configure("如果改名的话,可以写在里面");//如果不使用 configure()方法的话不会从根找xml而是找propoties文件
 //然后通过该配置文件得到导出工具
 org.hibernate.tool.hbm2ddl.SchemaExport export=new org.hibernate.tool.hbm2ddl.SchemaExport(cfg);
 //调用工具的导出方法
 export.create(是否将生成的语句打印到控制台,是否执行导出);
--一次简单的保存数据过程
 org.hibernate.cfg.Configuration cfg=new org.hibernate.cfg.Configuration().configure();//首先加载hibernate.cfg.xml文件
 SessionFactory sessionFac=cfg.buildSessionFactory();//通过hibernate.cfg.xml配置文件得到 sessionFactory(管理和创建session的工厂),一个数据库对应一个sessionFactory
 Session session=null;
 try{
  session=sessionFac.openSession();//创建一个session,对connection的一层封装,该方法抛出HibernateException,hibernate中全是RuntimeException
  session.beginTransaction();//手动开启事务,该方法抛出HibernateException
  User user=new User();
  session.save(user);该方法抛出HibernateException
  session.getTransaction.commit()//手动提交事务,该方法抛出HibernateException
 }catch(Exception e){
  session.getTransaction.rollback();//发生异常,回滚事务,该方法抛出HibernateException
 }finally{
  if(session!=null){
   if(session.isOpen()){
    session.close();//关闭session,该方法抛出HibernateException
   }
  }
 }
--hibernate常用接口
 *可以使用的数据源
  JDBC,常用的配置方式就是默认使用JDBC的连接方式的,该方式支持本地事务,不支持分布式事务
  JNDI, Java Name And Directory Interface(java名称和目录接口),每一个字符串对应到一个对象,相当于注册表一样,可以管理对象,以目录层次的方式管理注册上来的对象,解耦嘛,通过名称就能获取对象,而不不要知道具体对象的实现细节。可以通访问JNDI数据源的方式获取连接池中的连接
  JTA,Java Transaction Api,java 事务AIP,分布式事务的支持,因为hibernate.cfg.xml可以配置多个session-factory
   用容器的方式管理分布式数据库的事务,使用两阶段提交协议来实现分布式事务的原子性
 *无侵入性的接口
  Configuration,用于读取hibernate.cfg.xml配置文件
  SessionFactory, 每一个SessionFactory对应一个数据库实例,它是重量级的,创建很耗时,所以一般一个程序只需要创建一份实例就行了,由于推荐只有一份实例,所以他是线程安全的,由于他对应的整个数据库,所以它中间管理的缓存能被多个session实例共享,就是说它维护二级缓存
  Session,一般每一次请求对应一个session,它是通过SessionFactory创建的,非线程安全,所以注意同步,自身管理它的事务,自身管理他的connection,并和一级缓存息息相关,用一套很复杂的机制管理对象的状态
   只有用的时候它才从连接池获取connection对象,而不是open之后就打开连接
   虽然我们配置的JDBC方式,但hibernate内部默认实现了连接池的,所以不会有多大的性能问题,就用常规的配置方法就够了
   session用完之后必须关闭
  Transaction,用于管理事务的接口,和session接口密切相关
  Query,hibernate中实现查询的接口,支持普通SQL语句和HQL(hibernate query language)语句(HQL也是adapter设计模式的运用,首先这套语法操作对象,适合于任何数据库,就是对sql语句功能的扩展的适配而已)
  UserType,扩展转换器使用的接口,和struts的Convert机制一致
  Interseptor,拦截器,类似于servletAPI中的Filter,能拦截到对象修改的事件,比如保存之前,修改之后的事件拦截(简单的说就是状态改变的事件会被拦截,可以再三种状态的转换过程中处理一些事情)
 *有侵入性的接口(不建议使用)
  Lifecycle,和拦截器差不多,比如保存之前,修改之后之类的事件拦截(简单的说就是状态改变的事件会被拦截,可以再三种状态的转换过程中处理一些事情)
  Validatable,类似于struts中actionForm中的validate,没多大意义,因为一般情况下都是只有安全的数据才能进入持久层,没必要再验证了嘛
  
--持久对象的生命周期(Persistent Object)
 在session的管理下对象的三种状态
  瞬时(Transient Object) 没有被session管理,并且数据库中没有对应的记录(对应是通过主键来判断的)
   刚new出来的对象,被session.delete()的持久化对象
  持久(Persistent Object) 被session管理,并且数据库中有对应的记录,管理意味着对象的引用地址被session持有,简单说就是引用地址放在session中维护的缓存里面了,当然不会被GC回收
   从数据库中get(),load(),find(),iterator().list()出来的对象,因为都是从数据库中本生就存在的记录读取出来,当然是持久的了
   当瞬时对象调用,save(),或者saveOrUpdate(),没有则插入,有则修改这些方法之后,成为持久对象
   当离线对象,调用它的update,saveOrUpdate,方法后成为持久对象
  离线(Detathed Object)  没有被session管理,但是数据库中有对应的记录
   当持久对象被evict(),或者session.close(),session.clear()之后,持久状态变为离线状态,因为session已经关闭,或者session的缓存已经不存在了
 注意三者之间会智能的转换,没有绝对的定义
  比如刚new出来的对象,但是通过ID可以判断他在数据库中有与之对应的记录,所以可以直接调用他的delete方法,在调用的时候实际上它已经是持久对象了
  刚new出来的对象,可以直接调用他的saveOrUpdate,或者update方法,如果在数据库有对应的记录,实际上将它看成离线状态了
  很简单,最重要的是是否有与之对应的记录,是否被session管理他会自动判断
 
--请写一个单例
 public class Singleton {
  private static Singleton singleton=null;
  private Singleton(){}
  public synchronized static Singleton getInstance(){
   if(singleton==null){
    return new Singleton();
   }else{
    return singleton;
   }
  }
 }
 
--JUnit
 *单元测试工具,有了他就可以以方法作为单元进行测试,而不需要每次都依赖用main()方式测试
 *首先需要继承junit.frameword.TestCase类,方法名以"test"开头,必须为public void,方法不能有参数
 *assertEquals(),断言方法实现测试,setUp()在类初始化的时候执行,tearDown()方法在类消亡之前调用
 
--session接口的基本用法和该接口实现类生命周期
 *通常情况下让session和事务的生命周期一致是比较好的方式
 *实例分析一
  User user=new User();
  session1.save(user);//生成主键ID,如果是需要数据库生成则发语句,否则不发语句,并将user纳入session管理,放入insert这个map结构中(成为persistent状态)
  user.setName("xxx");//将状态放入session的数据快照中
  //实际上这里也可以显示的调用session.update(user),但没有任何作用
  session1.getTranaction().commit();//检查脏数据,从insert机构中构造出insert语句,数据快照对比构造出update语句,并发出语句
  session1.close();
  user.setName("xxx")//user现在是离线状态,它的修改不修改session里面任何东西
  session2.update(user);//发出update语句,并将user再次纳入session管理
  session2.getTransaction().commit();
  session2.close();
 *实例分析二
  User user=session.get(User.class,"存在的ID");
   发出sql语句,并返回User对象的引用
  User user=session.load(User.class,"存在的ID");
   不发出sql语句,返回User的继承类(cglib帮忙实现的),注意JDK的动态代理需要目标对象实现接口,而cglib不需要,它是用生成继承类的方式实现代理的
   当第一次使用到该代理的getXXX()方法的时候,代理类会检查成员变量target是否为空,如果为空则发出sql语句,填充数据
  User user=session.get(User.class,"不存在的ID");
   发出sql语句,返回null
  User user=session.load(User.class,"不存在的ID");
   不发出sql语句,返回User的继承类(cglib帮忙实现的)
   当第一次使用到该代理的getXXX()方法的时候,代理类会检查成员变量target是否为空,如果为空则发出sql语句,如果从数据库中没有找到对象则抛出ObjectNotFoundException
   就算class上的lazy=false也会抛出该异常
  
--Query接口的简单使用
 *前面的get,load只能通过主键来取一条记录,如果需要取多条记录的话就需要使用Query接口了
 Query query=session.createQuery("HQL");//只能用HQL,session.createSQLQuery("支持普通sql")
 query.setFirstResult(2);//从第三条开始取,
 quety.setMaxResults(2);//取两条数据出来
 List list=query.list();//返回List集合
 
--hbm.xml中标签的常用基础属性
 <hibernate-mapping
   package="xxx.xxx"//定义全局的package,可以简化子标签
   auto-import="true/false"//默认为true,比如在HQL中能直接只用("from User"),就是该属性的作用,如果为false则必须写全类的路径(包名+类名)
   >
  <class
   name="对应的类名"
   table="生成的表的名称"
   discriminator-value="鉴别值"//和继承映射有关,增加的鉴别列的值是什么,使用在一棵继承树映射成一张表,常用在<sub-class>标签里面,不常用在<class>标签里面
   dynamic -update="true/false"//默认为false,如果设为true,那么只有修改的属性才生成update语句(比如我只setName (),它会生成只修改name的sql语句,但是这样不能复用SQL语句了),但是能节约网络流量,需要则中考虑
   dynamic-insert="true/false"//默认为false,和update一致,如果为true,那么空属性不会添加到insert语句中
   betch-size="缓存相关"//它指示每次查询多少条记录,性能优化的时候注意使用
   optimistic-lock=""悲观锁和乐观锁,后期补上,设置了这种锁模式,那么get或者load的时候,不需要指定锁模式,都能应用锁模式
   lazy="true/false"//延迟加载,默认支持lazy,基本上所有的lazy特性的都是true,只有property属性上的lazy默认为false
   >
   <id
    name="属性名"
    column="生成的列名"
    type ="string"使用自定义的数据转换实现方式,,假定该属性类型为String,那么默认转换是(string------> varchar),但是只要type="long"则(string----->long),一般使用默认转换机制就行了
    lentgh="长度",默认为255
   >
    <generator class="生成策略">//常用的生成策略有
            1.increment:依赖本机java虚拟机生成标识,但是只保证自己的机器上不重复
            2.identity:依赖数据库的生成策略
            3.sequence:orical的序列主键生成方式
            4.uuid:hibernate生成的32为字符串
            5.native:根据数据库只能选择是identity还是sequence等等
            6.assigned:程序员手动分配,字符串类型(varchar)
            7.foregin:一对一主键关联的时候用,到时候补充上,就是用其他表的主键的值
            其中uuid的生成效率最高,但是字符串的查找比数字慢,没什么性能上的权衡
     
    </generator>
   </id>
   <property
    name="属性名"
    column="自定义的列名"
    type="xxx"//修改默认的转换机制,一般都是用默认值,和ID的type属性一样
    update="true/false"//默认为true,是否出现在update语句中,如果设成true了,那么相当于只读属性了
    insert="true/false"//默认为true,是否出现在insert语句中,如果设成true那该属性永远都是null,或者初始化的值
    lazy="true/false"//晚加载,使用类加强工具来实现lazy特性,一般用在数据量比较大的列上,默认为false
    unique="true/false"//是否唯一,就是指定它是否自动添加唯一键约束
    not-null="true/false"//是否为非空
    length="长度"//修改默认生成长度
   />
  </class>
 </hibernate-mapping>
 
 
--实体类的设计原则
 *必须要有一个无参的构造方法,因为hibernate会用到
 *使用非final,如果为final那么cglib怎么继承它呐?
 *提供get,set方法,因为hibernate对调用它。
 
--多对一关联映射
 *用户的组的关系就是多对一
 *从多方能够找到一方的引用
  <many-to-one name="group" column="groupid"/>在本身的一方增加"groupid"外键列,引用Group表的主键
 *保存
  一的一方必须要是持久状态才能在多的一方持有一的一方的引用,注意这个持久状态是可以手动构造出来的
  可以使用casecode来级联save,那么在持久化多方之前会先持久化一的一方
 *删除多方的一条记录的时候如果有cascade="delete",会将一方的数据一同删掉,会出问题
 *读取
  <many-to-one>的lazy默认为true,注意理解该lazy是作用于user中的group的
   lazy为默认值true的时候
    get(user)立即发送sql语句返回的user对象,当不调用user.getGroup()的时候,不会发出查询group的语句,只有用到group的时候才发出
    load(user)返回user代理对象,当调用user的普通属性的时候发出查询普通属性的sql,但也不会发出查询group的语句,只有用到group的时候才发出
   lazy为false的时候
    get(user)立即发出查询普通属性和group的所有语句
    load(user)什么都不发出,但是一旦访问任意普通属性,都将发送查找group的语句
--一对一关联(主键,单向)
 *将数据拆分成两张表一般运用一对一关联映射
 *人和省份证就是一对一
 *也是一样:一方需要持有另一方的引用,
 *原理:其中一个表的主键又作为外键参照另一个表的主键,主键生成策略为foreign,然后指定关联的引用对象是什么<one-to-one>,意思就是用什么来维持这样一对一的关系
   比如用户持有省份证对象的引用
   第一步:配置生成策略
   <id name="id">
    <generator class="foreign">//表示本ID依赖另一张表的逐渐
     //主键来源于哪里呢?
     <param name="property">idCard(表示主键来源于那个对象的主键)</param>//其中"property"表示属性
     //整个的意思这样理解,本表的主键来源于idCard对象的主键
    </generator>
   </id>
   第二步:配置关联关系
   <one-to-one name="idCard" constrained="true"/>//这样才能完成的加上外键约束,就是用什么来加载它的关联对象,如果不配的话,那表的完整性就不够了
    <one-to-one>默认使用主键加载
 *<one-to-one>默认cascade="all",意思就是说不设定cascade并且没有先保存另一个对象,也不会发生TransientObjectException异常
--一对一关联(主键,双向)
 *其中一对一主键单向关联已经将所有的关系建立好了,
 作为双向只需要在另一端持有引用,然后指示hibernate怎样加载就行了。
 所以在另一方只需要加上
 <one-to-one name="维护关系的对象"/>当然不能再有constrained="true",参照关系已经完全建立起来了,不需要再关系上再考虑,只修改对象模型,关系模型不需要任何修改
 一对一的逐渐关联的另一方的<one-to-one>的抓取策略为join,那本生的一端的抓取策略是什么呐?还是select没有任何影响,
 *<one-to-one>中没有inverse属性,所以还是规规矩矩的从维护主逻辑的那边插入数据咯
--一对一关联(外键,单向)
 *多出一个外键列来维持关系
 *思路,使用<many-to-one>标签增加外键参照对方的主键,但让该外键列不能重复
  <many-to-one name="关联属性" column="添加的外键列名" unique="true">
--一对一关联(外键,双向)
 *在单向的基础之上,关系模型没有任何变化,对象模型增加一个引用
 *然后在另一方使用<one-to-one>但是他默认指向对方的主键,怎么办呢<one-to-one name="xxx" property-ref="对方生成的外键列"/>OK!
  这种形式,从另外一端加载也是默认采用的join抓取策略,注意了这样的共性,如果<one-to-one>使用在非主逻辑的一方,它的抓取策略都是join
 *<many-to-one>和<one-to-one>都没有inverse属性,所以不要想利用这种机制,只有集合上有inverse属性

--cascade只对存储有作用,对加载无任何效果

--session.flush()
 *在事务提交之前默认实行,或者显式的调用session.flush()方法
 *作用:数据检查,执行SQL(但是还没有提交)
 代码分析一(借助hibernate的主键生成策略)
  User user=new User();
  session.save(user);
  //由于是hibernate自身的主键生成策略,不会发出SQL语句,此时将user纳入session的缓存中,并且该缓存中的existInDatabase=false,因为没有发sql语句嘛,insert队列中加入该对象
  session.flush();
  //数据检查,清理缓存,发现insert队列中有数据,构造出insert语句,发出语句后清空该队列,并且将缓存中existInDatabase=true
  //此时只是发出SQL语句,但是事务没有提交,视数据库隔离级别可能在数据库中能看到该数据
  session.getTransaction().commit();
  //正式提交事务,事务一旦提交无法回滚
 代码分析二(借助数据库的主键生成策略)
  User user=new User();
  session.save(user);
  //由于依赖于数据库生成策略,发出sql语句,相当于hibernate实现的主键生成策略的save并加上flush方法;
  session.flush();
  //检查数据,检查insert等队列什么也没有,也就是任何操作都不会做
  session.getTransaction().commit();
  //事务提交
 代码分析三(借助hibernate的主键生成策略)
  User user=new User();
  session.save(user);
  session.evict(user);
  //将user从缓存中清除但是insert集合中有数据
  session.flush();
  //检查数据发现insert集合中有数据,形成insert语句,清除insert集合,并试图将缓存中的existInDatabase=true,但是这时该缓存已经不存在,所以抛出“线程不安全异常”
  session.getTransaction().commit();
 那么怎样解决分析三中的问题呢?看代码分析四的解决方案
 代码分析四(解决三种的问题)
  User user=new User();
  session.save(user);
  session.flush();
  //执行了flush之后insert临时集合数据没有,下面再调用commit的时候当然不会试图在去修改existInDatabase了,当然不会出现异常咯
  session.evict(user);
 代码分析五
  User user=new User();
  session.save(user);
  user.setName("xxx");
  User user2=new User();
  session.save(user2);
  //发出的语句顺序是insert,insert,update,可以和意图不一致,那么在update的时候显示的调用一下flush就行了,注意这个小细节
--数据库隔离级别

 隔离级别    是否存在脏读  是否存在不可重复读  是否存在幻读
 -----------------------------------------------------------------------------------------
 read uncommited  YES    YES     YES
 -----------------------------------------------------------------------------------------
 read commited  NO    YES     YES    Oracle默认
 -----------------------------------------------------------------------------------------
 repeatable read  NO    NO     YES    MySql默认
 -----------------------------------------------------------------------------------------
 synchronized read  NO    NO     NO
 -----------------------------------------------------------------------------------------
 
 名词解释
  *脏读,事务还没有提交就能读出来数据,这一动作叫脏读
  *不可重复读,比如本次读出的姓名为张三,刷新后变成李四,由于查询出来的数据没有被锁定,这种情况其他用户可以修改数据,也就出现了脏读的现象,一般发生于被修改
  *幻读,比如本次读出5条记录,刷新后为10条,感觉是幻觉一样,一般存在于数据的增加或删除
 
--一对多单向
 *注意<one-to-many>这个标签是嵌入到set或者bag标签中的,
 *关系模型和多对一是一样,只不过我要从一的一方去加载多的一个集合
 *set和bag分别使用set和list两种保存数据的结构区别而已,bag有序可重复,set无序不能重复
 *在对象模型中要用Set或者List接口类型,而不能用HashSet,或ArrayList之类的,因为hibernate也对集合实现了延迟加载,它生成的代理的原理是实现List和Set接口,从而扩展功能,如果你使用HashSet它怎样去实现了,特别注意了
 *标签解释
  <set name="students">
   <key column="classid"></key>//在多的一方加入classid列,作为外键指向本对象的主键,但是"students"的类型是set,我怎么知道多的一方是什么呐,通过下面的<one-to-many>标签表示
   <one-to-many class="xxx.Student"/>//指定多的一方的类型
  </set>
 *保存
  *同样的班级的学生的问题
  *set的lazy默认为true,不管是get还是load如果不使用到集合中的数据则不会发出语句
  *同样注意TransientObjectException问题,所以先存学生再存班级才正确,但是存学生的时候还不知道只那个班的,所以学生中指向班级的外键列为空,
   当存班级的时候会update该外键列,因为是班级这方维护关系的嘛(这是hibernate的实现机制),这样会造成一定的问题,和数据库操作过多,怎样解决呢?
   上面的问题就算是在set上设置了cascade也不能防止udate语句的发生啊,它只不过自动先给调用了save学生的方法而已嘛
   所以在一的一端来维护这样的关系不是很推荐,还是在多的一端来维护更好些
   使用了inverse=true,那么一的一方不管关系了,虽然不发update语句,但是关系无法维护啊,因为一的一端就是通过update来维护关系的,又怎么办呢?
   所以还是没有办法,只有从多的一方存,其实有一种方案可以解决的,结合cascade,但是没多大意义,这里就不深究了,就从多的一方来存吧
   所以一般inverse不是用在一对多双向中,而是用在多对多双向里面的,在一对多双向里面注意使用cascade就够了,但是也可以用inverse,但是意义不大奥
   并且一的一方<set>加入inverse=true,多的一方<many-to-one>中加入cascode="all"
 *加载
 一对多双向的就是为了在多的一段维护关系才需要搞成双向的,注意他存在的意图

--标签优化对比
 ---------------------------------------------------------------------------------------
 标签    是否支持lazy  是否支持cascade  是否支持inverse
 ---------------------------------------------------------------------------------------
 <one-to-many>  NO    NO     NO 
 ---------------------------------------------------------------------------------------
 <class>    YES    NO     NO 
 ---------------------------------------------------------------------------------------
 <many-to-many>  NO    NO     NO 
 ---------------------------------------------------------------------------------------
 <many-to-one>  YES    YES     NO 
 ---------------------------------------------------------------------------------------
 <one-to-one>  YES    YES     NO 
 ---------------------------------------------------------------------------------------
 <set|bag>   YES    YES     YES 
 ---------------------------------------------------------------------------------------
 其中<one-to-many>和<many-to-many>都是<set>或<bag>的子标签
 inverse只能用在set或者bag标签中,只对存数据有关联,和删除,修改都无效果
---------------------------------------------------------------------------------------------------------------------------------------
 lazy特性详解
  *hibernate的lazy只有在session开启的情况下才有效,如果session关闭的时候,会发生代理不能初始化异常,这在web开发中很容易出现
   也可以说lazy的生命周期和session是一致的,一般可以让session开着,或者关闭lazy属性,让它返回本省的对象,不要让它返回代理对象
   get本省就返回真实对象,但不影响他属性(实体对象类型)的lazy特性,而load返回代理对象,当然也不影响他属性的lazy特性了
  四中标签上的lazy特性
   *<class>(默认打开,可选true/false)也就是对象的lazy:该对象在被加载上来的时候的lazy特性
   *<property>(默认为关闭,可选true/false)属性上,使用类加强工具来实现lazy,也就是说用到该属性的时候才执行SQL,用的很少,可以不用掌握
    这种情况就像是我在C#实现的lazy特性,数据量很大的属性可以设置该lazy特性,设置为true之后,那么该属性只有用到的时候才从数据库查
   *<set>(默认打开,可选true/false/extra)也就是集合的lazy:该集合在被加载上来的时候的lazy特性
    集合的lazy也是用代理实现的,它的实现和类不一样,它是通过实现set等集合接口来加强它的功能,这是hibernate的扩展,而不是cglib干的了
    extra:智能,比如调用集合.size()只会发select count(*),而如果是true的话会将所有的集合元素都查询上来
    所以推荐使用extra
   *单端关联(默认proxy,可选false/proxy(使用代理实现lazy)/noproxy(使用类增强工具实现lazy))
    什么是单端关联,就是对方是one(one-to-one,many-to-one),因为”多端关联“标签就使用set上的lazy特性,再给这样的属性的话就多余了嘛
    默认的proxy和集合是完全一样的
    但是noproxy为什么会出现呢?只是另一种实现机制而已
--多对多单向
 *通常使用中间表来维护关系
 *中间表为复合主键
 *标签
  <set name="set集合属性名" table="要生成的第三方表名">
   <key column="在第三方表生生成的列,作为外键参照本表的主键"/>
   <many-to-many class="对方的类">
    <column name="该对方的类在第三方表生成的列,作为外键惨遭对方表的主键"/>
   </many-to-many>
  </set>
--多对多双向
 *inverse用在多对多的双向中才有它的意义,能不免不必要的多余的sql语句
 *很简单,理解了单向就理解了双向
--自连接映射(很简单)
 <class name="Node">
  <id name="id">
   <generator class="identity"/>
  </id>
  <set name="children">
   <key column="parentId"/>
   <one-to-many class="Node"/>
  </set>
  <many-to-one name="parent" column="parentId"/>
 </class>
--基础映射总结
 *一对多中对一中set中元素的删除,只会update多的东西,不会删除
 *多对多中对一方set中元素的删除,会删除中间表的数据,
--继承映射(一颗继承树映射成一张表)
 关系模型,整个整成一张表,表里有一个列用来鉴别是那种类型
 映射配置
  <class name="Animal(父类)" table="t_Animal">
  <id name="id">
   <generator class="identity"/>
  </id>
  <discriminator column="type(加入的鉴别列名)" type="string(鉴别值的类型,是varchar还是int等)"/>
  <property name="name"/>//父类的属性
  <subclass name="Bird(子类)" discriminator-value="B(鉴别值是什么)">
   <property name="weight(子类中加入的属性)"></property>
  </subclass>
  <subclass name="Pig(子类)" discriminator-value="P(鉴别值是什么)">
   <property name="hight(子类中加入的属性)"></property>
  </subclass>
 </class>
 *Animal al=load(Animal.class,1);
 注意该al的instanceof是生成的代理类型,而代理又是Animal的子类
 但是使用get(或者load方式,但是class上lazy设为false)就能返回具体的类型,也就是说是Pig啊还是Bird啊,也就是说它支持多态查询,也就是说虽然你查的是Animal.Class,但是他因该返回实际的子类类型
 使用HQL返回的LIST中式具体的类型,注意不会返回代理,如"from Animal".list()返回具体的类型,所以可以通过"from java.lang.Object".list()返回数据库的所有对象
 
 ********************list()不会返回对象代理,而iterator()会返回对象的代理********************
 
--继承映射(每个类存为不同的表,包括父类)
 *Animal,Pig,Bird分别才能三张表
 *关系模型,每个子类表有一个PID作为外键参照Animal的主键OK
 <class name="Animal(父类)" table="t_Animal">
  <id name="id">
   <generator class="identity"/>
  </id>
  <property name="name"/>
  <joined-subclass name="Pig" table="t_pig">
   <key column="pid(增加该列作为外键参照Animal的主键)"/>
   <property name="weight(子类增加的属性)"/>
  </joined-subclass>
  <joined-subclass name="Bird" table="t_bird">
   <key column="bid(增加该列作为外键参照Animal的主键)"/>
   <property name="height(子类增加的属性)"/>
  </joined-subclass>
 </class>
 *这种方式的多态查询和第一种一致
--继承映射(每个子类存为不同的表,不存父类)
 关系模型,每个子类该有什么字段就有什么字段
 配置方式,注意第一不要生成父类,让他为抽象,第二不能使用自增
 <class name="Animal(父类)" abstract="true"><!-- (让父类不生成表) -->
  <id name="id">
   <generator class="assigned,或者uuid"/><!-- 不能在使用identity,因为每个子类都为单独的表,自增特性会加到子类,那么会重复,重复了又怎样加载呐 -->
  </id>
  <property name="name"/>
  <union-subclass name="Pig" table="t_pig">
   <property name="hight(子类增加的属性)"/>
  </union-subclass>
  <union-subclass name="Bird" table="t_bird">
   <property name="weight(子类增加的属性)"/>
  </union-subclass>
 </class>
 和前两种的多态查询机制一致
--三种继承方式比较(综合考虑使用那种)
 第一种,效率会高,但是有冗余字段
 第二种,表多,效率低,但是关系很清晰
 第三种,则中,但是不是用自增主键生成策略
 
--组合映射
 对象模型中,不是关联关系,而是聚集关系,也就是说没有OID对象为组件的部分
 对象模型,组件对象,和需要使用的组建对象
 映射方式,很简单,使用<component>标签搞定
 <class name="Person">
  <id name="id">
   <generator class="identity"/>
  </id>
  <property name="name"/>
  <component name="body(组件类型)">
   <property name="手"/>
   <property name="脚"/>
  </component>
 </class>
 关系模型:会生成一张表,在对象模型里面能将组件取出来单独使用
 在存的时候,不需要先save组件,也不能save组建,因为他不是pojo实体,没有oid,什么是实体(java类加上映射文件就是实体)
--联合主键映射,可以看成组建映射的特例,主键类可以复用
 对象模型
  将主键属性单独封装到一个类中,该类实现序列化接口,并且需要给予联合主键的属性生成复写equals,和hashcode方法
  需要实现联合主键的类持有该类引用
 关系模型,生成一张表
 映射配置,很简单
 <class name="Person">
  <composite-id name="持有的联合主键类的名称">
    <key-property name="联合主键类的属性一"/>
    <key-property name="联合主键类的属性二"/>
  </composite-id>
 </class>
 
--普通集合的映射(很简单)
 不再是关联映射,也就是说对象模型只有一个对象,集合是对象的一个子集而已,没有对应的对象模型
 关系模型,每个集合会单独创建一张表来保存数据,之间用主外键的形式关联
 <set name="setValue" table="set_value(生成一张表来存集合数据)">
  <key column="set_id"/>
  <element type="string(集合里的对象存为什么类型)" column="值放在setValue的那个列中"/>
 </set>
 <list name="listValue" table="list_value"><!-- 和数组(<array>标签)用法一致 -->
  <key column="list_id"/>
  <list-index column="lisi_index(存索引的列名)"></list-index>
  <element type="string(集合里的对象存为什么类型)" column="值放在setValue的那个列中"/>
 </list>
 <map name="mapValue" table="map_value(生成一张表来存集合数据))">
  <key column="map_id"/>
  <map-key type="string" column="key存在什么列中"></map-key>
  <element type="string(集合里的对象存为什么类型)" column="value存在什么列中"/>
 </map>
 
--悲观锁
 在read commited数据库隔离级别下面实现悲观锁,就等同于将数据库隔离级别升级到repeatable read,因为我不让其他人改,当然就实现了可重复读咯
 悲观锁,锁定的输入其他任何用户都不能"查看",更不能修改,注意查看都是不允许的哦,由于这种特性,最好用在短事务,而不要使用长事务
 hibernate对悲观锁的支持,当查询出来之后锁定,具体怎么样支持的呢?
  load(xxx.class,1001,LockMode.UPGRADE);生成select ..... for update;行级别锁,OK!
  注意使用了锁模式,load的lazy失效,马上发sql语句,并且返回实际的对象,而不是代理
  其他用户会阻塞,等待第一个用户commit或者rollback事务之后才继续执行
--乐观锁
 锁定之后其他用户能查看也能修改,那它又是怎么解决更新丢失问题的呢?
 严格意义上它不是一种锁,只不过是一种更新检测手段而已
 原理,数据库中加入版本等级列,来记录每条数据的版本,更新一次版本加一,当试图在读取出来的旧版本上更新数据时,无法更新,只有版本和数据库版本一致才能更新
 那么hibernate的实现细节是什么呢?
  在配置文件中加入<version name="生成的记录版本号的列名"/>
  ok,其他什么都不用做,也不需要在加载数据的时候给锁模式,因为他本生就不是锁嘛,在更新的时候hibernate会自动判断
 由于乐观锁并发性比较高,所以一般都是用乐观锁
 
--HQL(hibernate还有对象化查询,但是不成熟,所以可以不用掌握)
 它和任何数据库都没有关系,它会通过方言来翻译成对应的数据库的sql语言
 HQL中用"."导航类的属性,如"from User user where user.group.name like '%xxx'"
 在HQL中关键字不区分大小写,但类名,属性名需要区分
 HQL也可以使用别名,和SQL一样,可以使用直接给别名,也可以使用"as"关键字
 HQL可以使用数据库的特定函数,但是这样移植性就会有问题
*简单属性查询(不能忽略select)
 查询单一属性的话,返回该属性类型的list集合,查询多个属性的话,返回object数据,没什么,如果是我来实现hibernate也会这样做
 但是如果我想将查询某些属性,但是我需要以类型集合方式返回list怎么办呢?呵呵,hibernate当然想到了这些,使用下面的HQL:
  "select new User(id,name) from User",前提条件User要有User(int id,String name)这样的构造函数
*实体对象查询(可以忽略select)
 使用select 查询实体对象必须采用别名如:"select user from User as user",而没有*之类的概念,只有函数里面才能使用"*"
 Query.list()返回List接口,并将所有的数据放入List集合中,当调用该方法时,发出查询所有数据的SQL语句
 Query.iterator()返回Iterator迭代接口,当调用该方法时只返回id的集合,并不是所有的数据,而调用该迭代器去取得其中元素的时候,首先去session(一级缓存中找),如果没有找到则发出查询该条记录的SQL语句,如果找到直接使用
  那么这可能会出现多次发出sql语句的问题,这就是N+1问题,注意避免
 如果首先执行了list将数据放入session中那个缓存集合里面,再使用迭代查询就不会出现N+1问题了,因为他在session的缓存中能找到记录,(但是获取迭代接口的时候也会发查询id的语句)
 结论
  迭代接口使用缓存,但是每次都会发送查询id的语句
  而(默认情况下)list接口不适用缓存,每次他都会发sql语句,它只往缓存写数据,而不从缓存中用数据
   那么在那种情况下才使用缓存呢?后面再说,配合查询缓存就不会发了,前提是第二次使用list它能从缓存里通过ID找到缓存的对象
*条件查询
 可以用参数占位传参方式:
 Query query=session.createQuery("from User user where user.name like ?");
 List list=query.setParameter(0,"%张").list();注意从0开始索引,而JDBC是从1开始索引的,并注意方法链的支持
 可以用参数命名传参方式:
 Query query=session.createQuery("from User user where user.name like :name");
 List list=query.setParameter("name","%张").list();注意从0开始索引,而JDBC是从1开始索引的,并注意方法链的支持
 可以用参数命名集合传参方式
 Query query=session.createQuery("from User user where user.name in (:names)");
 List list=query.setParameterList("names",new Object{"张三","李四"}).list();
*原生的SQL语句(使用createSQLQuery)
 Query query=session.createSQLQuery("select * from t_user");不能在给类名了,
 query.list()返回object数组
 所以在批量更新的时候就最好使用原生sql
*外置命名查询
 就是把HQL放在配置文件里面
 在hbm.xml中的<hibernate-mapping>下,而不是<class>下,因为他不属于某个实体嘛,加上
 <query name="searchStudent">
  <![CDATA[
   from Student(这里写的语句和xml本生的符号不冲突)
  ]]>
 </query>
 读取方式
 Query query=session.getNamedRuery("searchStudent");
*查询过滤器
 原理,面向切面的编程
 配置
 <hibernate-mapping package="entity">
  <class name="Student">
   <id name="id">
    <generator class="identity"/>
   </id>
   <filter name="idFilter" condition="id &lt; :myid"/>
  </class>
  <filter-def name="idFilter">
   <filter-param name="myid" type="integer"/><!-- 使用hibernate的类型 -->
  </filter-def>
 </hibernate-mapping>
 如何启用
 session.enableFilter("idFilter").setParameter("id",10);只对当前设置了过滤器中session的HQL查询有效
 
--分页查询
 设置两个属性OK
 
--连接查询
 内连接 "select u.name,g.name from User u join u.Group g "不需要写on因为,映射文件里已经有这个关系了----->生成inner join on ...语句
 外连接 也是left join和right join,没什么

--统计查询
 Query query=session.createQuery("select count(*) from User");
 query.list().get(0)为long数据类型,看更好的方法
 query.uniqueResult()返回object,里面的类型为long---->返回首行首列,返回对象而不是list接口引用
 HQL中也能使用group by,和集合函数一起使用
 
--在HQL中可以使用DML语句
 DML(Data Manipulation Language)数据操作语言,就是修改,删除,插入
 DDL(Data Definition Language)数据定义语言
 在HQL中最好不要使用DML,比如数据已经被存入session的缓存中,然后执行了DML中的删除,HQL中的DML是不会和session缓存同步不,就会造成脏数据的问题
 为什么他不同步session的缓存呢?性能考虑,如果我批量修改很大的数据,同时修改数据库的同时在修改大量的缓存,这对性能的影响是非常恐怖的,基于这一个考虑,所有的基于ORM的框架都没有实现同步
 所以ORM框架都不擅长做聚集性操作,因为如果和缓存不同步,那就失去了框架的意义了
 
--缓存
 缓存和池的区别,首先他们都是为了提高效率而出现的技术
  缓存中放变化不大的对象才有意义
 *一级缓存
  一级缓存的生命周期是和session一致的,被session所管理,所以也叫session级的缓存,或者叫事务级的缓存
  get和load都会使用缓存,他们在取数据的时候首先回去session中找,如果找到了就不会再发出sql语句,没有找到发sql语句,并纳入session管理
  ----------迭代器问题----------------
  *迭代器查询对象,首先会发出查询id的sql语句,当对象用到的时候发查询对象的语句(前提,该对象lazy=true),如果class上的lazy=false,那么当调用iterator方法的时候会先把第一条数据加载上来放入session,
   然后第一次调用iterator.next()时候,发出查询第二条数据的sql,就这样先加载下一个然后在缓存取当前的
   也就是说迭代器每次都只发出查询一条数据的sql,并且如果缓存里有对象了,迭代器不会在发出sql语句,而是使用缓存,并且迭代出来的对象也会自动放入到缓存中去
  *迭代器查询普通属性,当调用iterator()方法的时候立即发出查询具体属性的sql(而不会先发出查ID的SQL),并且该对象是无法放在session里面的啊,因为他返回的不是一个实体,所以就算是查询同一个对象的属性它每次都会发sql
   也就是说迭代出来的普通属性,由于他不能放在session里面,所以他无法重用,也就是说不适用缓存
   也可以说session缓存只能放实体对象,而不会放其他的对象
  *不同的session不能共享一级缓存,因为分别维护不同的缓存队列嘛
  *大批量的插入也不适合使用hibernate因为他要将数据加到缓存里面,可能会照成内存溢出
   如果更要用hibernate最好的解决方案是,插入一点就flush()一次强制持久化,clear()一次清空缓存,这样相对不会照成缓存里数据溢出的问题,因为一级缓存没有失效期
  使用一级缓存的有
   get,load,iterator,注意list()只往一级缓存里写,但是他不用一级缓存,除非配合查询缓存
  一级缓存没有缓存策略,比如没有失效期等,是一种比较简单的缓存实现
 *二级缓存
  需要继承第三方缓存产品,并且也是只缓存实体对象
  二级缓存也叫进程级的缓存,也可以叫sessionFactory级的缓存
  配置和使用
   使用EHCache,本生提供了ehcache这个jar包,可以通过里面的xml配置文件配置相关的缓存策略,不过使用默认值就行了
   拷贝ehcache.xml文件到src,hibernate模版提供了的,在里面也可以配置缓存策略,可以精确到每一个类的缓存(是只策略的运用,而不是是否启用缓存),但是一般让其对所有实体类有效,使用默认值就行了
   启用二级缓存(在hibernate.cfg.xml加入属性)
    <property name="hibernate.cache.use_second_level_cache">true</property>
   指定缓存产品提供商(在hibernate.cfg.xml加入属性)这里使用ehcache
    <property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
   指定实体类使用二级缓存(在xxx.hbm.xml中加入属性)
    注意一般变化不快的实体类才使用二级缓存
    在class中加入子标签<cache usage="
    read-only-->当修改数据库中数据时候缓存中数据不会改变,但这比较常用,也不会出现很大问题,因为二级缓存有过期时间啊,所以不会存在很大的数据不一致问题
    read-write-->当修改数据库中数据的时候缓存中数据会随之改变
    "/>
    也可以在hibernate.cfg.xml中加入<class-cache class="xxx.xxx" usage="read-only"/>来指定那些类使用二级缓存,这样比较直观
   那些方法支持二级缓存呢?
    首先去一级缓存找,然后去二级缓存里找
    get(),load(),iterator()都是用二级缓存
   二级缓存的管理,使用sessionFactory对象管理
    sessionFactory.evict(User.class);//清除二级缓存队列里面所有的User实例
    sessionFactory.evict(User.class,1);//清楚二级缓存里面的特定的User实例
   控制session的CacheMode
    默认是CacheMode.NORMAL;//能存也能取
    session.setCacheMode(CacheMode.GET);//让session在加载的时候只取而不存
    session.setCacheMode(CacheMode.PUT);//让session在加载的时候只存而不取
 *查询缓存
  缓存普通结果,而不再局限于实体对象,并且能缓存实体中的ID
  一级缓存和session生命周期一致
  二级缓存和sessionFactory的生命周期一致
  查询缓存的生命周期是不一定的,当前关联的表发生修改,查询缓存的生命周期结束,它的生命周期不可控制
  配置和使用(查询缓存默认是关闭的)
   在hibernate.cfg.xml中修改属性<property name="hibernate.cache.use_query_cache">true</property>
   query.setCacheable(true);//开启查询缓存,从这里可以看出查询缓存是作用于query接口的方法中的,也就是和list方法和iterator有关联
    但是虽然是配置query上的,但是查询缓存对迭代接口不起作用,只对list方法有作用
  比如两个用query.list()来查询普通属性,如果开启了查询缓存则第二次不会发sql语句了
  而query.iterator,就算开启了查询缓存,每次都会发
  session和查询缓存没有关系,不同的session会共享一份查询缓存的
  
  ----严重的问题----
  开启查询缓存,调用两次list查询”实体对象“(注意是实体对象而不是普通属性)
  第一次将实体对象的ID,缓存
  第二次它从查询缓存找到了ID,它会用用该缓存,用ID去一二级缓存找有没有对应的东西,如果有则用,如果没有则发N条语句
  。。怎样解决这个问题呢?由于他会通过ID去找一二级缓存,所以如果一二级缓存里有对象则第二次list一条语句也不会发,所以并不是所有情况下list都会发语句的
  结论:查询缓存和迭代没有任何关系
   当用list查普通属性的时候,会被放入缓存
   当用list查对象的时候,会将对象的ID放入缓存
   
--抓取策略
 抓取什么,抓取一个类的关联的对象(所以class标签上时没有抓取策略的,因为他配置的对象怎样被加载),比如说<one-to-one>标签的轻量级一端(一对一双向,一对多双向)默认使用的join抓取策略
 抓取策略只是对get.load方法有影响,但是其中只有一种情况对HQL有影响
 *单端关联上的抓取策略(为什么没有多端关联的抓取策略呢,因为多端关联默认使用集合上配置的抓取策略)
  提供两个可配置的值(select和join默认为select)
  在get或者load方法中,如果抓取策略为join的时候那么关联对象的lazy特性失效
 *集合上的抓取策略
  提供三个可配置的值(select,join.subselect默认为select)
  集合也是当使用join的时候,所关联的集合对象的lazy也失效
  subselect(子查询抓取策略),这种抓取策略只对HQL查询有效,
   例子,班级和学生的抓取HQL"select class from Class class where class.id in (1,2,3)";
   默认情况下,首先发查询班级的sql,每次用到一个班级的学生时候发送这个班级对应的学生的sql,
   设置的subselect之后,首先发查询班级的sql,当用到某个班级的学生的时候将这三个半的学生全部查询出来,
--class或者set标签上的betch-size属性是干什么的,它指示每次查询多少条记录,如果没有设置的话每次查询一条(比较智能,可以不用考虑原理)
 
--批量更新和查询
 在hibernate上的配置需要依赖数据库是否支持,这种配置是指挥数据库的,对程序是透明的
 <property name="hibernate.jdbc.batch_size">50</property>50条为一个单位,更新
 <property name="hibernate.jdbc.fetch_size">20</property>20条为一个单位,查询,取得嘛
 
 mySql部分支持,oracle完全支持,sqlServer完全支持
 
--HQL中的fetch
 仅从使用的角度来看,预先抓取和立即检索的效果一样,只不过预先抓取可以减少sql语句的条数。 因为他预先将数据填充的缓存中,下次不需要在发出sql语句
 
--ThreadLocal已经和hibernate使用维护session的开启状态
 ThreadLocal会为每一个线程维护一个和该线程绑定的变量的副本,从而隔离了多个线程的数据,每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
 每次个线程在从ThreadLocal取得对象的时候,该变量和当前线程绑定,是一份对象的副本
 看看filter的实现
 public class HibernateUtil implements Filter {
 private static ThreadLocal<Session> threadLocal=new ThreadLocal<Session>();
 private static SessionFactory factory=null;
 public void destroy() {
 }
 public void doFilter(ServletRequest request, ServletResponse response,
   FilterChain chain) throws IOException, ServletException {
   try{
    chain.doFilter(request, response);//该方法是阻塞行的,在方法之前,截取请求可以做一定的事情,方式执行之后是所有将html发送回到客户端的时候才执行
    //所以在这个方法执行后,关闭session是最合适的,这样保证每次请求完成之后session被关闭
   }finally{//保证肯定能被关闭,和将TreadLocal清空
    Session session=threadLocal.get();
    if(session!=null){
     if(session.isOpen()){
      session.close();
      threadLocal.remove();
     }
    }
   }
 }
 public void init(FilterConfig arg0) throws ServletException {//tomcat启动会实例化过滤器,并执行该方法,并且整个生命周期只执行一次
  //由于sessionFactory是重量级的,所以只让它被创建一次,写在过滤器的init方法中式再好不过的了
  try {
   Configuration cfg=new Configuration().configure();
   factory=cfg.buildSessionFactory();
  } catch (Exception e) {
   e.printStackTrace();
   throw new ServletException(e);
  }
 }
 //对外提供获取session的方法,每个线程获取的session都和线程实现了绑定
 public static Session getSession(){
  Session session=threadLocal.get();
  if(session==null){
   session=factory.openSession();
   threadLocal.set(session);
  }
  return session;
 }
}


分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics