--hibernate.cfg.xml *该文件一般位于src下面,当然可以任意放 *文件具体内容 <hibernate-configuration> <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工具导出数据表 org.hibernate.cfg.Configuration cfg= new org.hibernate.cfg.Configuration().configure( "如果改名的话,可以写在里面" ); 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(); SessionFactory sessionFac=cfg.buildSessionFactory(); Session session= null ; try { session=sessionFac.openSession(); session.beginTransaction(); User user= new User(); session.save(user);该方法抛出HibernateException session.getTransaction.commit() } catch (Exception e){ session.getTransaction.rollback(); } finally { if (session!= null ){ if (session.isOpen()){ session.close(); } } } --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); user.setName( "xxx" ); session1.getTranaction().commit(); session1.close(); user.setName( "xxx" ) session2.update(user); 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" ); query.setFirstResult( 2 ); quety.setMaxResults( 2 ); List list=query.list(); --hbm.xml中标签的常用基础属性 <hibernate-mapping package = "xxx.xxx" auto- import = "true/false" > < class name= "对应的类名" table= "生成的表的名称" discriminator-value= "鉴别值" dynamic -update= "true/false" dynamic-insert= "true/false" betch-size= "缓存相关" optimistic-lock= "" 悲观锁和乐观锁,后期补上,设置了这种锁模式,那么get或者load的时候,不需要指定锁模式,都能应用锁模式 lazy= "true/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" update= "true/false" insert= "true/false" lazy= "true/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" > <param name= "property" >idCard(表示主键来源于那个对象的主键)</param> </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); session.flush(); session.getTransaction().commit(); 代码分析二(借助数据库的主键生成策略) User user= new User(); session.save(user); session.flush(); session.getTransaction().commit(); 代码分析三(借助hibernate的主键生成策略) User user= new User(); session.save(user); session.evict(user); session.flush(); session.getTransaction().commit(); 那么怎样解决分析三中的问题呢?看代码分析四的解决方案 代码分析四(解决三种的问题) User user= new User(); session.save(user); session.flush(); session.evict(user); 代码分析五 User user= new User(); session.save(user); user.setName( "xxx" ); User user2= new User(); session.save(user2); --数据库隔离级别 隔离级别 是否存在脏读 是否存在不可重复读 是否存在幻读 ----------------------------------------------------------------------------------------- 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> <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 < :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 ); sessionFactory.evict(User. class , 1 ); 控制session的CacheMode 默认是CacheMode.NORMAL; session.setCacheMode(CacheMode.GET); session.setCacheMode(CacheMode.PUT); *查询缓存 缓存普通结果,而不再局限于实体对象,并且能缓存实体中的ID 一级缓存和session生命周期一致 二级缓存和sessionFactory的生命周期一致 查询缓存的生命周期是不一定的,当前关联的表发生修改,查询缓存的生命周期结束,它的生命周期不可控制 配置和使用(查询缓存默认是关闭的) 在hibernate.cfg.xml中修改属性<property name= "hibernate.cache.use_query_cache" > true </property> query.setCacheable( true ); 但是虽然是配置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); } finally { Session session=threadLocal.get(); if (session!= null ){ if (session.isOpen()){ session.close(); threadLocal.remove(); } } } } public void init(FilterConfig arg0) throws ServletException { try { Configuration cfg= new Configuration().configure(); factory=cfg.buildSessionFactory(); } catch (Exception e) { e.printStackTrace(); throw new ServletException(e); } } public static Session getSession(){ Session session=threadLocal.get(); if (session== null ){ session=factory.openSession(); threadLocal.set(session); } return session; } }
转载于:https://www.cnblogs.com/kelin1314/archive/2010/09/11/1824026.html