首页 > 技术文章 > hibernate学习

wanjn 2017-08-28 21:23 原文

2.hibernate的配置文件中如果确少实体类某个字段的配置,那取不到值为null,关于属性配置的type属性,指示将对应字段的值转换为type配置的类型,能转换成功就像,不能转换就报错。比如varchar类型的2017-08-30对应type都可以配置为Date类型,很多情况type都可以省略。但是建议还是规范将java字段类型和数据库类型对应起来比较好

4.XXX.hbm.xml文件就是描述数据的元数据文件

5.hibernate4.0之后sessionFactory的创建方式和之前发生了变化

6.hbm2ddl.auto配置信息值为update时,当xxx.hbm.xml中的字段映射发生变化时,数据库对应的表的原来字段不会改变,而是新增变化的字段

2.hibernate默认开启一级缓存(hibernate的一级缓存和mybatis的一级缓存区别还是很大的)

总结:

  事务是否提交不影响一级缓存,

  将数据放入一级缓存的方法有:调用session的get,load,update,save方法

 sessiion常见操作的特点:

    1)session.clear()和session.evict(obj)不会导致事务提交,只会导致对象被清理出一级缓存;

    2)session.close()也不会导致事务提交,只起到关闭session的作用而一级缓存是session级别的,session都关闭了,一级缓存当然也就不存在了;              

    3) session.flush()不会导致事务提交;但是事务提交会导致session.flush()。flush的作用是将实体内种和数据库对应的记录不同的地方更新到数据库中。

4.hql是面向对象的查询方式,所有查询的时候要用 类名+对象名而不是表名,是官方推荐查询方式。使用该方式的hql语句设置传递的参数的方式:

   1)使用?作为占位符,此时按照?的位置设置值,index从0开始

   2)使用:+名字,此时按照 名字(当做一个变量) 设置位置。

---------------------------------------------------------------------------------------分割线,上面的都是不可信的,我要重新做笔记了-----------------------------------------------------------------------------------

flush方法相关

1.数据是否持久化到数据库,只和事务(底层)是否提交有关。特别的flush不会导致事务的提交,也就是当实体对应和对应数据库表的记录数据不同,调用flush即使发送了update语句,但是实际的数据不会更新到数据库,因为这个时候事务还没有提交

2.调用hibernate封装的transaction的commit会导致flush。从代码上可以看到在commit之前先调用了flush方法再进行底层事务commit。但是真正将数据库写到数据库的操作还是commit完成的,flush只起到对比校验从而判断是否发送sql语句(相同不发送,不相同发送)

3.在未提交事务或显示调用session.flush()方法之前,也有可能进行flush()操作(被封装了)

  1)执行HQL或QBC查询时,会先flush()来保证该事务中得到数据库中最新的记录(虽然此时数据库的数据还是原来的数据因为事务未提交,但是HQL或QBC查询出来的记录已经是之前查询里改变的了)

  2)如果记录的id是由底层的数据库使用自增的方式生成的,则调用save()方法时,会立刻发送insert语句,因为save方法后必须保证对象的id是存在的。(如果id的生成方式是由hibernate生成的,那么在事务提交的时候才发送insert语句),所有才会出现这种现象:在不提交的事务的前提下,session调用save方法和update,delete方法的行为是不同的,save方法会发送insert 语句,update,delete方法不会发送update语句。

4.commit()和flush()方法的区别:flush发送一系列sql语句,但不提交事务,commit方法先调用flush方法,然后提交事务。只有事务提交数据才真正持久化到数据库中

reflush方法相关

1.reflush方法会强制且一定发送select语句,以保证session缓存中对象的状态和数据库中状态一致(比如在程序事务执行的过程中,另一个事务修改了查询出来的数据并提交了,这个时候reflush方法就会把其他事务提交的数据,同步到该事务中。但是这个也和数据库的隔离级别有关,比如mysql的隔离级别为可重复读,这个样的话即使调用reflush还是不会同步回来的,读已提交的隔离级别就会)

