Hibernate实战总结
项目实战
简介
Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库。常被用于在JavaEE架构中完成数据持久化的重任。
属性介绍
Hibernate的官方手册上有完整的属性介绍,见P32−P41
或者也可以看这里
数据加载方式
在传统的JDBC操作中,我们通过SQL语句加载数据进行操作。当SQL语句提交之后,这些数据就被读取待用。但是在Hibernate的世界里,数据(针对关联数据)就有了多种加载方式
- 及时加载:实体加载完成后立刻加载其关联数据
- 延迟加载:实体加载时关联数据并非即刻获取,而是关联数据第一次被访问时再读取
- 预先加载:实体与关联数据同时读取,不过是通过一条SQL(外连接outer join)同时读取
- 批量加载:对于及时加载和延迟加载,可以通过批量加载进行性能优化
主键生成策略
- assigned:主键由应用逻辑产生,数据交由Hibernate保存时,主键值已经设置完毕,无需Hibernate干预
- hilo:通过hi/lo(高低位方式high low)算法实现的主键生成机制,需要额外的数据库表保存主键生成历史状态
- seqhilo:与hilo类似,通过hi/lo算法实现的主键生成机制,只是将hilo中的数据表换成了序列sequence,需要数据库中先创建sequence,适用于支持sequence的数据库,如Oracle
- increment:由Hibernate从数据库中取出主键的最大值(每个session只取1次),以该值为基础,每次增量为1,在内存中生成主键,不依赖于底层的数据库,因此可以跨数据库
- identity:identity由底层数据库生成标识符。identity是由数据库自己生成的,但这个主键必须设置为自增长,使用identity的前提条件是底层数据库支持自动增长字段类型
- sequence:采用数据库提供的sequence机制生成主键,需要数据库支持sequence
- native:native由hibernate根据使用的数据库自行判断采用identity、hilo、sequence其中一种作为主键生成方式,灵活性很强
- uuid.hex:由Hibernate基于128位唯一值产生算法,根据当前设备IP,时间,JVM启动时间,内部自增量等4个参数生成十六进制数值(编码后以长度为32位的字符串表示)作为主键。重复概率非常非常小。
- uuid.string:与uuid.hex类似,只是生成的主键未进行编码(长度16位)。
- foreign:使用另外一个相关联的对象的主键作为该对象主键
- select:使用触发器生成主键,主要用于早期的数据库主键生成机制,能用到的地方非常少,是Hibernate3中新引入的主键获取机制,主要针对遗留系统的改造工程。
缓存
缓存是数据库数据在内存中的临时容器,它包含了库表数据在内存中的临时拷贝,位于数据库与数据访问层之间。而Hibernate的缓存分为两个层次:内部缓存(session层)和二级缓存(sessionFactory层)。缓存主要在以下两种情况下发挥作用:通过主键id加载数据时以及延迟加载时。
事务管理
事务的四个特性
- Atomic:原子性
事务中包含的操作被看作一个逻辑单元,这个逻辑单元中的操作要么全部成功,要么全部失败。
- Consistency:一致性
一致性意味着,只有合法的数据可以被写入数据库,如果数据有任何违例(比如数据与字段类型不符),则事务应该将其回滚到最初状态。
- Isolation:隔离性
事务允许多个用户对同一个数据的并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必修与其他并行事务的修改相互独立。
- Durability:持久性
事务结束后,事务处理的结果必须能够得到固话(保存在可掉电的存储器上)。
事务隔离等级
在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同。
- 未授权读取(读未提交)(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
- 授权读取(读提交)(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
- 可重复读取(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
- 序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
JDBC与JTA的事务管理
- JDBC的事务管理:JDBC的一切行为包括事务是基于一个connection的,在JDBC中是通过Connection对象进行事务管理的,默认是自动提交事务,可以手工将自动提交关闭,通过commit方法进行提交,rollback方法进行回滚,如果不提交,则数据不会真正的插入到数据库中。
- JTA的事务管理:JTA事务管理由JTA容器实现,JTA容器对当前加入事务的众多Connection进行调度,实现其事务性要求。JTA的事务周期可横跨多个JDBC Connection生命周期。
分页
Hibernate提供了一个支持跨系统的分页机制,这样无论底层是什么样的数据库都能用统一的接口进行分页操作。比如下面的代码就是从第500条开始取出100条记录:
Query q = session.createQuery("from FooBar as f");
q.setFirstResult(500);
q.setMaxResults(100);
List l = q.list();
优化策略
见这里的总结
PS:一个SQL注入攻击的实例
例如,我们为了实现用户登录功能,编写了如下代码
"from User user where user.name='"+username+"' and user.password='"+password+"'";
假设这里的username和password来自页面输入框中用户填写的数据。此时,如果我们在登录网页上输入用户名:
somebody' or 'x'='x
,密码随意,那么登录结果会很让人意外的成功。为什么呢,我们在看一下这个时候拼装得到的HQL:
"from User user where user.name='somebody' or 'x'='x' and user.password='anyword';
显然,由于用户名中的
or 'x'='x'
被添加进了HQL作为一个子句执行,where逻辑必定为真(只要数据库中有用户名为somebody的记录存在),而密码是否正确无关紧要。