--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 < :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;
}
}
分享到:
相关推荐
SSH之Hibernate总结 SSH(Struts、Spring、Hibernate)是Java Web开发中常见的三大框架,而Hibernate作为ORM(对象关系映射)框架,是连接Java应用程序与数据库的关键组件。本总结将围绕Hibernate的核心概念、配置...
【Hibernate总结】 Hibernate是一个强大的Java持久化框架,它封装了JDBC,负责对象持久化,作为应用程序和数据库之间的中间层。映射关系是通过配置文件维护的,这使得Hibernate能够与不同的数据库系统灵活地交互。 ...
本资源包含的"hibernate总结练习源码"提供了对Hibernate ORM框架实际应用的实例,有助于深入理解和掌握其核心概念与功能。 1. **对象关系映射(ORM)**:Hibernate 提供了一种机制,将数据库中的表映射为Java类,表...
《Hibernate总结(三)》 在本篇关于Hibernate的总结中,我们将深入探讨这个流行的Java对象关系映射(ORM)框架的关键概念和技术细节。Hibernate作为一款强大的工具,它极大地简化了Java开发人员处理数据库操作的工作...
标题:hibernate总结 描述:此文档是个人在使用Hibernate框架进行数据持久化操作时的经验积累和技巧分享。文档全面覆盖了Hibernate的各种配置方法、数据映射技术、集合类映射、对象关系映射(ORM)基础以及与J2EE...
### 学习Hibernate总结 #### 一、简介与配置 Hibernate是Java环境下一款优秀的对象关系映射(ORM)框架,它极大地简化了数据访问层(DAL)的编码工作,允许开发人员将主要精力集中在业务逻辑上而不是繁琐的SQL语句...
1. 引入Hibernate所需的JAR文件,例如在Eclipse中创建Hibernate类库。 2. 创建配置文件`hibernate.cfg.xml`,其中包含数据库连接信息、日志格式等。 3. 设计实体类,对应数据库表中的记录,并提供对应的getter和...
**总结项目实战** 在实际项目中,使用 Hibernate 可以简化数据库操作,提高开发效率。常见的应用场景包括用户管理、订单处理、商品分类等。项目实践中要注意合理设计实体关系,避免 N+1 查询问题,利用缓存优化性能...
在Java开发中,Hibernate是一个非常重要的对象关系映射(ORM)框架,它简化了数据库操作,使得开发者可以使用面向对象的方式来处理数据。本教程将深入探讨如何使用Hibernate来实现一个简单的课程管理系统,涵盖多对...
总结来说,JDBC是基础的数据库访问技术,适合进行简单的数据库操作,而Hibernate则通过提供面向对象的接口,极大地简化了数据库操作,尤其适用于复杂的业务场景。两者结合使用,可以让开发者在享受面向对象编程便利...
《Hibernate4总结文档》 Hibernate4作为一款强大的Java对象关系映射框架,简化了数据库操作,使得开发者可以更专注于业务逻辑而不是数据库层面的细节。本文将深入探讨Hibernate4的配置和使用,帮助开发者更好地理解...
总结来说,Hibernate是一个强大的ORM框架,它极大地简化了Java应用的数据库操作,提高了开发效率,并提供了高级特性如缓存、事务管理等。通过深入理解和熟练使用Hibernate,开发者可以构建更高效、更易于维护的...