持久化对象状态相关

1.持久化的对象的id不能被修改。

2.save方法和persist的区别:persist方法之前如果实体对象被赋予id,那么调用persist方法将不会执行插入操作,而是抛出异常

3.1.hibernate默认开启事务,所有要使用hibernate,一切的操作都要显示的开启事务,以及显示提交事务中,才能将对数据的操作持久化到数据库中

4.hibernate默认开启延迟加载,延迟加载是通过代理的方式来实现的,因为这点会带来很多不同

  session 的get方法和load都是按照主键查询,不同点如下:

    1)get不是延迟加载,调用方法立即向数据库发出sql语句查询;load是延迟加载,只有当调用非主键的get方法时才向数据库发送sql语句查询数据(这点可以通过调试代码非常明显的看到)

    2)当get查询不存在的主键时返回null。load返回代理对象,且该代理对象内的主键属性为查询时的主键,即使数据库中不存在该主键的记录,只有当调用该代理对象的非主键的get方法才会报错(ObjectNotFoundException)

     因此,如果在开启延迟加载的前提下,使用load方法查询数据,这个时候就不能在dao层立刻关闭session,因为调用get方法才真正查询数据库,如果session关闭了就会报懒加载异常(页面调用get是在调用dao层之后),解决办法是使用OpenSessionInView模式

update 方法相关

4.如果要更新一个持久化对象,不需要显示的调用update方法,在事务提交的时候会调用flush方法,将修改持久化到数据库中;如果要更新一个游离对象要显示的调用session的update方法,同时将该游离对象变为持久化对象;update更新游离对象不管对象和数据库对象是不是一致,都会发送update语句,而更新持久化对象的时候,如果持久化对象和数据库对应的记录一致的话就不发送update语句,不一样才发。

  这样就带来一个问题,当显示调用update方法来更新和数据库记录相同的数据的时候盲目发出来的update语句可能会导致触发器的错误调用(如果业务上有的话),解决办法是更新前先查一下数据,将数据先变成持久化对象,这个查询不用自己手动写,只需要在XXX.hnb.xml文件的class元素上添加select-before-update=true属性即可。如果业务上没有触发器,一般不设置该属性,因为多一次查询就增加了开销

5.如果数据库中没有实体对应的记录,但是还对该实体进行update操作,会抛异常

6.当update方法关联一个游离对象时,如果在session缓存中已经存在相同oid的对象,会抛异常(org.hibernate.NonUniqueObjectException)。因为session中不能有俩个oid相同的对象

saveOrUpdate方法相关

