ORM:
Hibernate实现ORM简介:
对象关系映射(ORM)
ORM(Object/Relation Mapping)是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将Java程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式。 虽然同时暗示者额外的执行开销;但是如果ORM作为一种中间件实现,则会有很多机会做优化,而这些在手写的持久层并不存在。更重要的是用于控制转换的元数据需要提供和管理;但是同样,这些花费要比维护手写的方案要少;而且就算是遵守ODMG (Object Data Management Group )规范的对象数据库依然需要类级别的元数据(Meta Data)。
Hibernate 工作原理:
启动Hibernate
1.构建Configuration实例,初始化该实例中的所有变量 Configuration cfg = new Configuration().configure(); 加载hibernate.cfg.xml文件至该实例内存
2.通过hibernate.xfg.xml文件中的mapping节点配置,加载hbm.xml文件至该实例内存利用上面创建的Configuration实例构建一个SessionFactory实例 SessionFactory sf = cfg.buildSessionFactory();
3.由上面得到的SessionFactory实例创建连接 Session s = sf.openSession();
4.由上面得到的Session实例创建事务操作接口Transaction的一个实例tx Transaction tx = s.beginTransaction();
5.通过Session接口提供的各种方法操作数据库的访问
6.提交数据库的操作结果 tx.commit();
7.关闭Session链接 s.close();
Hibernate的运行过程如下图:
映射:
一、基本数据类型映射:对Java的基本类型的映射,如int,char,long等。
在Hibernate的映射文件中的配置如:<property name="name" type=”string” />
二、集合映射: 如List、Set、Map的映射。
三、关联映射:
1、 单向关联:一端只知道另一端的存在,用一个例子来说:A类知道B类,但B类并不知道A类的存在。
a)单向一对多:如一个人对应多个地址,
它的语法是:<one-to-many class="从表类名" />
b)单向一对一:
一对一的关联可以基于主键关联,但基于主键关联的持久化类不能拥有自己的主键生成策略,它的主键由关联类负责生成。
用一个简单的例子来说:一个人对应一个地址。
语法:<one-to-one name=”从表类名” constrained=”true”/>
另外一种写法:
<many-to-one name="从表对象名" column="从表主键名 " unique-key="true" />
c)单向多对多:一个人可对应多个地址,一个地址也可以对应多个人。
语法:<many-tomany column=”从表主键名” class=”从表类名” />
d)单向多对一:多个人对应一个地址。
语法:<many-to-one name="从表对象名 " column="从表主键名"/>
2、双向关联:两端都知道对方的存在,如:A类知道B类,B类也知道A类。
双向关联有两个概念:
a)、inverse(反转):将自己的控制权交给对方控制,多在在one-to-many和many-to-many的集合定义中使用。
b). Cascade(级联):用来说明当对主对象进行某种操作时是否对其关联的从对象也作类似的操作,常用的Cascade取值由:none,all,save-update,delete,Delete-orphan。
双向关联与单向关联的区别就在与双向是互知的。
双向一对一:一个人对应一个地址,
语法:在主表这一方: <one-to-one name=”从表类名” cascade=”all” />
从表方:
<many-to-one name=”主表类名” column=”主表主键名” unique-key=”true” />
双向一对多:一个人对应多个地址。
主表这方:
<set name="从表对象名" inverse="true" cascade="all">
<!--column用于指定外键列名-->
<key column="外键列名" not-null="true"/>
<!--映射关联类-->
<one-to-many class="从表类名 "/>
</set>
从表方:
<many-to-one name="主表对象名" column="主键"cascade="all" />
双向多对多:一个人可对应多个地址,一个地址也可以对应多个人。
双向多对多需要第三张表来连接。
主表方:
<set name="从表对象名" table="连接表名" cascade="all">
<key column="指定连接表中关联当前实体类的列名" not-null="true"/>
<many-to-many column="连接表中关联本实体的外键" class="从表类名 "/>
</set>
从表方:
<set name="主表对象名" inverse="true" table="连接表名 ">
<!--column="addressid"是连接表中关联本实体的外键-->
<key column="从表主键"/>
<many-to-many column="当前实体主键" class="当前的实体类名 "/>
</set>
四、组件映射:多用来表示对象的从属关系(组合,聚合)。
比如一个学生可能有姓名,年龄,性别,成绩等属性,其中成绩是另一个对象,又有科目,分数属性,这里就需要用组件映射来表是之间的关系。
语法:
<component name=”对象属性名” class=”对象类名” >
<property name=”所属类属性名” />
<component/>
五、继承映射:
继承映射分为三种:
a)、单表继承:就是将多个类的信息存放在一张表中,通常继承多个类。单表继续需要一个<discriminator column=”字段名称” type=”类型”/>鉴别器。
子类使用<subclass>标签定义,如
<subclass name=”子类名” discriminator-value=”鉴别器(用于区分多个子类)” >
<property name=”子类字段名”/>
<subclass/>
b)、具体表继承:每个子类一个表。
它的语法:
<union-subclass name="子类名" table="子类表名">
<property name="子类中的字段" />
</union-subclass>
c)、类表继承:每个具体类一个表。
每个具体类一张表可以有两种方法:
1、 第一种方法是使用 <union-subclass>。如:
<union-subclass name="子类名"table="表名 ">
<property name="字段名"/>
</union-subclass>
这种方式的局限在于,如果一个属性在超类中做了映射,其字段名必须与所有子类 表中定义的相同。
2、 另一种可供选择的方法是采用隐式多态;隐式多态则采用每个具体类的 PO 独立建表的策略,在它的映射文件中将看不出任何的和接口、抽象类的关系,同时对于抽象类。
<class name="子类名" table="表名">
<id name="id">
<generator class="native"/>
</id>
<property name="字段"/>
</class>
对于这种映射策略而言,通常用<any>来实现到父类的多态关联映射。
如:<any name=”父类名” meta-type=”String”>
<meta-value value=”子类别名” class=”子类类名”/>
<column name=”父类字段” />
<any />
这种方法的缺陷在于,在Hibernate执行多态查询时无法生成带UNION的SQL语句。
对“每个具体类映射一张表”的映射策略而言,隐式多态的方式有一定的限制。而<union-subclass>映射的限制则没有那么严格。
在对于Hibernate的显式和隐式多态下面讲的很详细:
http://docs.huihoo.com/hibernate/reference-v3_zh-cn/inheritance.html
查询:
Hibernate支持三种查询方式
(1)hql
(2)条件查询Criteria,Query
(3)原始sql
hql查询
它全称Hibernate Qusery Language, HQL是面向对象的(OO)的. HQL是非常有意识的被设计为完全面向对象的查询,它可以理解如继承、多态 和关联之类的概念。
注意:大小写敏感。
(1) from子句
i. 查询所有
1. session.createQuery("from Person");
ii. 查询单条数据
1. 有两种方式取值:
2. 先得到一个Query对象实例
a) query.uniqueResult()
b) query.list().get(0)
3. 查询具体表中某些字段数据
a) session.createQuery("select name,age from Person");
4. 它返回的List 集合,元素是数组对象。
(2) 关联(Association)与连接(Join)
inner join(内连接),可以简写为join
left outer join(左外连接) ,可以简写为left join
right outer join(右外连接) , 可以简写为right join
full join (全连接,并不常用)
(3) 聚集函数
HQL查询甚至可以返回作用于属性之上的聚集函数的计算结果:
select avg(Person.weight), sum(Person.weight), max(Person.weight), count(Person) from Person person
(4) 多态查询
一个如下的查询语句:
from Person person
不仅返回Person类的实例, 也同时返回它子类的实例. Hibernate 可以在from子句中指定任何 Java 类或接口. 查询会返回继承了该类的所有持久化子类 的实例或返回声明了该接口的所有持久化类的实例。
(5) where子句
where子句允许你将返回的实例列表的范围缩小. 如果没有指定别名,你可以使用属性名来直接引用属性:
(6) 表达式
在where子句中允许使用的表达式包括 大多数你可以在SQL使用的表达式种类:
(7) order by子句
查询返回的列表(list)可以按照一个返回的类或组件(components)中的任何属性(property)进行排序:
from Person person order by person.name asc, person.weight desc
|
可选的asc或desc关键字指明了按照升序或降序进行排序.
(8) group by子句
一个返回聚集值(aggregate values)的查询可以按照一个返回的类或组件(components)中的任何属性(property)进行分组:having子句在这里也允许使用.
select cat.color, sum(cat.weight), count(cat) from Cat cat group by cat.color having cat.color in (eg.Color.TABBY, eg.Color.BLACK)
|
(9) 子查询
对于支持子查询的数据库,Hibernate支持在查询中使用子查询。一个子查询必须被圆括号包围起来(经常是SQL聚集函数的圆括号)。 甚至相互关联的子查询(引用到外部查询中的别名的子查询)也是允许的。
from Cat as fatcat where fatcat.weight > ( select avg(cat.weight) from DomesticCat cat )
|
Criteria(QBC中C指的就是Criteria)
它是一个接口,它的使用比hql要容易的多。Criteria接口与DetachedCriteria。
离线操作:就是当请求和数据库断开连接的时候,DetachedCriteria也可以操作。它不需要借助Session来创建,而是直接用。
离线操作:即便是数据库连接中断也能进行数据操作。
(二者的区别就是DetachedCriteria可以离线操作,而Criteria不可以)
带条件查询:用到Restrictions 对象,该对象用于添加约束。Person为一个POJO类。
Criteria criteria= session.createCriteria(Person.class); // Criteria类为查询类,Restrictions是条件类
// 查询某个人名字为bb 的,eq=”equals” // criteria.add(Restrictions.eq("name", "bb"));
// 模糊查询,姓名中包含"a"的 // criteria.add(Restrictions.like("name", "%a%"));
// 查询年龄在20-30之间的,包含了20,30 // criteria.add(Restrictions.between("age", 20, 30));
// 查询年龄>30的,gt=”greater than” // criteria.add(Restrictions.gt("age", 30));
// 查询年龄<30的 lt=”less than” // criteria.add(Restrictions.lt("age", 30));
// age>=22,ge=”greater or equals” // criteria.add(Restrictions.ge("age", 22));
// age<=40,le=”less or equals” // criteria.add(Restrictions.le("age", 20)); |
模板查询(QBE,E就是指Example)
也是一种查询方式,比较常用,是允许通过给对象设值来查询。
例子:
Person person = new Person (); person.setName("aaa"); Criteria criteria=session.createCriteria(Person.class);
//模糊查询name,还要比较age属性是否匹配。 criteria.add(Example.create(testBatch2).ignoreCase().enableLike(MatchMode.ANYWHERE));
//模糊查询name,但不要比较age属性。也就是根据name属性查询 criteria.add(Example.create(person).ignoreCase().enableLike(MatchMode.ANYWHERE).excludeProperty("age")
|
使用聚集函数:用到 Projections这个投影类,里面包含了对聚集函数操作,返回的结果可用uniqueResult() 取得.
// 取age 的最大值 criteria.setProjection(Projections.max("age")); |
结果集排序:Order对象来操作,里面包含了升序,降序的方法。
criteria.addOrder(Order.asc("age")); //升序 criteria.addOrder(Order.desc("age")); //降序 |
原始的SQL
使用SQL查询可以利用某些数据库的特性,或者用于将原有的JDBC应用迁移到Hibernate应用上。使用命名的SQL查询还可以将SQL语句放在配置文件中配置,从而提高程序的解耦,命名SQL查询还可以用于调用存储过程。
对原生SQL查询执行的控制是通过SQLQuery接口进行的,通过执行Session.createSQLQuery()获取这个接口。
List cats = sess.createSQLQuery( " select * from cats " ).addEntity(Cat. class ).list();
//查询多表时避免字段名重复的问题 session.createSQLQuery( " select {cat.*} from cats cat " ).addEntity( " cat " , Cat. class ).list(); |
命名SQL查询
可以将SQL语句不放在程序中,而放在配置文件中,这种方式以松耦合的方式配置SQL语句,可从而降低耦合。
在Hibernate的映射文件中定义查询名,然后确定查询所用的SQL语句,然后就可以直接调用该命名SQL查询。在这种情况下,不需要调用addEntity()方法,因为在配置命名SQL查询时,已经完成了查询结果与实体的关联。
在配置文件里配置
<sql-query name="persons"> <return alias="person" class="eg.Person"/> SELECT person.NAME AS {person.name}, person.AGE AS {person.age}, person.SEX AS {person.sex} FROM PERSON person WHERE person.NAME LIKE :namePattern </sql-query> |
调用
List people = sess.getNamedQuery("persons") .setString("namePattern", namePattern) .setMaxResults(50) .list(); |
Hibernate 3引入了对存储过程查询(stored procedure)和函数(function)的支持.以下的说明中,这二者一般都适用。 存储过程/函数必须返回一个结果集,作为Hibernate能够使用的第一个外部参数. 下面是一个Oracle9和更高版本的存储过程例子.
创建一个存储过程
CREATE OR REPLACE FUNCTION selectAllEmployments RETURN SYS_REFCURSOR AS st_cursor SYS_REFCURSOR; BEGIN OPEN st_cursor FOR SELECT EMPLOYEE, EMPLOYER, STARTDATE, ENDDATE, REGIONCODE, EID, VALUE, CURRENCY FROM EMPLOYMENT; RETURN st_cursor; END; |
配置
<!-- 定义命名SQL查询,name属性指定命名SQL查询名 -->
<sql-query name="selectAllEmployees_SP" callable="true">
<!-- 定义返回列与关联实体类属性之间的映射 -->
<return alias="emp" class="Employment">
<!-- 依次定义每列与实体类属性的对应 --> <return-property name="employee" column="EMPLOYEE"/> <return-property name="employer" column="EMPLOYER"/> <return-property name="startDate" column="STARTDATE"/> <return-property name="endDate" column="ENDDATE"/> <return-property name="regionCode" column="REGIONCODE"/> <return-property name="id" column="EID"/>
<!-- 将两列值映射到一个关联类的组件属性 -->
<return-property name="salary">
<!-- 映射列与组件属性之间的关联 -->
<return-column name="VALUE"/>
<return-column name="CURRENCY"/>
</return-property>
</return>
{ ? = call selectAllEmployments() }
</sql-query> |
使用Query接口或Criteria接口实现分页
● setFirstResult(),设置返回结果集的起始点。
● setMaxResults(),设置查询获取的最大记录数。
实例状态:
PO的三个状态:瞬态,持久化,脱管(离线)
1、(瞬态)未被持久化的VO
此时就是一个内存对象VO,由JVM管理生命周期
2、(持久化)已被持久化的PO,并且在Session生命周期内
此时映射数据库数据,由数据库管理生命周期
3、(脱管)曾被持久化过,但现在和Session已经detached了,以VO的身份在运行
这种和Session已经detached的PO还能够进入另一个Session,继续进行PO状态管理,此时它就成为PO的第二种状态了。这种PO实际上是跨了Session进行了状态维护的。
在传统的JDO1.x中,PO只有前面两种状态,一个PO一旦脱离PM,就丧失了状态了,不再和数据库数据关联,成为一个纯粹的内存VO,它即使进入一个新的PM,也不能恢复它的状态了。
Hibernate缓存介绍:
缓存在Hibernate中主要有三个方面:一级缓存、二级缓存和查询缓存。
一级缓存
Hibernate的一级缓存是由Session提供的,因此它只存在于Session的生命周期中,也就是当Session关闭的时候该Session所管理的一级缓存也会立即被清除,所以也可以理解为一级缓存是session缓存。
Hibernate的一级缓存是Session所内置的,不能被卸载,也不能进行任何配置。
一级缓存采用的是key-value的Map方式来实现的,在缓存实体对象时,对象的主关键字ID是Map的key,实体对象就是对应的值。所以说,一级缓存是以实体对象为单位进行存储的,在访问的时候使用的是主关键字ID。
虽然,Hibernate对一级缓存使用的是自动维护的功能,没有提供任何配置功能,但是可以通过Session中所提供的方法来对一级缓存的管理进行手工干预。Session中所提供的干预方法包括以下两种。
● evict() :用于将某个对象从Session的一级缓存中清除。
● clear() :用于将一级缓存中的对象全部清除。
在进行大批量数据一次性更新的时候,会占用非常多的内存来缓存被更新的对象。这时就应该阶段性地调用clear()方法来清空一级缓存中的对象,控制一级缓存的大小,以避免产生内存溢出的情况。具体的实现方法如清单14.8所示。
清单14.8 大批量更新时缓存的处理方法
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
for ( int i=0; i<100000; i++ ) {
Customer customer = new Customer(……);
session.save(customer);
if ( i % 20 == 0 ) {
//将本批插入的对象立即写入数据库并释放内存
session.flush();
session.clear();
}
}
tx.commit();
session.close();
Hibernate查询方法与缓存的关系
在前面介绍了Hibernate的缓存技术以及基本的用法,在这里就具体的Hibernate所提供的查询方法与Hibernate缓存之间的关系做一个简单的总结。
在开发中,通常是通过两种方式来执行对数据库的查询操作的。一种方式是通过ID来获得单独的Java对象,另一种方式是通过HQL语句来执行对数据库的查询操作。下面就分别结合这两种查询方式来说明一下缓存的作用。
通过ID来获得Java对象可以直接使用Session对象的load()或者get()方法,这两种方式的区别就在于对缓存的使用上。
● load()方法
在使用了二级缓存的情况下,使用load()方法会在二级缓存中查找指定的对象是否存在。
在执行load()方法时,Hibernate首先从当前Session的一级缓存中获取ID对应的值,在获取不到的情况下,将根据该对象是否配置了二级缓存来做相应的处理。
如配置了二级缓存,则从二级缓存中获取ID对应的值,如仍然获取不到则还需要根据是否配置了延迟加载来决定如何执行,如未配置延迟加载则从数据库中直接获取。在从数据库获取到数据的情况下,Hibernate会相应地填充一级缓存和二级缓存,如配置了延迟加载则直接返回一个代理类,只有在触发代理类的调用时才进行数据库的查询操作。
在Session一直打开的情况下,并在该对象具有单向关联维护的时候,需要使用类似Session.clear(),Session.evict()的方法来强制刷新一级缓存。
● get()方法
get()方法与load()方法的区别就在于不会查找二级缓存。在当前Session的一级缓存中获取不到指定的对象时,会直接执行查询语句从数据库中获得所需要的数据。
在Hibernate中,可以通过HQL来执行对数据库的查询操作。具体的查询是由Query对象的list()和iterator()方法来执行的。这两个方法在执行查询时的处理方法存在着一定的差别,在开发中应该依据具体的情况来选择合适的方法。
● list()方法
在执行Query的list()方法时,Hibernate的做法是首先检查是否配置了查询缓存,如配置了则从查询缓存中寻找是否已经对该查询进行了缓存,如获取不到则从数据库中进行获取。从数据库中获取到后,Hibernate将会相应地填充一级、二级和查询缓存。如获取到的为直接的结果集,则直接返回,如获取到的为一些ID的值,则再根据ID获取相应的值(Session.load()),最后形成结果集返回。可以看到,在这样的情况下,list()方法也是有可能造成N次查询的。
查询缓存在数据发生任何变化的情况下都会被自动清空。
● iterator()方法
Query的iterator()方法处理查询的方式与list()方法是不同的,它首先会使用查询语句得到ID值的列表,然后再使用Session的load()方法得到所需要的对象的值。
在获取数据的时候,应该依据这4种获取数据方式的特点来选择合适的方法。在开发中可以通过设置show_sql选项来输出Hibernate所执行的SQL语句,以此来了解Hibernate是如何操作数据库的。
Hibernate锁:乐观锁,悲观锁。
悲观锁:是基于数据库锁的机制实现的,只支持单个事务的操作,有一个事务在操作时别的事务操作就不能进行。
session.load(Person.class, "ID", LockMode.UPGRADE);
乐观锁:支持多个事务操作。是采用数据库版本机制实现的
<class name="包名.类名" table="表名" optimistic-lock="version">
<id name="id">
<generator class="uuid.hex" />
</id>
<version name="version"/>
<property name="属性名" />
</class>
注:version在数据进行更改时会自动加一
使用hibernate 操作oracle:
Hibernate连接Oracle数据库的步骤
在hibernate.cfg.xml需要改动两处地方
1. 数据库连接驱动::oracle.jdbc.driver.OracleDriver
2. 方言:org.hibernate.dialect.OracleDialect
Hibernate 原始sql的另一种查询方法命名查询
<sql-query name="persons">
<return alias="person" class="erson"/>
SELECT
person.person_id AS {person.id},
person.NAME AS {person.name},
person.AGE AS {person.age}
FROM PERSON person
</sql-query>
Hibernate调用Oracle函数
<sql-query name="名字" callable="true">
<return alias="person" class="erson">
<return-property name="id" column="person_id"/>
<return-property name="name" column="name"/>
<return-property name="age" column="age"/>
</return>
{?=call oracle函数名()}
</sql-query>
Hibernate的事务:
Hibernate 是JDBC 的轻量级封装,本身并不具备事务管理能力。在事务管理层,
Hibernate将其委托给底层的JDBC或者JTA,以实现事务管理和调度功能。
Hibernate的默认事务处理机制基于JDBC Transaction。
抓取策略:连接.查询.子查询.批量
1.连接抓取(Join fetching) - Hibernate通过 在SELECT语句使用OUTER JOIN(外连接)来获得对象的关联实例或者关联集合。一个对象一条SQl语句.
<set name="students" inverse="true" cascade="all" fetch="join">
<key column="classid" />
<one-to-many class="com.Student" />
</set>
Classes cla = (Classes)session.load(Classes.class,1);
System.out.println(cla.getName());
for(Iterator iter = cla.getStudents().iterator();iter.hasNext();){
Student student = (Student)iter.next();
System.out.println(student.getName());
}
Fetch=”join” ,上面程序只发了一条sql语句.
2.查询抓取(Select fetching) - 另外发送一条 SELECT 语句抓取当前对象的关联实体或集合。除非你显式的指定lazy="false"禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条select语句。在上面的程序中会发出当前对象关联的集合的大小+1条sql语句.
3.子查询抓取(Subselect fetching) - 另外发送一条SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合。除非你显式的指定lazy="false" 禁止延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条
select语句。所有最多只会发出两条SQL语句. 如果只有一个对对象则Join与Subselect没有区别.如果是多个则Join查询次数为对象的个数而Subselect则只有一条sql语句.
4.批量抓取(Batch fetching) - 对查询抓取的优化方案, 通过指定一个主键或外键列表,Hibernate使用单条SELECT语句获取一批对象实例或集合。
<set name="students" inverse="true" cascade="save-update" batch-size=”3”>
List students = session.createQuery(“select s from Student s where s.id in(1,2,3,4,5,6,7,8).list();
for(Iterator iter=students.iterator();iter.hasNext();){
Student student = (Sutdent)iter.next();
System.out.println(student.getName());
System.out.println(student.getClassess().getName());
}
当第二个for循环时,每次加载 之前设置的 数量 实体对象, 如:现在设置3,那么当
Iterator iter=students.iterator();
iter.hasNext();时候,他会查询出3个student对象