1.该方法即包括save操作也包括update操作。判断是执行save方法还是执行update方法的条件时,如果操作的对象的oid为空,即游离对象的啥时候执行save操作,如果oid不为空则执行update操作,但是如果oid不为空,且数据库中没有对应记录的时候,就会抛异常 (另外有个特殊情况oid 的值等于unsave-value属性值的对象也被认为是游离对象,此时虽然oid不为空,但是执行save操作

delete方法相关

1.delete执行删除操作,只要实体的oid(不管这个对象是游离对象还是持久化对象)和数据库有对应的记录,就会准备删除该提交了(具体发送sql是在flus方法时,持久化到数据库是提交事务时)。如果oid在数据库中没有对应记录,则抛异常(org.hibernate.StaleStateException)

2.默认情况下,调用delete的对象的id不是为空的,如果要为空,则要设置hibernate的cfg.xml配置的hibernate.user_identified_rollback为true即可

调用存储过程相关

hibernate没有为调用存储过程编写接口,如果要调用存储过程还是需要得到底层的connection对象来调用,那从hibernate中获得底层connection的方法是:

session.doWork( new Work(){      public void execute( Connection connection){}}),在匿名方法里使用框架传递进来的connection对象来调用存储过程即可 

cfg.xml配置文件相关

hibernate.jdbc.fetch_size:设置100比较合理,比如要查询1万条记录,分批次查询每次查询出来的记录数就是该配置信息配置的数量,如果该值设置的越大,连接数据库的次数就越少,速度越快,但是对web服务器 的内存的压力就越大;反过来设置的越小,连接数据库的次数越多,速度越慢,对web服务器内存的压力越小

hibernate.jdbc.batch_size:设置为30比较合理批处理一次发送的sql的数量,数量越大,发送的sql次数越少,速度越快

hbm.xml配置文件相关

dynamic-update/insert:true。动态更新/插入。默认为false,每次更新或插入的时候,会更新所有字段的值,不管字段是不是和 数据库记录一致,设置为true时,在sql语句set后面只出现和数据库不一致的字段

主键生成策略相关(推荐代理主键,即不含有业务逻辑的字段)

Increment:表示代理主键由hibernate生成。原理是先去数据库查max (id)的值(所有可以看到使用这种方式的时候会先发送select max(id)的sql语句),在加1.该生成方式有线程安全问题,不能在正式环境中使用,只能用于测试

identity:由数据库负责生成。使用该生成方式要求:底层数据库要把该主键定义为自增长(数据库要支持自增长,比如oracle就不行)此外,oid要定义为long,int或者short类型

sequence:表示利用数据库提供的sequence来实现主键增长,比如oracle数据库

hilo:不依赖底层数据库,适用于任何数据库。没有并发问题

native:跨平台的,自己会去判断。一般用该生成方式即可

属性映射配置相关

update:false表示该字段不能被修改,即使在代码中显示的修改该字段,也不会将修改持久化到数据库中

unique:true。表示该字段的约束为唯一。这个适用于由hibernate自动生成数据库表时起作用

index:该字段是否添加索引,索引的名字为配置内容

length:表示映射字段的长度

formula ="(sql)".根据给定的sql语句来得到派生属性的值(在实体类中有属性,在数据库表中没有对应的字段,该属性的值是由其他已知属性的值通过规则得到的。)其中sql其实就是在一个内置的sql查询

日期类型映射

java 中的java.util.Date 类型包括 日期,时间,时间戳;但是标准sql中date就是日期,time就是时间,timestamp就是时间戳。为了将java类型和标准sql类型映射起来,java在java.util.Date类上扩展了对应的类即其子类,java.sql.Date  java.sql.Time  java.sql.Timestamp;  在hibernate中,使用util下的Date类型去映射标准sql中的三种类型。在hbm.xml中用type=hibernate类型date,time,timestamp来映射即可 

组合关系的元素映射

使用场景:数据库中一张表的多个字段,被映射为程序中的俩个实体类。这个时候,俩个实体类之间是组合关系在hbm.xml用需要用<component>元素来映射实现这种组合关系的映射

关联关系:

  学习关联关系,一定要将域模型和关系数据模型 映射起来;学习hibernate的映射时,要当做所有的表都是有hibernate根据映射文件生成的,而不是手动建表生成的,也就是说关系数据模型的建立是要通过相应的映射配置来完成的。比如主外键关联,唯一性约束等等

  1.单向多对1。只需要在多方配置即可

    插入的时候,如果先插入1方,再插入多方法,那么都是insert语句;如果先插入多方,在插入一方,会对应多出update语句。原因是,在插入多方后,多方的关联外键的值为空,只有当1方插入表后,才将插入后得到的id反过来更新到多方的关联外键上;查询多方带出的1方的对象是代理对象;多方映射的时候不用单独映射外键字段,直接和含有的1方的属性一起映射即可

  2.双向多对1.即多的一方是多对一关系,1的一方是1对多关系,这个时候需要在俩方都进行配置。1方集合要先初始化,防止发生空指针异常;查询关联对象时,是延迟加载的。返回的set是hibernate的集合类型(所以声明集合类型的时候要用接口类型,不能用具体的实现类),里面可以存放代理对象

    双休多对1关系默认是由俩边来维护的,这样会带来一些效率问题,即多发送update语句,比如先插入1方,在插入多方,会多出插入多方数量的update语句;如果插入多方,在插入1方,会多出插入多方数量的2倍的update语句。那如何来提高效率,关系交给一方维护:

    通过inverse=false一方来维护关系。inverse=false,表示不反转,即维护关系的权限不交给另一方。设置为true的时候,表示将维护关系交给对方。1对n时,关系有n方维护(inverse=false),1方放弃维护(inverse=true)。这样设置完后发生update的效果和单向多对1的效果一样了。同样建议先插入1的一端,再插入多的一端;此时set元素的table

属性表示set集合中存放的对象放在哪个表中,子元素key,表示存放的那张表的外键字段名

    一般情况下,在一方在多方有关联的情况下,删除1方的时候会报错。那如果有这种需求即删除1方的时候自动将关联1方的所有多方都删除,就需要在配置级联操作。设置set元素的属性cascade = “delete”即可,还有其他的比如级联保存,级联更新(开发的时候不建议使用,推荐使用手工方式,有助于放置数据的不一致性)

    order-by。可以通过set元素的该属性设置查询结果的排序,里面的值是表的字段名,可以使用sql函数

  3.1对1的的关系

    关系数据模型实现的方法有俩种,第一种是按照外键映射即一方添加一个外键实现关联关系,第二种是按照主键映射即一方的主键是按照外键来生成的,外键参考与另一方的主键。

    1)按照外键映射:

      一方添加many-to-one映射信息添加外键同时添加唯一约束,另一方用one-to-one实现映射同时加上。这个时候先插入没有外键的一方再插入有外键的一方只有insert语句,顺序反过来就会发送update语句,这个和1对多时是一致的原因也死一样的;查有外键的实体类信息,会通过延迟加载去得到另一方数据,但是查没有外键的一方的数据,会立即left outer join 出另一方的数据,默认情况下链表条件时错误的,需要在one-to-one的元素上添加 property-ref=“manager”(比如)来指明有外键一方用哪个字段来和我的主键链接

    2)按照主键映射:

      此时俩边都是one-to-one 映射。在需要用外键生成主键的一方配置主键生成策略:比如

<generator class="foreign" >
<param name="property">manager</param>
</generator>

然后可以通过在该配置文件的one-to-one 元素的constrained="true"属性,表示为主键添加外键约束。此时left outer join 链表的条件不会错,就是俩个主键的连接

  4.多对多关系映射,多对多关系映射一定要借助中间表,且通过many-tomany来进行配置

    1)单方向多对多关系,需要在一方配置many-to-many。在该配置信息中指明中间表名,以及该实体类的主键在中间表的外键字段名,以及另一方的在中间表的外键字段名

    2)双向的多对多关系,需要在俩方都配置many-to-many配置,且其中一些配置刚好相反。且需要在一方many-to-many配置上设置inverse="true",不然在保存具有双向关系的对象的时候会报错。

   5.继承关系映射可以有三种方法:

      1.subclass:特点父类和子类共用一张表,表里有个特殊的辨别者列(不用体现在实体类中,只需要体现在配置文件中),配置文件只需要映射父类即可不用映射子类。该值在配置的时候通过discriminator元素配置列信息,通过 class 元素的属性discriminator-value指定。子类独有的那一列不能有非空约束;多态查询 父类记录只需要一张表,查询子类也只需要一张数据表;如果继承层次较深,则表中字段就会很多

      2.joined-subclass:每个子类一张表(子类和父类分开表存储),子类实例信息由父类和子类表共同存储。不需要辨别者列,子类特有字段可以添加非空约束。子类表的主键是父类表主键的外键(只要配置了该配置元素,外键约束会自动生成);插入父类插入一张表,子类俩张,插入效率低点;查询的时候要连接表查多张表,效率低一点

      3.union-subclass:将父类和子类映射为独立的表,存放独立的数据。但是主键的生成方式不能为identity,用的话会报错;多态查询父类会把父类和子类的信息全部查出来(这和前面俩个是一样的),会用子查询,效率会差一点;插入效率还可以;若更新父表的字段,更新效率低。

 

  

  

 

推荐阅读