Oracle 是一个数据管理系统 ,和SQL Server一样是关系型数据库,安全性高,可为大型数据库提供更好的支持。
Oracle 数据库的主要特点:
1.支持多用户大事务量的事务处理
2.在保存数据库安全性和完整性方面性能优越
3.支持分布式数据处理,将分布在不同物理位置的数据库用通信网络连接起来,
4.具有可移植性,Oracle可以在Window、Linux 等多个操作系统平台上使用,而SQL Server 只能在window下使用
我们知道oarcle数据库真正存放数据的是数据文件(data files),Oarcle表空间(tablespaces)实际上是一个逻辑的概念,他在物理上是并不存在的,那么把一组data files 捻在一起就成为一个表空间。
表空间属性:
一个数据库可以包含多个表空间,一个表空间只能属于一个数据库
一个表空间包含多个数据文件,一个数据文件只能属于一个表空间
表这空间可以划分成更细的逻辑存储单元
oracle数据库出现“批处理中出现错误: ORA-00001: 违反唯一约束条件”解决方法
最近使用oraclede impdp工具全库导入数据库时,在数据库里面使用出现如下情况。
SQL state [null]; error code [17081]; 批处理中出现错误: ORA-00001: 违反唯一约束条件 (GDXAORCL.SYS_C0055359); nested exception is java.sql.BatchUpdateException: 批处理中出现错误: ORA-00001: 违反唯一约束条件 (GDXAORCL.SYS_C0055359) -(:155)
由于表的ID是唯一的,所以用 select max(id) from test 查的该ID大于Sequences里面的开始ID,所以出现了该情况
为此,我们只要更改Sequences里面的”开始于“的ID大于max(ID)即可。
问题解决
nvl(comm,0) --null value | 如果第一个参数的值为null,则返回第二个值,否则返回第一个值 |
nvl(comm,500,1000) | 如果第一个参数的值为null,则返回第二个值,否则返回第三个值 |
decode(to_char(hiredate,'MM'),'01','一月','02','二月''03','三月') 相当于case when then end | 如果to_char() 的值为01,则返回一月 如果to_char() 的值为02,则返回二月 ........ |
Sql语句:
Sql语句:
Sql语句:
Sql语句:
Sql语句:
Sql语句:
Sql语句:
Sql语句:
Sql语句:
SQL语句:
Sql语句:
SQL语句:
Sql语句:
答案:
分身术:
--select * from 子查询 引用名1 join 子查询 引用名2 on 要查出什么,on后面就 是什么
select * from (select * from SC where cno=1) a
join (select * from SC where cno=2) b on a.sname=b.sname
join (select * from SC where cno=3) c on b.sname=c.sname
第二章: Oracle 数据库应用
Oracle 新建用户
提示scott账户被锁定的解决方案:
使用 超级管理员登录 dba 输入 alter user scott account unlock;
接下来,你还可以重新给scott这个用户设定密码
修改scott的登录密码:
alter user scott identified by 123;
删除用户 :drop user 用户名 cascade; --级联删除用户
数据库权限管理:
权限分为:系统权限和对象权限
常见系统权限:
create session :连接到数据库
create table、create view、 create sequence 等
对象权限:是指用户对数据库中具体对象所拥有的权限,对象权限是针对某个特定的模式对象执行操作的权利
模式对象:表、视图、索引、存储过程、序列
使用角色能更加方便和高效地对权限进行管理,所以数据库管理员通常使用角色向用户授予权限,而不是直接向用户授予权限,在Oracle数据库系统中预定义了很多角色,如connect角色、resource角色、DBA角色,一般程序使用的用户只要授予connect和resource 两个权限即可。DBA角色具有系统权限,并且可以给其它用户角色授权。由于DBA角色的权限比较多,这里就不一一列举
授权:
grant select on scott.emp to zhangsan;
grant update on scott.emp to zhangsan;
数据库用户安全设计原则:
》》数据库用户权限授权按照最小分配原则
》》数据库用户要分为管理、应用、维护、备份、四类用户
》》不允许使用Sys 和System 用户建立数据库应用对象
》》禁止grant dba to user
序列:
序列是用来生成唯一、连续的整数的数据库对象。序列通常用来自动生成主键或唯一键的值。序列可以按升序排也可以按降序排列,简单的说就是自动增长列,流水号
创建序列:
物理分类 | 逻辑分类 |
分区或非分区索引 | 单列或组合索引(最多包含32列) |
B树索引(标准索引) | 唯一或非唯一索引 |
正常或反向键索引 | 基于函数索引 |
位图索引 |
数据库索引优缺点
创建索引可以大大提高系统的性能: 第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。 第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 第四,在使用分组和排序 子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。 第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。 增加索引也有许多不利的方面: 第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。 第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 创建索引的原则: 索引是建立在数据库表中的某些列的上面。因此,在创建索引的时候,应该仔细考虑在哪些列上可以创建索引,在哪些列上不能创建索引。一般来说,应该在这些列上创建索引,例如: 在经常需要搜索的列上,可以加快搜索的速度; 在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构; 在经常用在连接的列上,这 些列主要是一些外键,可以加快连接的速度; 在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的; 在经常需要排序的列上创 建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间; 在经常使用在WHERE子句中的列上面创建索引,加快条件的判断速度。
前言
众所周知建立索引是为了提高数据库查询效率。正解的索引确实能够数倍的提高数据库查询效率,但一个错误的索引将
会把数据库拖慢,甚至拖死。
本文意在探讨如何选择索引类型。
正文
Oracle常用的有两种索引类型:B树索引和位图索引。
一、 B树索引
B树索引:B树索引是最常用的索引,它的存储结构类似于书的目录索引结构,有分支节点和叶子节点,分支节点相当于书的大目录,叶子节点相当于具体到页的索引。B树索引是oracle数据库的默认索引类型。
(B树索引结构图)
B树索引适用对象:
(1) 适合高基数的列(唯一值多);
(2) 适合与大量的增、删、改(OLTP);
(3) 不能用包含OR操作符的查询;
什么时候不适合创建B树索引:引用一下oracle官方文档
Where B-Trees Should Not Be Created
Several situations are worth noting where you should not create B-Tree indexes on columns. These cases include columns which:
§ Have only a few distinct values in their domains. For example, a Type column that has only four distinct values (A, B, C, and D). The index would be said to have "low selectivity." If you have an Oracle database, then these columns of low selectivity are ideal candidates for Bitmap indexes.// 只有几个不同的值供选择。例如,一个“类型”列中,只有四个不同的值(A,B,C,和D)。该索引是一个低效的选择。如果你有一个Oracle数据库,那么为这些选择范围小的的列建立位图索引是更好的选择。
§ Occur in WHERE clauses but within functions other than MIN or MAX.//当在where 条件中使用了除了MIN和MAX以外的函数。
Indexes in these cases waste space and slow down the load process.
小结:
B树索引经过大量的插入删除操作以后一个是容易使树不平衡,再一个是删除后空间不回收。所以定期重建索引非常有必要。
二、 位图索引
位图索引:
(位图索引结构图)
位图索引优点:
(1) 用一个位来表示一个索引的键值,节省了存储空间;
(2) 对and,or或=的查询条件,位图索引查询效率很高,计算机善于处理0,1数据。
什么时候适合使用位图索引:引用一下oracle官方文档
Candidates for Bitmap Indexes
Bitmap indexes are most advantageous whenever the cardinality of the index is less than one percent, or lowly-selective. This criterion is nearly the opposite of the guideline for B-Tree indexes.
§ A query constrains multiple columns which have few distinct values in their domains (large number of duplicate values).// 一个查询条件包含多个列,并且要创建索引的列只有几个不同的值(拥有大量重复值)。
§ A large number of rows satisfy the constraints on these columns.//大量的数据符合这些列上的约束条件。
§ Bitmap indexes have been created on some or all of these columns. //位图索引可以创建在一个、多个或全部列上。
§ The referenced table contains a large number of rows. //被引用的表包含了非常多的行。
注意:
CAUTION: Bitmap indexes should be used only for static tables and are not suited for highly volatile tables in online transaction processing systems
.//位图索引只能用在相对稳定的表,不适合用在表数据频繁变化的联机系统中。
什么时候不适合创建位图树索引:
(1) 频繁进行插入或更新的表;
(2) 列值很多,可选范围很大的表;
删除索引:
符合以下条件可以删除索引:
(1):应用程序不再需要索引
(2):执行批量加载前删除索引。大量加载数据前删除索引,加载后再重建索引有以下好处:
一:提高加载的性能,二:更有效地使用索引空间
(3)索引已坏
重建索引:
将反向键索引更改为正常的索引:
alter index indexName rebuild noreverse;
何时应该重建索引:
(1)用户表被移动到新的表空间后,表的索引不是自动转移,需要将索引移到新的表空间:
alter index indexName rebuild tablespace_name;
(2)索引中包含很多已经删除的项。对表进行频繁的删除,造成空间的浪费
(3)需要将现有的正常索引转换成反向键索引
(1)改善表的查询性能:可以只访问表中的特定的分区,而不是整张表(2)表更容易管理:按分区加载数据和删除数据更容易(3)便于备份和恢复:可以独立备份和恢复每个分区(4)提高数据的安全性:将不同的分区分布在不同的磁盘,可以减少所有分区数据同时损坏的可能性
Oracle 11 g 新增特性,可以使用PL/sql 块中的赋值语句访问序列,提高序列的使用的灵活性 例如:v_no=emp_seq.nextval
%type: 与数据库的某一列的数据类型一致,%rowtype 与数据库的某一行的数据类型一致
declare
--返回多行数据,采用游标
v_rec employee%rowtype; --指定这个变量代表employee 的某一行
cursor r is
select * into v_rec from employee where sal > 2000;
begin
open r;
while (true) loop
fetch r into v_rec;
exit when r%notfound;
dbms_output.put_line(' 姓名:' || v_rec.ename || ' 工资: ' || v_rec.sal ||' 工作时间: ' ||
to_char(v_rec.hiredate, 'yyyy-mm-dd'));
end loop;
end;
PL/SQL 控制结构 公有3中类型,具体包括:
条件控制:
if () then
elsif() then
else
end if;
循环控制:
--loop循环
loop
要执行的语句;
exit when --条件满足时,退出循环语句
end loop;
--while循环
declarev_count number :=1;
begin
while (v_count<=100)loop
dbms_output.put_line(v_count);
v_count:=1+v_count;
end loop;
end;
--for循环
begin
for ii in 1..100 loop
dbms_output.put_line(ii);
end loop;
end;
顺序控制
顺序控制包括NULL 语句和goto语句。goto语句不推荐使用
NULL语句是一个可执行语句,相当于一个占位符或不执行任何操作的空语句,可以是某些语句变得有意义,提高程序的可读性,保证其他语句结构的完整性和正确性
if v_counter >=10 then null;
异常处理部分请参考:总结:整理 oracle异常错误处理 .
显式游标
在Oracle 数据库中 ,游标是一个十分重要的概念,由于首次接触游标的概念,所以要求在学习的过程中掌握显式游标的的特点,并应用自如。
4.1 游标概念
4.1.1 处理显式游标
4.1.2 处理隐式游标
4.1.3 关于 NO_DATA_FOUND 和 %NOTFOUND的区别
4.1.4 使用游标更新和删除数据
4.2 游标变量
4.2.1 声明游标变量
4.2.2 游标变量操作
游标的使用
在 PL/SQL 程序中,对于处理多行记录的事务经常使用游标来实现。
4.1 游标概念
在PL/SQL块中执行SELECT、INSERT、DELETE和UPDATE语句时,ORACLE会在内存中为其分配上下文区(Context Area),即缓冲区。游标是指向该区的一个指针,或是命名一个工作区(Work Area),或是一种结构化数据类型。它为应用等量齐观提供了一种对具有多行数据查询结果集中的每一行数据分别进行单独处理的方法,是设计嵌入式SQL语句的应用程序的常用编程方式。
在每个用户会话中,可以同时打开多个游标,其数量由数据库初始化参数文件中的OPEN_CURSORS参数定义。
对于不同的SQL语句,游标的使用情况不同:
SQL语句 | 游标 |
非查询语句 | 隐式的 |
结果是单行的查询语句 | 隐式的或显示的 |
结果是多行的查询语句 | 显示的 |
4.1.1 处理显式游标
1. 显式游标处理
显式游标处理需四个 PL/SQL步骤:
l 定义/声明游标:就是定义一个游标名,以及与其相对应的SELECT 语句。
格式:
[ RETURN datatype ]
IS
select_statement;
游标参数只能为输入参数,其格式为:
在指定数据类型时,不能使用长度约束。如NUMBER(4),CHAR(10) 等都是错误的。
[RETURN datatype]是可选的,表示游标返回数据的数据。如果选择,则应该严格与select_statement中的选择列表在次序和数据类型上匹配。一般是记录数据类型或带“%ROWTYPE”的数据。
l 打开游标:就是执行游标所对应的SELECT 语句,将其查询结果放入工作区,并且指针指向工作区的首部,标识游标结果集合。如果游标查询语句中带有FOR UPDATE选项,OPEN 语句还将锁定数据库表中游标结果集合对应的数据行。
格式:
在向游标传递参数时,可以使用与函数参数相同的传值方法,即位置表示法和名称表示法。PL/SQL 程序不能用OPEN 语句重复打开一个游标。
l 提取游标数据:就是检索结果集合中的数据行,放入指定的输出变量中。
格式:
执行FETCH语句时,每次返回一个数据行,然后自动将游标移动指向下一个数据行。当检索到最后一行数据时,如果再次执行FETCH语句,将操作失败,并将游标属性%NOTFOUND置为TRUE。所以每次执行完FETCH语句后,检查游标属性%NOTFOUND就可以判断FETCH语句是否执行成功并返回一个数据行,以便确定是否给对应的变量赋了值。
l 对该记录进行处理;
l 继续处理,直到活动集合中没有记录;
l 关闭游标:当提取和处理完游标结果集合数据后,应及时关闭游标,以释放该游标所占用的系统资源,并使该游标的工作区变成无效,不能再使用FETCH 语句取其中数据。关闭后的游标可以使用OPEN 语句重新打开。
格式:
注:定义的游标不能有INTO 子句。
例1. 查询前10名员工的信息。
CURSOR c_cursor
IS SELECT first_name || last_name, Salary
FROM EMPLOYEES
WHERE rownum < 11 ;
v_ename EMPLOYEES.first_name % TYPE;
v_sal EMPLOYEES.Salary % TYPE;
BEGIN
OPEN c_cursor;
FETCH c_cursor INTO v_ename, v_sal;
WHILE c_cursor % FOUND LOOP
DBMS_OUTPUT.PUT_LINE(v_ename || ' --- ' || to_char(v_sal) );
FETCH c_cursor INTO v_ename, v_sal;
END LOOP;
CLOSE c_cursor;
END ;
传统的fetch into一次只能取得一条数据,使用fetch bulk collect into可以一次从游标中取得所有数据,使用limit子句可以限制一次取的数据条数
1、fetch bulk collect into
- begin
- declare
- cursor c_dept is select * from dept;
- type dept_record is table of dept%rowtype;
- v_dept dept_record;
- begin
- open c_dept;
- fetch c_dept bulk collect into v_dept;
- close c_dept;
- for i in 1.. v_dept.count loop
- dbms_output.put_line('部门名称:'||v_dept(i).dname||' 部门地址:'||v_dept(i).loc);
- end loop;
- end;
- end;
2、使用limit子句限制提取的行数
- begin
- declare
- cursor c_dept is select * from dept;
- type dept_record is table of dept%rowtype;
- v_dept dept_record;
- begin
- open c_dept;
- loop
- exit when c_dept%NOTFOUND;
- fetch c_dept bulk collect into v_dept limit 3;
- dbms_output.put_line('-----------------------------------------------------------');
- for i in 1.. v_dept.count loop
- dbms_output.put_line('部门名称:'||v_dept(i).dname||' 部门地址:'||v_dept(i).loc);
- end loop;
- end loop;
- close c_dept;
- end;
- end;
例2. 游标参数的传递方法。
DeptRec DEPARTMENTS % ROWTYPE;
Dept_name DEPARTMENTS.DEPARTMENT_NAME % TYPE;
Dept_loc DEPARTMENTS.LOCATION_ID % TYPE;
CURSOR c1 IS
SELECT DEPARTMENT_NAME, LOCATION_ID FROM DEPARTMENTS
WHERE DEPARTMENT_ID <= 30 ;
CURSOR c2(dept_no NUMBER DEFAULT 10 ) IS
SELECT DEPARTMENT_NAME, LOCATION_ID FROM DEPARTMENTS
WHERE DEPARTMENT_ID <= dept_no;
CURSOR c3(dept_no NUMBER DEFAULT 10 ) IS
SELECT * FROM DEPARTMENTS
WHERE DEPARTMENTS.DEPARTMENT_ID <= dept_no;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO dept_name, dept_loc;
EXIT WHEN c1 % NOTFOUND;
DBMS_OUTPUT.PUT_LINE(dept_name || ' --- ' || dept_loc);
END LOOP;
CLOSE c1;
OPEN c2;
LOOP
FETCH c2 INTO dept_name, dept_loc;
EXIT WHEN c2 % NOTFOUND;
DBMS_OUTPUT.PUT_LINE(dept_name || ' --- ' || dept_loc);
END LOOP;
CLOSE c2;
OPEN c3(dept_no => 20 );
LOOP
FETCH c3 INTO deptrec;
EXIT WHEN c3 % NOTFOUND;
DBMS_OUTPUT.PUT_LINE(deptrec.DEPARTMENT_ID || ' --- ' || deptrec.DEPARTMENT_NAME || ' --- ' || deptrec.LOCATION_ID);
END LOOP;
CLOSE c3;
END ;
2.游标属性
Cursor_name%FOUND 布尔型属性,当最近一次提取游标操作FETCH成功则为 TRUE,否则为FALSE;
Cursor_name%NOTFOUND 布尔型属性,与%FOUND相反;
Cursor_name%ISOPEN 布尔型属性,当游标已打开时返回 TRUE;
Cursor_name%ROWCOUNT 数字型属性,返回已从游标中读取的记录数。
例3:给工资低于1200 的员工增加工资50。
v_empno EMPLOYEES.EMPLOYEE_ID % TYPE;
v_sal EMPLOYEES.Salary % TYPE;
CURSOR c_cursor IS SELECT EMPLOYEE_ID, Salary FROM EMPLOYEES;
BEGIN
OPEN c_cursor;
LOOP
FETCH c_cursor INTO v_empno, v_sal;
EXIT WHEN c_cursor % NOTFOUND;
IF v_sal <= 1200 THEN
UPDATE EMPLOYEES SET Salary = Salary + 50 WHERE EMPLOYEE_ID = v_empno;
DBMS_OUTPUT.PUT_LINE( ' 编码为 ' || v_empno || ' 工资已更新! ' );
END IF ;
DBMS_OUTPUT.PUT_LINE( ' 记录数: ' || c_cursor % ROWCOUNT );
END LOOP;
CLOSE c_cursor;
END ;
例4:没有参数且没有返回值的游标。
v_f_name employees.first_name % TYPE;
v_j_id employees.job_id % TYPE;
CURSOR c1 -- 声明游标,没有参数没有返回值
IS
SELECT first_name, job_id FROM employees
WHERE department_id = 20 ;
BEGIN
OPEN c1; -- 打开游标
LOOP
FETCH c1 INTO v_f_name, v_j_id; -- 提取游标
IF c1 % FOUND THEN
DBMS_OUTPUT.PUT_LINE(v_f_name || ' 的岗位是 ' || v_j_id);
ELSE
DBMS_OUTPUT.PUT_LINE( ' 已经处理完结果集了 ' );
EXIT ;
END IF ;
END LOOP;
CLOSE c1; -- 关闭游标
END ;
例5:有参数且没有返回值的游标。
v_f_name employees.first_name % TYPE;
v_h_date employees.hire_date % TYPE;
CURSOR c2(dept_id NUMBER , j_id VARCHAR2 ) -- 声明游标,有参数没有返回值
IS
SELECT first_name, hire_date FROM employees
WHERE department_id = dept_id AND job_id = j_id;
BEGIN
OPEN c2( 90 , ' AD_VP ' ); -- 打开游标,传递参数值
LOOP
FETCH c2 INTO v_f_name, v_h_date; -- 提取游标
IF c2 % FOUND THEN
DBMS_OUTPUT.PUT_LINE(v_f_name || ' 的雇佣日期是 ' || v_h_date);
ELSE
DBMS_OUTPUT.PUT_LINE( ' 已经处理完结果集了 ' );
EXIT ;
END IF ;
END LOOP;
CLOSE c2; -- 关闭游标
END ;
例6:有参数且有返回值的游标。
TYPE emp_record_type IS RECORD(
f_name employees.first_name % TYPE,
h_date employees.hire_date % TYPE);
v_emp_record EMP_RECORD_TYPE;
CURSOR c3(dept_id NUMBER , j_id VARCHAR2 ) -- 声明游标,有参数有返回值
RETURN EMP_RECORD_TYPE
IS
SELECT first_name, hire_date FROM employees
WHERE department_id = dept_id AND job_id = j_id;
BEGIN
OPEN c3(j_id => ' AD_VP ' , dept_id => 90 ); -- 打开游标,传递参数值
LOOP
FETCH c3 INTO v_emp_record; -- 提取游标
IF c3 % FOUND THEN
DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name || ' 的雇佣日期是 '
|| v_emp_record.h_date);
ELSE
DBMS_OUTPUT.PUT_LINE( ' 已经处理完结果集了 ' );
EXIT ;
END IF ;
END LOOP;
CLOSE c3; -- 关闭游标
END ;
例7:基于游标定义记录变量。
CURSOR c4(dept_id NUMBER , j_id VARCHAR2 ) -- 声明游标,有参数没有返回值
IS
SELECT first_name f_name, hire_date FROM employees
WHERE department_id = dept_id AND job_id = j_id;
-- 基于游标定义记录变量,比声明记录类型变量要方便,不容易出错
v_emp_record c4 % ROWTYPE;
BEGIN
OPEN c4( 90 , ' AD_VP ' ); -- 打开游标,传递参数值
LOOP
FETCH c4 INTO v_emp_record; -- 提取游标
IF c4 % FOUND THEN
DBMS_OUTPUT.PUT_LINE(v_emp_record.f_name || ' 的雇佣日期是 '
|| v_emp_record.hire_date);
ELSE
DBMS_OUTPUT.PUT_LINE( ' 已经处理完结果集了 ' );
EXIT ;
END IF ;
END LOOP;
CLOSE c4; -- 关闭游标
END ;
3. 游标的FOR循环
PL/SQL语言提供了游标FOR循环语句,自动执行游标的OPEN、FETCH、CLOSE语句和循环语句的功能;当进入循环时,游标FOR循环语句自动打开游标,并提取第一行游标数据,当程序处理完当前所提取的数据而进入下一次循环时,游标FOR循环语句自动提取下一行数据供程序处理,当提取完结果集合中的所有数据行后结束循环,并自动关闭游标。
格式:
-- 游标数据处理代码
END LOOP;
其中:
index_variable为游标FOR 循环语句隐含声明的索引变量,该变量为记录变量,其结构与游标查询语句返回的结构集合的结构相同。在程序中可以通过引用该索引记录变量元素来读取所提取的游标数据,index_variable中各元素的名称与游标查询语句选择列表中所制定的列名相同。如果在游标查询语句的选择列表中存在计算列,则必须为这些计算列指定别名后才能通过游标FOR 循环语句中的索引变量来访问这些列数据。
注:不要在程序中对游标进行人工操作;不要在程序中定义用于控制FOR循环的记录。
例8:
CURSOR c_sal IS SELECT employee_id, first_name || last_name ename, salary
FROM employees ;
BEGIN
-- 隐含打开游标
FOR v_sal IN c_sal LOOP
-- 隐含执行一个FETCH语句
DBMS_OUTPUT.PUT_LINE(to_char(v_sal.employee_id) || ' --- ' || v_sal.ename || ' --- ' || to_char(v_sal.salary)) ;
-- 隐含监测c_sal%NOTFOUND
END LOOP;
-- 隐含关闭游标
END ;
例9:当所声明的游标带有参数时,通过游标FOR 循环语句为游标传递参数。
CURSOR c_cursor(dept_no NUMBER DEFAULT 10 )
IS
SELECT department_name, location_id FROM departments WHERE department_id <= dept_no;
BEGIN
DBMS_OUTPUT.PUT_LINE( ' 当dept_no参数值为30: ' );
FOR c1_rec IN c_cursor( 30 ) LOOP DBMS_OUTPUT.PUT_LINE(c1_rec.department_name || ' --- ' || c1_rec.location_id);
END LOOP;
DBMS_OUTPUT.PUT_LINE(CHR( 10 ) || ' 使用默认的dept_no参数值10: ' );
FOR c1_rec IN c_cursor LOOP DBMS_OUTPUT.PUT_LINE(c1_rec.department_name || ' --- ' || c1_rec.location_id);
END LOOP;
END ;
例10:PL/SQL还允许在游标FOR循环语句中使用子查询来实现游标的功能。
FOR c1_rec IN ( SELECT department_name, location_id FROM departments) LOOP DBMS_OUTPUT.PUT_LINE(c1_rec.department_name || ' --- ' || c1_rec.location_id);
END LOOP;
END ;
显式游标主要是用于对查询语句的处理,尤其是在查询结果为多条记录的情况下;而对于非查询语句,如修改、删除操作,则由ORACLE 系统自动地为这些操作设置游标并创建其工作区,这些由系统隐含创建的游标称为隐式游标,隐式游标的名字为SQL,这是由ORACLE 系统定义的。对于隐式游标的操作,如定义、打开、取值及关闭操作,都由ORACLE 系统自动地完成,无需用户进行处理。用户只能通过隐式游标的相关属性,来完成相应的操作。在隐式游标的工作区中,所存放的数据是与用户自定义的显示游标无关的、最新处理的一条SQL 语句所包含的数据。
格式调用为: SQL%
注:INSERT, UPDATE, DELETE, SELECT 语句中不必明确定义游标。
隐式游标属性
属性 | 值 | SELECT | INSERT | UPDATE | DELETE |
SQL%ISOPEN | FALSE | FALSE | FALSE | FALSE | |
SQL%FOUND | TRUE | 有结果 | 成功 | 成功 | |
SQL%FOUND | FALSE | 没结果 | 失败 | 失败 | |
SQL%NOTFUOND | TRUE | 没结果 | 失败 | 失败 | |
SQL%NOTFOUND | FALSE | 有结果 | 成功 | 失败 | |
SQL%ROWCOUNT | 返回行数,只为1 | 插入的行数 | 修改的行数 | 删除的行数 |
例11: 删除EMPLOYEES表中某部门的所有员工,如果该部门中已没有员工,则在DEPARTMENT表中删除该部门。
V_deptno department_id % TYPE : =& p_deptno;
BEGIN
DELETE FROM employees WHERE department_id = v_deptno;
IF SQL % NOTFOUND THEN
DELETE FROM departments WHERE department_id = v_deptno;
END IF ;
END ;
例12: 通过隐式游标SQL的%ROWCOUNT属性来了解修改了多少行。
v_rows NUMBER ;
BEGIN
-- 更新数据
UPDATE employees SET salary = 30000
WHERE department_id = 90 AND job_id = ' AD_VP ' ;
-- 获取默认游标的属性值
v_rows : = SQL % ROWCOUNT ;
DBMS_OUTPUT.PUT_LINE( ' 更新了 ' || v_rows || ' 个雇员的工资 ' );
-- 回退更新,以便使数据库的数据保持原样
ROLLBACK ;
END ;
4.1.3 关于 NO_DATA_FOUND 和 %NOTFOUND的区别
SELECT … INTO 语句触发 NO_DATA_FOUND;
当一个显式游标的WHERE子句未找到时触发%NOTFOUND;
当UPDATE或DELETE 语句的WHERE 子句未找到时触发 SQL%NOTFOUND;在提取循环中要用 %NOTFOUND 或%FOUND 来确定循环的退出条件,不要用 NO_DATA_FOUND.4.1.4 使用游标更新和删除数据
游标修改和删除操作是指在游标定位下,修改或删除表中指定的数据行。这时,要求游标查询语句中必须使用FOR UPDATE选项,以便在打开游标时锁定游标结果集合在表中对应数据行的所有列和部分列。
为了对正在处理(查询)的行不被另外的用户改动,ORACLE 提供一个 FOR UPDATE 子句来对所选择的行进行锁住。该需求迫使ORACLE锁定游标结果集合的行,可以防止其他事务处理更新或删除相同的行,直到您的事务处理提交或回退为止。
语法:
如果另一个会话已对活动集中的行加了锁,那么SELECT FOR UPDATE操作一直等待到其它的会话释放这些锁后才继续自己的操作,对于这种情况,当加上NOWAIT子句时,如果这些行真的被另一个会话锁定,则OPEN立即返回并给出:
ORA-0054 :resource busy and acquire with nowait specified.
如果使用 FOR UPDATE 声明游标,则可在DELETE和UPDATE 语句中使用
WHERE CURRENT OF cursor_name子句,修改或删除游标结果集合当前行对应的数据库表中的数据行。
例13:从EMPLOYEES表中查询某部门的员工情况,将其工资最低定为 1500;
V_deptno employees.department_id % TYPE : =& p_deptno;
CURSOR emp_cursor
IS
SELECT employees.employee_id, employees.salary
FROM employees WHERE employees.department_id = v_deptno
FOR UPDATE NOWAIT;
BEGIN
FOR emp_record IN emp_cursor LOOP
IF emp_record.salary < 1500 THEN
UPDATE employees SET salary = 1500
WHERE CURRENT OF emp_cursor;
END IF ;
END LOOP;
-- COMMIT;
END ;
例14:将EMPLOYEES表中部门编码为90、岗位为AD_VP的雇员的工资都更新为2000元;
v_emp_record employees % ROWTYPE;
CURSOR c1
IS
SELECT * FROM employees FOR UPDATE ;
BEGIN
OPEN c1;
LOOP
FETCH c1 INTO v_emp_record;
EXIT WHEN c1 % NOTFOUND;
IF v_emp_record.department_id = 90 AND
v_emp_record.job_id = ' AD_VP '
THEN
UPDATE employees SET salary = 20000
WHERE CURRENT OF c1; -- 更新当前游标行对应的数据行
END IF ;
END LOOP;
COMMIT ; -- 提交已经修改的数据
CLOSE c1;
END ;
4.2 游标变量
与游标一样,游标变量也是一个指向多行查询结果集合中当前数据行的指针。但与游标不同的是,游标变量是动态的,而游标是静态的。游标只能与指定的查询相连,即固定指向一个查询的内存处理区域,而游标变量则可与不同的查询语句相连,它可以指向不同查询语句的内存处理区域(但不能同时指向多个内存处理区域,在某一时刻只能与一个查询语句相连),只要这些查询语句的返回类型兼容即可。
4.2.1 声明游标变量
游标变量为一个指针,它属于参照类型,所以在声明游标变量类型之前必须先定义游标变量类型。在PL/SQL中,可以在块、子程序和包的声明区域内定义游标变量类型。
语法格式为:
[ RETURN return_type ] ;
其中:ref_type_name为新定义的游标变量类型名称;
return_type 为游标变量的返回值类型,它必须为记录变量。
在定义游标变量类型时,可以采用强类型定义和弱类型定义两种。强类型定义必须指定游标变量的返回值类型,而弱类型定义则不说明返回值类型。
声明一个游标变量的两个步骤:
步骤一:定义一个REF CURSOU数据类型,如:
TYPE ref_cursor_type IS REF CURSOR;
步骤二:声明一个该数据类型的游标变量,如:
cv_ref REF_CURSOR_TYPE;
例:创建两个强类型定义游标变量和一个弱类型游标变量:
TYPE deptrecord IS RECORD(
Deptno departments.department_id % TYPE,
Dname departments.department_name % TYPE,
Loc departments.location_id % TYPE
);
TYPE deptcurtype IS REF CURSOR RETURN departments % ROWTYPE;
TYPE deptcurtyp1 IS REF CURSOR RETURN deptrecord;
TYPE curtype IS REF CURSOR ;
Dept_c1 deptcurtype;
Dept_c2 deptcurtyp1;
Cv curtype;
与游标一样,游标变量操作也包括打开、提取和关闭三个步骤。
1. 打开游标变量
打开游标变量时使用的是OPEN…FOR 语句。格式为:
FOR select_statement;
其中:cursor_variable_name为游标变量,host_cursor_variable_name为PL/SQL主机环境(如OCI: ORACLE Call Interface,Pro*c 程序等)中声明的游标变量。
OPEN…FOR 语句可以在关闭当前的游标变量之前重新打开游标变量,而不会导致CURSOR_ALREAD_OPEN异常错误。新打开游标变量时,前一个查询的内存处理区将被释放。
2. 提取游标变量数据
使用FETCH语句提取游标变量结果集合中的数据。格式为:
INTO {variable [ , variable ] … | record_variable};
其中:cursor_variable_name和host_cursor_variable_name分别为游标变量和宿主游标变量名称;variable和record_variable分别为普通变量和记录变量名称。
3. 关闭游标变量
CLOSE语句关闭游标变量,格式为:
其中:cursor_variable_name和host_cursor_variable_name分别为游标变量和宿主游标变量名称,如果应用程序试图关闭一个未打开的游标变量,则将导致INVALID_CURSOR异常错误。
例15:强类型参照游标变量类型
TYPE emp_job_rec IS RECORD(
Employee_id employees.employee_id % TYPE,
Employee_name employees.first_name % TYPE,
Job_title employees.job_id % TYPE
);
TYPE emp_job_refcur_type IS REF CURSOR RETURN emp_job_rec;
Emp_refcur emp_job_refcur_type ;
Emp_job emp_job_rec;
BEGIN
OPEN emp_refcur FOR
SELECT employees.employee_id, employees.first_name || employees.last_name, employees.job_id
FROM employees
ORDER BY employees.department_id;
FETCH emp_refcur INTO emp_job;
WHILE emp_refcur % FOUND LOOP
DBMS_OUTPUT.PUT_LINE(emp_job.employee_id || ' : ' || emp_job.employee_name || ' is a ' || emp_job.job_title);
FETCH emp_refcur INTO emp_job;
END LOOP;
END ;
例16:弱类型参照游标变量类型
PROMPT ' What table would you like to see? '
ACCEPT tab PROMPT ' (D)epartment, or (E)mployees: '
DECLARE
Type refcur_t IS REF CURSOR ;
Refcur refcur_t;
TYPE sample_rec_type IS RECORD (
Id number ,
Description VARCHAR2 ( 30 )
);
sample sample_rec_type;
selection varchar2 ( 1 ) : = UPPER (SUBSTR ( ' &tab ' , 1 , 1 ));
BEGIN
IF selection = ' D ' THEN
OPEN refcur FOR
SELECT departments.department_id, departments.department_name FROM departments;
DBMS_OUTPUT.PUT_LINE( ' Department data ' );
ELSIF selection = ' E ' THEN
OPEN refcur FOR
SELECT employees.employee_id, employees.first_name || ' is a ' || employees.job_id FROM employees;
DBMS_OUTPUT.PUT_LINE( ' Employee data ' );
ELSE
DBMS_OUTPUT.PUT_LINE( ' Please enter '' D '' or '' E ''' );
RETURN ;
END IF ;
DBMS_OUTPUT.PUT_LINE( ' ---------------------- ' );
FETCH refcur INTO sample;
WHILE refcur % FOUND LOOP
DBMS_OUTPUT.PUT_LINE(sample.id || ' : ' || sample.description);
FETCH refcur INTO sample;
END LOOP;
CLOSE refcur;
END ;
例17:使用游标变量(没有RETURN子句)
-- 定义一个游标数据类型
TYPE emp_cursor_type IS REF CURSOR ;
-- 声明一个游标变量
c1 EMP_CURSOR_TYPE;
-- 声明两个记录变量
v_emp_record employees % ROWTYPE;
v_reg_record regions % ROWTYPE;
BEGIN
OPEN c1 FOR SELECT * FROM employees WHERE department_id = 20 ;
LOOP
FETCH c1 INTO v_emp_record;
EXIT WHEN c1 % NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_emp_record.first_name || ' 的雇佣日期是 '
|| v_emp_record.hire_date);
END LOOP;
-- 将同一个游标变量对应到另一个SELECT语句
OPEN c1 FOR SELECT * FROM regions WHERE region_id IN ( 1 , 2 );
LOOP
FETCH c1 INTO v_reg_record;
EXIT WHEN c1 % NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_reg_record.region_id || ' 表示 '
|| v_reg_record.region_name);
END LOOP;
CLOSE c1;
END ;
例18:使用游标变量(有RETURN子句)
-- 定义一个与employees表中的这几个列相同的记录数据类型
TYPE emp_record_type IS RECORD(
f_name employees.first_name % TYPE,
h_date employees.hire_date % TYPE,
j_id employees.job_id % TYPE);
-- 声明一个该记录数据类型的记录变量
v_emp_record EMP_RECORD_TYPE;
-- 定义一个游标数据类型
TYPE emp_cursor_type IS REF CURSOR
RETURN EMP_RECORD_TYPE;
-- 声明一个游标变量
c1 EMP_CURSOR_TYPE;
BEGIN
OPEN c1 FOR SELECT first_name, hire_date, job_id
FROM employees WHERE department_id = 20 ;
LOOP
FETCH c1 INTO v_emp_record;
EXIT WHEN c1 % NOTFOUND;
DBMS_OUTPUT.PUT_LINE( ' 雇员名称: ' || v_emp_record.f_name
|| ' 雇佣日期: ' || v_emp_record.h_date
|| ' 岗位: ' || v_emp_record.j_id);
END LOOP;
CLOSE c1;
END ;
存储过程部分请参考:本为知笔记:Oracle存储过程详
第四章: Hibernate 入门
框架技术:相当于一个PPT 的模板:
使用框架技术的优势:
(1)不用考虑共性问题,框架已经帮我们做好了
(2)可以专心于业务逻辑,保证核心业务逻辑的开发质量
(3)结构统一便于学习和维护。
(4)框架中集成了前人的经验,可以帮助新手写出稳定、性能优良而且结构优美的高质量的程序。使混乱的东西变得结构化
主流框架的介绍:
1:Struts框架:Struts 是最早的java 开源框架之一,现在的大多数JavaEE Web 应用程序都是基于Struts框架构建的。Struts是MVC设计模式的一个优秀的实现,他通过采用Java Servlet/JSP技术,实现了基于JavaEE Web 设计模式应用框架,是MVC设计模式的一个经典产品。
2:Struts 2框架:
Struts2是一个基于MVC设计模式的Web应用框架,它本质上相当于一个servlet,在MVC设计模式中,Struts2作为控制器(Controller)来建立模型与视图的数据交互。Struts 2是Struts的下一代产品,是在 struts 1和WebWork的技术基础上进行了合并的全新的Struts 2框架。其全新的Struts 2的体系结构与Struts 1的体系结构差别巨大。Struts 2以WebWork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以Struts 2可以理解为WebWork的更新产品。虽然从Struts 1到Struts 2有着太大的变化,但是相对于WebWork,Struts 2的变化很小。
3:Hibernate框架:
Hibernate 是一个优秀的持久化框架(全世界最流行的开源对象关系映射【ORM】框架),负责简化将对象数据保存到数据库中,或从数据库中读取数据并封装到对象的工作中。hibernate 通过简单的配置和编码即可替代JDBC 繁琐的程序代码。hibernate已经成为当前主流数据库持久化框架,被广泛应用4:Spring 框架:Spring也是一个开源的框架,它的目标是使现有的Java EE技术更容易使用和促进良好的编程习惯。它是一个轻量级的框架,渗透了JavaEE 技术的方方面面。它主要作为依赖注入容器和AOP实现存在,还提供了声明式事务、对DAO层的支持等简化开发的功能。Spring还可以很方便的与Struts、Struts 2、 Hibernate等框架集成
大名鼎鼎的SSH集成框架就是Struts+Spring+Hibernate:使用这个集成框架将使我们的程序更加健壮、稳固、轻巧和优雅。这也是当前最流行的Java技术框架。
数据持久化:内存 <-------->硬盘,双向的(将头脑中的想东西记在本子上)将内存的中的数据模型转换为硬盘中的存储模型例如:文件的存储、数据的读取等都是数据持久化操作
ORM对象/关系数据库映射
ORM全称Object/Relational Mapping,对象/关系数据库映射,可以理解成一种规范。该框架的基本特征:完成面向对象的编程语言到关系数据库之间的映射。
ORM关系型数据库包装成面向对象的模型。
ORM框架由 实体+XML配置文件(如下图所示)
有了ORM 程序员就可以通过操作实体类来操作数据库中的表
基本映射方式:有三种
①数据库表映射类:持久化类被映射到一个数据表 ,当使用一个持久类来创建实例,修改实例属性,删除实例时,系统自动回转换对这个表进行CRUD操作。
②数据表的行映射对象(实例):持久化类会生成很多实例,每个实例就对应数据表中的一个特定行的操作。每个持久化对象对应数据表的一行记录。
③数据表中的列映射对象的属性:当在应用中修改某个持久化对象的指定属性时(持久化实例映射到数据行),
ORM将会转换成对对应数据表中指定数据行、指定列的操作。
About hibernate:
hibernate就是一个ORM框架的典型代表。
①hibernate是轻量级JavaEE应用的一个持久层框架。它的作用在于管理Java实体类到数据库表之间的映射关系,并且提供数据查询和获取数据的方法,可以大幅度的缩短使用JDBC处理数据持久化的时间。
②hibernate完成了对象模型和基于SQL关系模型的映射关系,使得程序员可以采用面向对象的方式来开发程序,充当了两者之间的桥梁。
③Hibernate是一个面向JAVA环境的对象/关系数据库映射工具,用来把对象模型表示的对象映射到基于SQL的关系模型数据结构中去 ,Hibernate的目标主要是为了释放开发者通常的数据持久化相关的编程的繁重任务。
对于以数据为中心的程序而言,往往在数据库中使用存储过程实现商业逻辑,Hibernate可能不是最好的解决方案,但是对于那些基于JAVA的中间件应用,设计采用面向对象的业务逻辑和商业逻辑,Hibernate是最有用的。
④Hibernate不仅仅管理JAVA类到数据库表的映射,还提供数据查询和获取数据的办法,可以大幅度减少开发时人员使用SQL和JDBC处理数据的时间。
对比一些框架的优缺点:
Hibernate 框架的优缺点以及使用场合:优点:
1、 开源和免费的License,方便需要时研究源码,改写源代码,进行功能定制。
2:方便:提高工作效率即开发效率,降低了维护成本(与JDBC 相比 代码量大大减少)。3:可拓展性强,支持java面向对象的特性(封装、继承、多态)。4:可移植性好,即跨数据库(只需要修改配置文件),是其最大的优点缺点:
运行性能较低,不适合大规模批量增删改;
不适合以数据为中心大量使用存储过程的应用;
不适用于小型项目;也不适用于关系模型设计不合理,不规范的系统
POJO (Plain Ordinary Java Object) 普通的java对象,是符合javaBean规范的类
Hibernate的持久化解决方案将用户从原始的JDBC访问中释放出来,用户无须再关注底层的JDBC的操作。而是以面向对象的方式进行持久层操作。底层数据连接的获得,数据访问的实现,事务控制都无须用户关心。将应用从底层的JDBC中抽象出来,通过配置文件管理底层的JDBC连接,让Hibernate解决持久化访问的实现。
简单工作原理:
分析:
1、Hibernate框架需要2种配置文件,分别是:hibernate.cfg.xml(存放数据库连接信息)与xxx.hbm.xml
2、xxx.hbm.xml是配置在hibernate.cfg.xml中,该文件确定了持久类与数据表,数据列之间的对应关系。
3、hibernate不再是直接调用JDBC访问,而是Session会话访问。在hibernate框架中,只有处于Seesion管理下的POJO对象才有持久化操作的能力。
体系架构:
几个关键对象:
SessionFatory:hibernate关键对象,它是单个数据库映射关系经过编译后的内存镜像,线程安全。主要作用是生成Session的工厂,该对象可以为事务之间可重用的数据提供可选的二级缓存。
Session:它是应用程序与持久储存层之间交互操作的一个单线程对象,是hibernate持久化操作的关键对象,所有的持久化对象必须在Session管理下才可以进行持久化操作。此对象的生命周期极短,底层封装了JDBC连接。Session对象持有一个必选的一级缓存,显式执行flush()之前,所有持久化操作的数据都缓存在Session对象处。
持久化对象:系统创建的POJO实例,一旦与特定的Session关联,并对应数据表的指定记录,该对象就处于持久化状态,这一系列对象都被称为持久化对象。在程序中对持久化对象执行的修改,都将自动被转换为持久层的修改。持久化对象完全可以是普通的JavaBean,唯一特殊的是他们正与一个Session关联。
事务(transaction):具有数据库事务的概念,Hibernate事务是对底层具体的JDBC、JTA、以及CORBA事务的抽象,在某些情况下,一个Transaction之内可能包含多个Session对象。虽然事务操作是可选的,但所有持久化操作都应该在事务管理下进行,即便是只读操作。
连接提供者(ConnctionProvider):生成JDBC连接的工厂,通过抽象将应用程序与底层的DataSource或DriverManager隔离开,该对象无须应用程序直接访问,仅在应用程序需要扩展时使用。注:实际开发中,很少有采用DriverManager来获取数据库连接, 通常都会使用DataSource来获取数据库连接。
事务工厂(TransactionFactory):是生成Transaction对象实例的工厂,该对象无须应用程序直接访问,它负责对底层具体的事务实现进行封装、将底层具体的事务抽象成Hibernate事务。
HibernateUtil类
多线程情况下共享数据库连接是不安全的。ThreadLocal保证了每个线程都有自己独立Session的对象,通过ThreadLocal类,既实现了多线程并发,同时,也实现了Singleton单例模式
SqlConnection connn 不应该定义为全局变量,不应该在多个线程之间共享数据库连接,
安全的做法是,每个线程在用到的时候new一个SqlConnection,这样不会有任何问题,因为现在数据库连接都使用连接池,new再多的connection都不会消耗太多资源,因为connection会循环利用,
package dao;import org.hibernate.HibernateException;import org.hibernate.Session;import org.hibernate.cfg.Configuration;/*** Configures and provides access to Hibernate sessions, tied to the* current thread of execution. Follows the Thread Local Session* pattern, see {@link http://hibernate.org/42.html }.*/public class HibernateUtil {/*** Location of hibernate.cfg.xml file.* Location should be on the classpath as Hibernate uses* #resourceAsStream style lookup for its configuration file.* The default classpath location of the hibernate config file is* in the default package. Use #setConfigFile() to update* the location of the configuration file for the current session.*/private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();/*** String a; 申明一个string类型的 a,即没有在申请内存地址,更没有在内存任何指向引用地址;String a = null ; 申明一个string类型的 a,同时在内存里申请了一个地址,但是该地址不指向任何引用地址;String a = "" ;申明一个string类型的 a,既在内存里申请了地址,该地址又指向一个引用该字符串的引用地址;*** */private static org.hibernate.SessionFactory sessionFactory=null;private static Configuration configuration = new Configuration();private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";private static String configFile = CONFIG_FILE_LOCATION;static {try {configuration.configure(configFile);sessionFactory = configuration.buildSessionFactory();} catch (Exception e) {System.err.println("%%%% Error Creating SessionFactory %%%%");e.printStackTrace();}}private HibernateUtil() {}/*** Returns the ThreadLocal Session instance. Lazy initialize* the <code>SessionFactory</code> if needed.** @return Session* @throws HibernateException*/public static Session getSession() throws HibernateException {Session session = (Session) threadLocal.get();if (session == null || !session.isOpen()) {if (sessionFactory == null) {rebuildSessionFactory();}session = (sessionFactory != null) ? sessionFactory.openSession(): null;threadLocal.set(session);}return session;}/*** Rebuild hibernate session factory**/public static void rebuildSessionFactory() {try {configuration.configure(configFile);sessionFactory = configuration.buildSessionFactory();} catch (Exception e) {System.err.println("%%%% Error Creating SessionFactory %%%%");e.printStackTrace();}}/*** Close the single hibernate session instance.** @throws HibernateException*/public static void closeSession() throws HibernateException {Session session = (Session) threadLocal.get();threadLocal.set(null);if (session != null) {session.close();}}/*** return session factory**/public static org.hibernate.SessionFactory getSessionFactory() {return sessionFactory;}/*** return session factory** session factory will be rebuilded in the next call*/public static void setConfigFile(String configFile) {HibernateUtil.configFile = configFile;sessionFactory = null;}/*** return hibernate configuration**/public static Configuration getConfiguration() {return configuration;}}
package dao;import org.hibernate.Session;public class DB {// 让多个动作,处于同一个事务当中public static Session session=dao.HibernateUtil.getSession();public static void begin() {//开启一个事务增删改操作必须,查询操作可选if(session!=null)session.beginTransaction();}public static void commit() {if(session!=null)session.getTransaction().commit();}public static void rollback() {if (session.getTransaction() != null)session.getTransaction().rollback();}}记得在web.xml 中配置<filter></filter>
新建java类的时候,实现javax.servlet.Filter接口即可; Open Session In View 模式 在web 应用中,使用jsp 从这个对象导航到与之关联的对象或集合数据,这些关联的对象或集合数据如果是被延迟加载的,Hibernate就会抛出LazyInitializationException 因为在调用完hibernate后,session对象已经关闭了,针对这个问题,hibernate社区提出了 OpenSessionInView模式: 在用户每一次请求过程中始终保持一个Session对象打开 使用OpenSessionInView的目的就是为了阻止它那么快的session
<filter><filter-name>EncodingFilter</filter-name><filter-class>ui.filter.EncodingFilter</filter-class></filter><filter-mapping><filter-name>EncodingFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping><!-- --><filter><filter-name>OpenSessionInViewFilter</filter-name><filter-class>ui.filter.OpenSessionInViewFilter</filter-class></filter><filter-mapping><filter-name>OpenSessionInViewFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>使用Hibernate 实现按主键查询
package ui.filter;import java.io.IOException;import javax.servlet.Filter;import javax.servlet.FilterChain;import javax.servlet.FilterConfig;import javax.servlet.ServletException;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import org.hibernate.Session;import dao.HibernateUtil;public class OpenSessionInViewFilter implements Filter {public void destroy() {}public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {Session session =HibernateUtil.getSession();try {System.out.println("打开连接!");chain.doFilter(req,res);} catch (Exception e) {e.printStackTrace();}finally{HibernateUtil.closeSession();System.out.println("连接关闭!");}}public void init(FilterConfig arg0) throws ServletException {}}session.save(dept);//新增 若是修改或删除得先从数据库里查出来 Dept d =(Dept)session.load(Dept.class,11);//修改 d.setDeptName("质量部"); session.getTransaction().commit(); JAVA对象的三种形态:
public void test13(){Session session=getSession();//查出包含此员工的部门String hql="from Dept d where ? in elements(d.emps)";Emp emp=(Emp)session. load (Emp.class, (short)7499);Emp emp2=(Emp)session. get (Emp.class, (short)7499);//load()方法与get方法的区别是 当没有此主键时get()方法返回Null值,但是load()方法抛出异常List<Dept> list=session.createQuery(hql).setParameter(0, emp).list();for (Dept dept : list) {System.out.println(dept.getDname());}closeSession();}
瞬时状态(Transient),持久化状态(Persistent)与游离状态(Detached)。
瞬态:对象由new操作符创建,且尚未与Hibernate Session关联的对象,被认为处于瞬态。Session对于瞬态对象的java对象是一无所知的,瞬态对象不会被持久化到数据库中,也不会被赋予持久化标示,如果程序中失去了瞬态对象的引用,瞬态对象将被垃圾回收机制销毁。使用Hibernate session可以让其变为持久化状态。 持久化:当对象与session关联,被Session管理时,他就处于持久状态,持久化实例在数据库中有对应的记录,并拥有一个持久化标识(identifier)。那么对象是什么时候与session发生关联的呢?有两种方式:第一种通过session的查询接口(get()和load()方法)从数据库中加载对象的时候,加载的对象与数据库表中的一条记录关联,此时对象与加载他的session发生关联,第二种就是瞬时状态的对象通过调用save()方法或SaveOrUpdate()方法。 持久化的实例可以是刚保存的。也可以是刚被加载的。无论那种,持久化对象都必须与指定的Hibernate Session关联。对于持久状态的对象Session会检测到处于持久化状态对象的改动,在当前操作执行完成时将对象数据写回数据库。开发者不需要手动执行update,delete ,save Hibernate 会选择合适的时机(如提交事务时)将变更的固话到数据库中。
游离:某个实例曾经处于持久化状态,但随着与之关联的session被关闭,该对象就变成游离状态了。游离对象的引用依旧有效,对象可以继续被修改,只是不会同步到数据库中。如果重新让游离对象通过session的update()方法或merge()方法等与session关联,该对象会重新转换为持久化状态。
hibernate脏检查及刷新缓存机制Session具有一个缓存,可以管理和跟踪所有持久化对象,对象数据和数据库中的相关记录对应,在某些时间点Session会根据缓存中对象的变化来执行相关的SQL语句,将对象包含的变化数据更新到数据库中,这一过程称为刷新缓存,换句话说就是将Session缓存同步刷新为与数据库一致
在Hibernate中,状态前后发生变化的对象,称为脏对象!
脏检查
Session到底是如何进行脏检查的呢?当一个Customer对象被加入到Session缓存中时,Session会为Customer对象的值类型的属性复制一份快照。当Session刷新缓存时,会先进行脏检查,即比较Customer对象的当前属性与它的快照,来判断Customer对象的属性是否发生了变化,如果发生了变化,就称这个对象是“脏对象”,Session会根据脏对象的最新属性来执行相关的SQL语句,从而同步更新数据库。
脏数据检查:
什么是脏数据?脏数据并不是废弃和无用的数据,而是状态前后发生变化的数据。我们看下面的代码:
Transaction tx=session.beginTransaction();
User user=(User)session.load(User.class,”1”);//从数据库中加载符合条件的数据
user.setName(“zz”);//改变了user对象的姓名属性,此时user对象成为了所谓的“脏数据”
tx.commit();
当事务提交时,Hibernate会对session中的PO(持久化对象)进行检测,判断持久化对象的状态是否发生了改变,如果发生了改变就会将改变更新到数据库中。这里就存在一个问题,Hibernate如何来判断一个实体对象的状态前后是否发生了变化。也就是说Hibernate是如何检查出一个数据已经变脏了。
这就是为什么持久化不需要手动update save delete 的原因
通常脏数据的检查有如下两种办法:
A、数据对象监控:
数据对象监控是通过拦截器对数据对象的setter方法进行监控来实现的,这类似于数据库中的触发器的概念,当某一个对象的属性调用了setter方法而发生了改变,这时拦截器会捕获这个动作,并且将改属性标志为已经改变,在之后的数据库操作时将其更新到数据库中。这个方法的优点是提高了数据更新的同步性,但是这也是它的缺点,如果一个实体对象有很多属性发生了改变,势必造成大量拦截器回调方法的调用,这些拦截器都是通过Dynamic Proxy或者CGLIB实现的,在执行时都会付出一定的执行代价,所以有可能造成更新操作的较大延时。
B、数据版本比对:
这种方法是在持久化框架中保存数据对象的最近读取版本,当提交数据时将提交的数据与这个保存的版本进行比对,如果发现发生了变化则将其同步跟新到数据库中。这种方法降低了同步更新的实时性,但是当一个数据对象的很多属性发生改变时,由于持久层框架缓存的存在,比对版本时可以充分利用缓存,这反而减少了更新数据的延迟。
在Hibernate中是采用数据版本比对的方法来进行脏数据检查的,我们结合下面的代码来讲解Hibernate的具体实现策略。
Transaction tx=session.beginTransaction();
User user=(User)session.load(User.class,”1”);
user.setName(“zz”);
tx.commit();
当调用tx.commit();时好戏就此开场,commit()方法会调用session.flush()方法,在调用flush()方法时,会首先调用flushEverything()来进行一些预处理(如调用intercepter,完成级联操作等),然后调用flushEntities()方法,这个方法是进行脏数据检查的关键。
在继续讲解之前,我要先来介绍一个内部数据结构EntityEntry,EntityEntry是从属于SessionImpl(Session接口的实现类)的内部类,每一个EntityEntry保存了最近一次与数据库同步的实体原始状态信息(如:实体的版本信息,实体的加锁模式,实体的属性信息等)。除了EntityEntry结构之外,还存在一个结构,这个结构称为EntityEntries,它也是SessionImpl的内部类,而且是一个Map类型,它以”key-value”的形式保存了所有与当前session实例相关联的实体对象和原始状态信息,其中key是实体对象,value是EntityEntry。而flushEntities()的工作就是遍历entityEntities,并将其中的实体对象与原始版本进行对比,判断实体对象是否发生来了改变。flushEntities()首先会判断实体的ID是否发生了改变,如果发生了改变则认为发生了异常,因为当前实体与EntityEntry的对应关系非法。如果没有发生异常,而且经过版本比对判断确实实体属性发生了改变,则向当前的更新任务队列中加入一个新的更新任务,此任务将在将在session.flush()方法中的execute()方法的调用中,转化为相应的SQL语句交由数据库去执行。最后Transaction将会调用当前session对应的JDBC Connection的commit()方法将当前事务提交。
脏数据检查是发生在显示保存实体对象时,所谓显示保存是指在代码中明确使用session调用save,update,saveOrupdate方法对实体对象进行保存,如:session.save(user);但是有时候由于级联操作的存在,会产生一个问题,比如当保存一个user对象时,会根据user对象的状态来对他所关联的address对象进行保存,但是此时并没有根据级联对象的显示保存语句。此时需要Hibernate能根据当前对象的状态来判断是否要将级联对象保存到数据库中。此时,Hibernate会根据unsaved-value进行判断。Hibernate将首先取出目标对象的ID,然后将ID与unsaved-value值进行比较,如果相等,则认为实体对象尚未保存,进而马上将进行保存,否则,则认为实体对象已经保存,而无须再次进行保存。比如,当向一个user对象新加入一个它所关联的address对象后,当进行session.save(user)时,Hibernate会根据unsaved-value的值判断出哪个address对象需要保存,对于新加入的address对象它的id尚未赋值,以此为null,与unsaved-value值相等,因此Hibernate会将其视为未保存对象,生成insert语句加以保存。如果想使用unsaved-value必须如下配置address对象的id属性:
……
<id name=”id” type=”java.lang.Integer” unsaved-value=”null”>
<generator class=”increment”/>
</id>
……
缓存清理机制
当Session缓存中对象的属性每次发生了变化,Session并不会立即清理缓存和执行相关的SQL update语句,而是在特定的时间点才清理缓存,这使得Session能够把几条相关的SQL语句合并为一条SQL语句,一遍减少访问数据库的次数,从而提高应用程序的数据访问性能。
在默认情况下,Session会在以下时间点清理缓存。
- 当应用程序调用org.hibernate.Transaction的commit()方法的时候.commit方法先清理缓存,然后再向数据库提交事务。Hibernate之所以把清理缓存的时间点安排在事务快结束时,一方面是因为可以减少访问数据库的频率,还有一方面是因为可以尽可能缩短当前事务对数据库中相关资源的锁定时间。
- 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,就会清理缓存,使得Session缓存与数据库已经进行了同步,从而保证查询结果返回的是正确的数据。
- 当应用程序显示调用Session的flush()方法的时候。
Session进行清理缓存的例外情况是,如果对象使用native生成器来生成OID,那么当调用Session的save()方法保存该对象时,会立即执行向数据库插入该实体的insert语句。
什么是Hibernate
ORM 对象关系数据库
名.hbm.
瞬时状态:自己new的对象,
持久状态:在session没关闭的时候的状态
游离状态:脱离了与其相关联的session
是因为save ,update,。。。。这些方法的态度是不一样的
持久状态:很多的操作被架空(delete除外),因为在持久状态下
在瞬时状态:忠实执行指令
对于延迟加载对象,要附加到新的session中,需要调用update和saveOrUpdate 方法(除了修改和新增外,还有附加作用(针对游离对象的))
延迟加载的情况下,会无厘头的见到update或者saveOrUpdate语句,其实是想把对象附加到最新的session中,
当报这个错的时候:org.hibernate.PropertyAccessException: Null value was assigned to a property of primitive type setter of entity.Emp.empComm
说明实体类的类型不是包装类,而是数值类型,当给数值类型赋予null值时就会报错
第五章:HQL 实用 技 术
Hibernate支持三种查询方式:HQL(Hibernate Query Language 查询语言)
Criteria 查询及原生的SQL 。hql是一种面向对象的查询语言,其中没有字段的概念,只有类,对象和属性的概念查询的是类名,类的字段属性 select 字段名 from 类名,Criteria查询又称为对象查询,它采用面向对象的方式构造查询。
业务层使用Criteria 就不需要字符串组装,稳重,让人有安全感,适合用于动 态查询语句
数据库层使用hql 相当的自由,虽然适合用户动态查询,但是比较麻烦
查询某个字段非空
String hql=" from Dept d where d.loc is not null";
使用表达式:
String hql=" from Dept d where lower(d.deptName)='sales' "
from Emp where year( hireDate)=1980 order by hireDate desc;
package test;
import static org.junit.Assert.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.junit.Test;
import dao.HibernateUtil;
import entity.RentDistrict;
import entity.RentHouse;
import entity.RentStreet;
public class RentTest {
//统计所有房屋的平均价格、最高价格、最低价格
@Test
public void test() {
Session session=HibernateUtil. getSession();
String hql="select avg(rh.price),min(rh.price),max(rh.price) from RentHouse rh";
List<Object[]> list=(List<Object[]>) session.createQuery(hql).list();
for (Object[] obj : list) {
System.out.println("平均价格: "+obj[0]+"|| "+"最低价格: "+obj[1]+"||"+"最高价格: "+obj[2]);
}
HibernateUtil.closeSession();
}
//统计各个街道的房屋的平均价格、最高价格、最低价格---有些街道是新建的,还没有房屋 这时要用左右连接 right join-- group by rs.name
@Test
public void test2() {
Session session=HibernateUtil. getSession();
String hql="select avg(rh.price),min(rh.price),max(rh.price) from RentHouse rh right join rh.rentStreet rs group by rs.name";
List<Object[]> list=(List<Object[]>) session.createQuery(hql).list();
for (Object[] obj : list) {
System.out.println("平均价格: "+obj[0]+"|| "+"最低价格: "+obj[1]+"||"+"最高价格: "+obj[2]);
}
HibernateUtil.closeSession();
}
//统计各个县区的房屋的平均价格、最高价格、最低价格 district 地区
@Test
public void test3() {
Session session=HibernateUtil. getSession();
String hql="select avg(rh.price),min(rh.price),max(rh.price) from RentHouse rh right join rh.rentStreet.rentDistrict rs group by rs.name";
List<Object[]> list=(List<Object[]>) session.createQuery(hql).list();
for (Object[] obj : list) {
System.out.println("平均价格: "+obj[0]+"|| "+"最低价格: "+obj[1]+"||"+"最高价格: "+obj[2]);
}
HibernateUtil.closeSession();
}
//上机练习二
//查询有50条以上的房屋信息的街道
@Test
public void test4() {
Session session=HibernateUtil. getSession();
String hql="select rs from RentStreet rs where rs.rentHouses.size>2";
List<RentStreet> list=( List<RentStreet>) session.createQuery(hql).list();
for (RentStreet rs : list) {
System.out.println("街道名: "+rs.getName());
}
HibernateUtil.closeSession();
}
//查询所有房屋租金高于2000元的街道 all()
@Test
public void test5() {
Session session=HibernateUtil. getSession();
String hql="select rs from RentStreet rs where 2000< all(select rh.price from rs.rentHouses rh)";
List<RentStreet> list=( List<RentStreet>) session.createQuery(hql).list();
for (RentStreet rs : list) {
System.out.println("街道名: "+rs.getName());
}
HibernateUtil.closeSession();
}
//查询至少有一条房屋的租金低于1000元的街道
@Test
public void test6() {
Session session=HibernateUtil. getSession();
String hql="select rs from RentStreet rs where 1000> any(select rh.price from rs.rentHouses rh)";
List<RentStreet> list=( List<RentStreet>) session.createQuery(hql).list();
for (RentStreet rs : list) {
System.out.println("街道名: "+rs.getName());
}
HibernateUtil.closeSession();
}
//统计各个街道房屋信息条数
@Test
public void test7() {
Session session=HibernateUtil. getSession();
String hql="select rs from RentStreet rs ";
List<RentStreet> list=( List<RentStreet>) session.createQuery(hql).list();
for (RentStreet rs : list) {
System.out.println("街道名: "+rs.getName()+"房屋条数:"+rs.getRentHouses().size());
}
HibernateUtil.closeSession();
}
//上机练习三
//使用原生的SQL查询指定的街道的房屋信息
@Test
public void test8() {
Session session=HibernateUtil. getSession();
String hql="select rh.* from RENT_HOUSE rh join RENT_STREET rs on rh.STREET_ID =rs.id where rs.name like :streetName";
List<Object[]> list=( List<Object[]>) session.createSQLQuery(hql).setString("streetName", "%中关村%").list();
for (Object[] rs : list) {
System.out.println("房屋信息:"+rs[3]+rs[4]);
}
HibernateUtil.closeSession();
}
//使用<query>命名查询查询所有的房屋信息
@Test
public void test9() {
Session session=HibernateUtil. getSession();
List<RentHouse> list =session.getNamedQuery(
"queryHouse").list();
for (RentHouse rentHouse : list) {
System.out.println(rentHouse.toString());
}
HibernateUtil.closeSession();
}
//使用<sql-query>命名查询查询指定街道的一定价格区间的房屋信息
@Test
public void test10() {
Session session=HibernateUtil. getSession();
List<RentHouse> list =session.getNamedQuery(
"queryHouse2")
.setString("streetName", "%中关村%")
.setDouble("from", 2000)
.setDouble("end", 2600).list();
for (RentHouse rentHouse : list) {
System.out.println(rentHouse.toString());
}
HibernateUtil.closeSession();
}
//使用Criteria 查询所有的房屋信息
@Test
public void test11() {
Session session=HibernateUtil. getSession();
Criteria c =session.createCriteria(RentHouse.
class);
List<RentHouse> list =c.list();
for (RentHouse h : list) {
System.out.println(h.toString());
}
HibernateUtil.closeSession();
}
//使用Criteria 查询价格大于1000元的房屋信息
@Test
public void test12() {
Session session=HibernateUtil.getSession();
Criteria c =session.createCriteria(RentHouse.
class,
"h")
.add(Restrictions.gt("h.price", 1000));
//c.setProjection(Projections.groupProperty("h.price"));
List<RentHouse> list =c.list();
for (RentHouse h : list) {
System.out.println(h.toString());
}
HibernateUtil.closeSession();
}
//使用Criteria 查询价格小于2000元并且发布时间在2013年5月以后的房屋信息
@Test
public void test13() throws ParseException {
Session session=HibernateUtil.getSession();
Criteria c =session.createCriteria(RentHouse.
class,
"h")
.add(Restrictions.lt("h.price", 2000))
.add(Restrictions.gt("h.pubdate", new SimpleDateFormat("yyyy-mm-dd").parse("2006-05-01")));
List<RentHouse> list =c.list();
for (RentHouse h : list) {
System.out.println(h.toString());
}
HibernateUtil.closeSession();
}
//使用Projections 统计各个街道的平均价格。
@Test
public void test14() {
Session session = HibernateUtil.getSession();
Criteria c = session.createCriteria(RentStreet.
class,
"s");
c.createAlias("s.rentHouses", "h");
c.setProjection(Projections.projectionList()
.add(Projections.groupProperty("s.name"))
.add(Projections.avg("h.price")));
List<Object[]> list = c.list();
for (Object[] obj : list) {
System.out.println("街道名:" + obj[0] + "||" + "平均价格" + obj[1]);
}
HibernateUtil.closeSession();
}
//使用Projections类 统计各个区县的房屋的平均价格,并按平均价格升序排序
@Test
public void test15() {
Session session = HibernateUtil.getSession();
Criteria c = session.createCriteria(RentDistrict.
class,
"d");
c.createAlias("d.rentStreets", "s");
c.createAlias("s.rentHouses", "h");
c.setProjection(Projections.projectionList()
.add(Projections.groupProperty("d.name"))
.add(Projections.avg("h.price").as("houseAvg")))
.addOrder(Order.asc("houseAvg"));
List<Object[]> list = c.list();
for (Object[] obj : list) {
System.out.println("区县名:" + obj[0] + "||" + "平均价格" + obj[1]);
}
HibernateUtil.closeSession();
}
//使用DetachedCriteria 查询所有的房屋信息
@Test //如果报初始错误就是@Test没写
public void test16() {
Session session = HibernateUtil.getSession();
DetachedCriteria c= DetachedCriteria.forClass(RentHouse.
class,
"h");
List<RentHouse> list = c.getExecutableCriteria(session).list();
for (RentHouse h : list) {
System.out.println(h.toString());
}
HibernateUtil.closeSession();
}
//分页显示房屋信息, 并按发布时间降序排序
@Test
public void test17() {
Session session = HibernateUtil.getSession();
Criteria c = session.createCriteria(RentHouse.
class,
"h")
.setProjection(Projections.count("h.id"));
Integer count = (Integer) c.uniqueResult();// 总行数
int pageSize = 3;//每页显示行数
int totalpages = (count % pageSize == 0 ? (count / pageSize) : (count/pageSize + 1));// 总页数
int pageIndex = 2;// 当前页号
c=session.createCriteria(RentHouse.
class,
"h")
.addOrder(Order.desc("h.pubdate"));
@SuppressWarnings("unchecked")
List<RentHouse> listHouse = c
.setFirstResult((pageIndex - 1) * pageSize)
.setMaxResults(pageSize).list();
for (RentHouse h : listHouse) {
System.out.println(h.toString());
}
System.out.println(" 总行数:" + count + " 总页数:" + totalpages + " 每页显示数:"
+ pageSize + " 当前页号:" + pageIndex);
HibernateUtil.closeSession();
}
|
query 的list()和Iterate()的区别 让我们先运行一下代码 实例:public void fillAll(){ Session session=HibernateUtil.getSession(); Query query=session.createQuery("from Customers"); List<Customers> list=query.list(); for(Customers l:list){ System.out.println(l.getEmail()); }
执行结果:hibernate: selectcustomers0_.id as id0_, customers0_.realName as realName0_, customers0_.pass aspass0_, customers0_.sex as sex0_, customers0_.petName as petName0_,customers0_.email as email0_, customers0_.rdate as rdate0_ from Customers customers0_
null asd null
@Test public void fillA(){ Session session=HibernateUtil.getSession(); Query query=session.createQuery("from Customers"); Iterator<Customers> it=query.iterate(); while(it.hasNext()){ Customers c=(Customers) it.next(); System.out.println(c.getEmail());
}
} 执行结果:
Hibernate: select customers0_.id as col_0_0_ from Customers customers0_
Hibernate: selectcustomers0_.id as id0_0_, customers0_.realName as realName0_0_,customers0_.pass as pass0_0_, customers0_.sex as sex0_0_, customers0_.petNameas petName0_0_, customers0_.email as email0_0_, customers0_.rdate as rdate0_0_from Customers customers0_ where customers0_.id=? null
Hibernate: selectcustomers0_.id as id0_0_, customers0_.realName as realName0_0_,customers0_.pass as pass0_0_, customers0_.sex as sex0_0_, customers0_.petNameas petName0_0_, customers0_.email as email0_0_, customers0_.rdate as rdate0_0_from Customers customers0_ where customers0_.id=? asd
Hibernate: selectcustomers0_.id as id0_0_, customers0_.realName as realName0_0_,customers0_.pass as pass0_0_, customers0_.sex as sex0_0_, customers0_.petNameas petName0_0_, customers0_.email as email0_0_, customers0_.rdate as rdate0_0_from Customers customers0_ where customers0_.id=? Null 很明显的看出:Iterator输入的sql语句多:N+1条语句
总结: Query的两个方法,list()和 iterate() , 两个方法都是把结果集列出来, 他们有3点不一样, 1:返回的类型不一样,list()返回List,iterate()返回Iterator, 2: 获取数据的方式不一样,list()会直接查数据库,iterate()会先到数据库中把id都取出来,然后真正要遍历某个对象的时候先到缓存中找,如果找不到,以id为条件再发一条sql到数据库,这样如果缓存中没有数据,则查询数据库的次数为n+1。
3:iterate会查询2级缓存, list只会查询一级缓存。 4: list()中返回的List中每个对象都是原本的对象,iterate()中返回的对象是代理对象.(debug可以发现)
list()方法在执行时,直接运行查询结果所需要的查询语句。 iterator()方法则是先执行得到对象ID的查询,然后在根据每个ID值去取得所要查询的对象。 因此:对于list()方式的查询通常只会执行一个SQL语句,而对于iterate()方法的查询则可能需要执行N+1条SQL语句(N为结果集中的记录数). 结果集的处理方法不同: list()方法会一次取出所有的结果集对象,而且他会依据查询的结果初始化所有的结果集对象。如果在结果集非常庞大的时候会占据非常多的内存,甚至会造成内存溢出的情况发生。 iterator()方法在执行时不会一次初始化所有的对象,而是根据对结果集的访问情况来初始化对象。一次在访问中可以控制缓存中对象的数量,以避免占用过多的缓存,导致内存溢出情况的发生。 如果:数据量巨大,可以使用Query.iterate() 方法 |
第六章: Hibernate 关 联 映 射
在软件开发中,类与类之间最为普遍的关系就是关联关系,而且关联是有方向的
从Dept 到Emp 是一对多关联,这意味着每个Dept 对象会引用一组Emp对象
同理
从Emp 到Dept 是多对一关联,这意味着每个Emp对象只会会引用一个Dept对象
package entity;
import java.util.HashSet;
import java.util.Set;
public class Dept implements
java.io.Serializable {
// Fields
private Integer deptno;
private String dname;
private String loc;
private Set emps = new HashSet(0);
// Constructors
/** default constructor */
public Dept() {
}
/** full constructor */
public Dept(String dname, String loc, Set emps) {
this.dname = dname;
this.loc = loc;
this.emps = emps;
}
// Property accessors
//省略其他get set
public Set getEmps() {
return this.emps;
}
public void setEmps(Set emps) {
this.emps = emps;
}
} |
package entity;
import java.util.Date;
public class Emp implements
java.io.Serializable {
// Fields
private Short empno;
private Dept dept;
private String ename;
private String job;
private Short mgr;
private Date hiredate;
private Double sal;
private Double comm;
// Constructors
/** default constructor */
public Emp() {
}
// Property accessors
//省略其他get set
public Dept getDept() {
return this.dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
|
数据之间的一对多或者一对一关系,通常涉及两张表,建立了主外键关联
而多对多关系,除了两张表以外,还需要一张额外的表,通过外键分别引用两张多方表
配置一对多双向关联
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="entity.Emp" table="EMP" schema="SCOTT">
<id name="empno" type="java.lang.Short">
<column name="EMPNO" precision="4" scale="0" />
<!--序列必须手动添加 -->
<generator class="sequence" >
<param name="sequence">seq_emp</param>
</generator>
</id>
<!-- name="dept" 对应的是Emp实体类里的Dept的引用 ,即小名-->
<many-to-one name="dept" class="entity.Dept" fetch="select">
<!-- -->
<column name="DEPTNO" precision="2" scale="0" />
</many-to-one>
<property name="ename" type="java.lang.String">
<column name="ENAME" length="10" />
</property>
<property name="job" type="java.lang.String">
<column name="JOB" length="9" />
</property>
<property name="mgr" type="java.lang.Short">
<column name="MGR" precision="4" scale="0" />
</property>
<property name="hiredate" type="java.util.Date">
<column name="HIREDATE" length="7" />
</property>
<property name="sal" type="java.lang.Double">
<column name="SAL" precision="7" />
</property>
<property name="comm" type="java.lang.Double">
<column name="COMM" precision="7" />
</property>
</class>
</hibernate-mapping>
|
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="entity.Dept" table="DEPT" schema="SCOTT">
<id name="deptno" type="java.lang.Integer">
<column name="DEPTNO" precision="6" scale="0" />
<generator class="sequence" >
<param name=""sequence"">seq_dept</param>
</generator>
</id>
<property name="dname" type="java.lang.String">
<column name="DNAME" length="50" />
</property>
<property name="loc" type="java.lang.String">
<column name="LOC" length="50" />
</property>
<!-- inverse="true"表示放弃维护权 ,要不然两个都会去维护,浪费空间占内存
cascade="all" 表示级联增删改
可取值:none、save-update、 delete、all
级联增删改时,要相互设置(即建立一对多关联)
emp1.setDept(d);
d.getEmps().add(emp1);
只有这样当保存dept时,会查看一下其所关联的对象有没有发生改变,
如果有则自动更新其所有的关联 的对象
session.save(d);
所以应该在one方的set<>标签里设置inverse="true",以提高应用程序的性能
当解出双向关联时:
emp1.setDept(null);
d.getEmps().remove(emp1);
-->
<set name="emps" inverse="true" cascade="all" order-by=" lower(ename) desc">
<key>
<!-- name="DEPTNO" 是emp表的字段 -->
<column name="DEPTNO" precision="2" scale="0" />
</key>
<one-to-many class="entity.Emp" />
</set>
</class>
</hibernate-mapping>
|
延迟加载:
有些时候我们只需要访问Dept对象而不需要访问Emp对象,如果使用及时加载,会造成浪费空间占内存
而且影响性能,因此hibernate 为我们提供了延迟加载策略,需要用的时候才去取
如果程序加载一个持久化对象的目的是为了访问他的属性,则可以采用立即加载。如果程序加载一个持久化对象的目的仅仅是为了获得它的引用,则可以采用延迟加载
用于设定加载策略的lazy属 性
类级别 | <class >元素中lazy属性:(默认为true) true:延迟加载、false:立即加载 |
一对多 | <set>元素中lazy属性:(默认为true) true:延迟加载、false:立即加载、extra:增强延迟加载 |
多对一 | <many-to-one>元素中lazy属性: proxy:延迟加载、no-proxy:无代理延迟加载、false:立即加载 |
因为类级别的加载策略是默认延迟加载的所以当
程序写下一句:
Dept d=(Dept)session.load(Dept.class, 120);
//如果既要使用延迟加载(不希望把下属拖出来),又要一开始就初始化,可以使用
Session session=HibernateUtil.getSession();
Dept d=(Dept)session.load(Dept.class, 120);
if(!Hibernate.isInitialized(d)){
Hibernate.initialize(d);
}
HibernateUtil.closeSession();
//这样即使是关闭session之后 dept成为了游离对象,也可以访问其属性
System.out.println(d.getDname()); |
值得注意的是,不管Dept.hbm.xml文件的<class>元素的lazy属性是true还是false,session的get()
和Query的list()在Dept的类级别总是使用立即加载的策略,举例说明如下
get()总是立即到数据库中查询Dept对象,如果存在则返回Dept对象,不存在返回null
及时加载:
@Test
public void test3() {
Session session=HibernateUtil.getSession();
Dept d=(Dept)session.get(Dept.class, 10);
for (Emp e :(Set<Emp>)d.getEmps()) {
System.out.println(e.getEname());
}
/*
session.get(Dept.class, 120)方法仅立即加载Dept对象
而没有立即加载Dept对象的emps属性,所以emps还是一个没有被初始化的
集合代理类实例,那么Dept对象的emps属性引用的集合代理类何时被初始化呢
主要包含以下两种情况:
(1)当调用它的Iterator()、size()、isEmpty()、或contains()方法
或者forEach 循环迭代
(2)通过Hibernate类的静态方法initialize()初始化他
if(!Hibernate.isInitialized((Set<Emp>)d.getEmps()))
Hibernate.initialize((Set<Emp>)d.getEmps());
*/
HibernateUtil.closeSession();
} |
延迟加载:
@Test
public void test4() {
Session session=HibernateUtil.getSession();
Dept d=(Dept)session.get(Dept.class, 10);
System.out.println("---------get结束--------------");
Set<Emp> emps= d.getEmps()
;
System.out.println(emps.size());
System.out.println("----------获取大小结束-------------");
System.out.println("是否为空:"+emps.isEmpty());
/*
配置lazy="true"和配置lazy="extra"唯一的区别就是:
当仅仅需要获取集合的size()、contains()、isEmpty()方法时配置lazy="extra"效率比较高,
因为 配置lazy="true" 时即使是获取size()也会去查数据库,从而初始化集合代理类
可以使用增强版的延迟加载: 在<set>元素中配置lazy值为"extra"
<set name="emps" inverse="true" lazy="extra"
cascade="all" order-by=" lower(ename
)
desc
">
<key>
<!-- name="DEPTNO" 是emp
表的字段 -->
<column name="DEPTNO" precision="2" scale="0" />
</key>
<one-to-many class="entity.Emp" />
</set>
*/
HibernateUtil.closeSession();
} |
延迟加载原理:
@Test
public void test5() {
//在<many-to-one>标签中配置无代理延迟加载lazy="no-proxy"
//使用无代理延迟加载
Session session=HibernateUtil.getSession();
Emp e=(Emp)session.get(Emp.class, (short)7788);//第一行
Dept d=e.getDept();//第二行
System.out.println(d.getDname());//第三行
/*
第一行加载的Emp
对象的
dept
属性为null
第二行触发执行查询DEPT表的select语句,从而加载Dept
对象
第三行获取结果
当 lazy="proxy
"时,可以延长延迟加载的
Dept
对象的时间,
而当lazy="no-proxy
"时,则可以避免使用由
Hibernate
提供的
Dept
代理类实例,
使Hibernate
对程序的提供更加透明的持久化服务
注意:
当 lazy="no-proxy
"时,需要使用工具增强持久化类的字节码,否则运行情况和
lazy="proxy
"时相同。增强持久化类的详细配置请查看
Hibernate
帮助文档
*/
HibernateUtil.closeSession();
} |
映射一对一关 联
比如员工账号和员工档案是一对一
按照主键映射
@Test
public void test2() {
//目的:实现保存保存QQ信息的同时分配一个QQ号
Session session=HibernateUtil.getSession();
//首先要先new一个QQInfo
QqInfo qqInfo=
new QqInfo(
"土豪辉",
true);
QqQq qq=new QqQq(new BigDecimal(25423027d), "123456", "我的大学女友是谁", "去问我小学的同桌");
qqInfo.setQqQq(qq);
qq.setQqInfo(qqInfo);
session.save(qqInfo);
session.save(qq);
HibernateUtil.closeSession();
} |
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="entity.QqInfo" table="QQ_INFO" schema="A_HR">
<!-- <cache usage="read-write"/> -->
<id name="qqid" type="java.lang.Integer">
<column name="QQID" precision="6" scale="0" />
<generator class="foreign" >
<!-- 主键更随里面的父对象 -->
<param name="property">qqQq</param>
</generator>
</id>
<one-to-one name="qqQq" class="entity.QqQq" constrained="true"/>
<property name="nickname" type="java.lang.String">
<column name="NICKNAME" length="40" />
</property>
<property name="sex" type="java.lang.Boolean">
<column name="SEX" precision="1" scale="0" />
</property>
</class>
</hibernate-mapping>
|
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!--
Mapping file autogenerated by MyEclipse Persistence Tools
-->
<hibernate-mapping>
<class name="entity.QqQq" table="QQ_QQ" schema="A_HR">
<id name="qqid" type="java.lang.Integer">
<column name="QQID" precision="6" scale="0" />
<generator class="sequence" >
<param name="sequence">seq_qq_qq</param>
</generator>
</id>
<property name="qqno" type="java.math.BigDecimal">
<column name="QQNO" precision="20" scale="0" />
</property>
<property name="qqpwd" type="java.lang.String">
<column name="QQPWD" length="20" />
</property>
<!-- <component name=""></component> -->
<property name="qqpwdQuestion" type="java.lang.String">
<column name="QQPWD_QUESTION" length="20" />
</property>
<property name="qqpwdAnswer" type="java.lang.String">
<column name="QQPWD_ANSWER" length="20" />
</property>
<one-to-one name="qqSay" class="entity.QqSay" property-ref="qqQq" cascade="all"/>
<set name="qqHobbies" table="QQ_QQHOBBY" schema="A_HR">
<key>
<column name="QQID" precision="6" scale="0" not-null="true" />
</key>
<many-to-many entity-name="entity.QqHobby">
<column name="HOBBYID" precision="6" scale="0" not-null="true" />
</many-to-many>
</set>
<!--cascade="all" 表示级联增删改 -->
<!-- property-ref 里写的 entity.QqInfo实体类里的QQ属性名-->
<one-to-one name="qqInfo" class="entity.QqInfo"
property-ref="qqQq"
cascade="all"/>
</class>
</hibernate-mapping>
|
在默认情况下多对一采用延迟加载,一对一采用迫切左外连接查询
组件映射:一张表分成两个实体类
Hibernate 缓存
二级缓存--把不太变化的表读出来之后,就把它缓存起来,下次继续使用,减少数据库的开销
一级缓存:session的缓存,session关闭后就清空缓存,session.clear()
二级缓存:sessionFactory级别的缓存,多次开关session都没有影响
论坛查找前几位,使用二级查询缓存
查询缓存是二级缓存的增强版
UI 缓存
业务层缓存
数据库层缓存
京东的首页就是UI缓存,有页面缓存的框架
缓存是计算机领域的概念,它介于应用程序(内存)和永久性数据存储源(硬盘),
缓存的作用: 其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用程序的运行性能
二、what(Hibernate缓存原理是怎样的?)
Hibernate缓存包括三大类:Hibernate一级缓存和Hibernate二级缓存和查询缓存
1.Hibernate一级缓存又称为“Session的缓存”。
一级缓存的主要作用是装配对象之间的关联,session关闭后就清空缓存,session.clear()
Session内置不能被卸载,Session的缓存是事务范围的缓存(Session对象的生命周期通常对应一个数据库事务或者一个应用事务)。一级缓存是必需的。
一级缓存中,持久化类的每个实例都具有唯一的OID。
位于缓存中的对象称为持久化对象,他和数据库中的相关对象相对应,session能够在某些时间点,按照缓存中对象的变化来执行相关的sql语句,从而同步更新到数据库,这一过程称为刷新缓存。
当应用程序调用session的save()、load()、query的list()等方法时,如果session的缓存中还不存在相应的对象,hibernate就会把该对象加入到一级缓存中,如果已经存在,就看该对象有没有发生变化,如果有,在刷新缓存时,hibernate会根据缓存中对象的状态的变化来同步更新数据库
session缓存的作用:
(1)减少访问数据库的频率
(2)保证数据库中相关的记录和缓存中的对象同步
2.Hibernate二级缓存又称为“SessionFactory的缓存”。SessionFactory缓存分为内置缓存和外置缓存
内置缓存是Hibernate自带的,不可拆卸,是只读缓存用来存放映射元数据和预定义sql 语句
外置缓存,是一个可配置的插件,默认下SessionFactory不会启用这个插件,外置缓存中的数据是数据库数据的复制,SessionFactory的外置缓存称为Hibernate的二级缓存,Hibernate提供了org.hibernate.cache.CacheProvider接口,它充当缓存插件与Hibernate之间的适配器。
由于SessionFactory对象的生命周期和应用程序的整个过程对应,因此Hibernate二级缓存是进程范围或者集群范围的缓存,有可能出现并发问题,因此需要采用适当的并发访问策略,该策略为被缓存的数据提供了事务隔离级别。
缓存的作用范围分为三类:
事务范围:
session的数据只能被当前的事务访问,所以session缓存内的数据不会被多个事务并发访问
进程范围:进程内所有的事务共享缓存,进程结束,缓存生命周期结束,
二级缓存就是进程范围或集群范围内的缓存
集群范围:缓存被一个或多个机器上的多个进程共享
什么样的数据适合存放到第二级缓存中?
1) 很少被修改的数据
2) 不是很重要的数据,允许出现偶尔并发的数据
3) 不会被并发访问的数据
4) 常量数据
不适合存放到第二级缓存的数据?
1) 经常被修改的数据
2) 绝对不允许出现并发访问的数据,如财务数据,绝对不允许出现并发
3) 与其他应用共享的数据。
3.Session的延迟加载实现要解决两个问题:正常关闭连接和确保请求中访问的是同一个session。
Hibernate session就是java.sql.Connection的一层高级封装,一个session对应了一个Connection。
http请求结束后正确的关闭session(过滤器实现了session的正常关闭);延迟加载必须保证是同一个session(session绑定在ThreadLocal)。
4.Hibernate查找对象如何应用缓存?
当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;
查不到,如果配置了二级缓存,那么从二级缓存中查;
如果都查不到,再查询数据库,把结果按照ID放入到缓存删除、更新、增加数据的时候,同时更新缓存。
5.一级缓存与二级缓存的对比图。
| 一级缓存 | 二级缓存 |
存放数据的形式 | 相互关联的持久化对象 | 对象的散装数据 |
缓存的范围 | 事务范围,每个事务都拥有单独的一级缓存 | 进程范围或集群范围,缓存被同一个进程或集群范围内所有事务共享 |
并发访问策略 | 由于每个事务都拥有单独的一级缓存不会出现并发问题,因此无须提供并发访问策略 | 由于多个事务会同时访问二级缓存中的相同数据,因此必须提供适当的并发访问策略,来保证特定的事务隔离级别 |
数据过期策略 | 处于一级缓存中的对象永远不会过期,除非应用程序显示清空或者清空特定对象 | 必须提供数据过期策略,如基于内存的缓存中对象的最大数目,允许对象处于缓存中的最长时间,以及允许对象处于缓存中的最长空闲时间 |
物理介质 | 内存 | 内存和硬盘,对象的散装数据首先存放到基于内存的缓存中,当内存中对象的数目达到数据过期策略的maxElementsInMemory值,就会把其余的对象写入基于硬盘的缓存中 |
缓存软件实现 | 在Hibernate的Session的实现中包含 | 由第三方提供,Hibernate仅提供了缓存适配器,用于把特定的缓存插件集成到Hibernate中 |
启用缓存的方式 | 只要通过Session接口来执行保存,更新,删除,加载,查询,Hibernate就会启用一级缓存,对于批量操作,如不希望启用一级缓存,直接通过JDBCAPI来执行 | 用户可以再单个类或类的单个集合的粒度上配置第二级缓存,如果类的实例被经常读,但很少被修改,就可以考虑使用二级缓存,只有为某个类或集合配置了二级缓存,Hibernate在运行时才会把它的实例加入到二级缓存中 |
用户管理缓存的方式 | 一级缓存的物理介质为内存,由于内存的容量有限,必须通过恰当的检索策略和检索方式来限制加载对象的数目,Session的evit()方法可以显示的清空缓存中特定对象,但不推荐 | 二级缓存的物理介质可以使内存和硬盘,因此第二级缓存可以存放大容量的数据,数据过期策略的maxElementsInMemory属性可以控制内存中的对象数目,管理二级缓存主要包括两个方面:选择需要使用第二级缓存的持久化类,设置合适的并发访问策略;选择缓存适配器,设置合适的数据过期策略。SessionFactory的evit()方法也可以显示的清空缓存中特定对象,但不推荐 |
Hibernate的缓存机制的应用
1. 一级缓存的管理:
evict :依法驱逐,依法回收
evict(Object obj) 将指定的持久化对象从一级缓存中清除,释放对象所占用的内存资源,指定对象从持久化状态变为脱管状态,从而成为游离对象。
clear() 将一级缓存中的所有持久化对象清除,释放其占用的内存资源。
contains(Object obj) 判断指定的对象是否存在于一级缓存中。
flush() 刷新一级缓存区的内容,使之与数据库数据保持同步。
2.一级缓存应用: save()。当session对象调用save()方法保存一个对象后,该对象会被放入到session的缓存中。 get()和load()。当session对象调用get()或load()方法从数据库取出一个对象后,该对象也会被放入到session的缓存中。 使用HQL和QBC等从数据库中查询数据。
public class Client{ public static void main(String[] args) { Session session = HibernateUtil.getSessionFactory().openSession(); Transaction tx = null; try { /*开启一个事务*/ tx = session.beginTransaction(); /*从数据库中获取id="402881e534fa5a440134fa5a45340002"的Customer对象*/ Customer customer1 = (Customer)session.get(Customer.class, "402881e534fa5a440134fa5a45340002"); System.out.println("customer.getUsername is"+customer1.getUsername()); /*事务提交*/ tx.commit(); System.out.println("-------------------------------------"); /*开启一个新事务*/ tx = session.beginTransaction(); /*从数据库中获取id="402881e534fa5a440134fa5a45340002"的Customer对象*/ Customer customer2 = (Customer)session.get(Customer.class, "402881e534fa5a440134fa5a45340002"); System.out.println("customer2.getUsername is"+customer2.getUsername()); /*事务提交*/ tx.commit(); System.out.println("-------------------------------------"); /*比较两个get()方法获取的对象是否是同一个对象*/ System.out.println("customer1 == customer2 result is "+(customer1==customer2)); } catch (Exception e) { if(tx!=null) { tx.rollback(); } } finally { session.close(); } }}
结果Hibernate: select customer0_.id as id0_0_, customer0_.username as username0_0_, customer0_.balance as balance0_0_ from customer customer0_ where customer0_.id=?customer.getUsername islisi-------------------------------------customer2.getUsername islisi-------------------------------------customer1 == customer2 result is true
输出结果中只包含了一条SELECT SQL语句,而且customer1 == customer2 result is true说明两个取出来的对象是同一个对象。其原理是:第一次调用get()方法, Hibernate先检索缓存中是否有该查找对象,发现没有,Hibernate发送SELECT语句到数据库中取出相应的对象,然后将该对象放入缓存中,以便下次使用,第二次调用get()方法,Hibernate先检索缓存中是否有该查找对象,发现正好有该查找对象,就从缓存中取出来,不再去数据库中检索。
3.二级缓存的管理:
evict(Class arg0, Serializable arg1)将某个类的指定ID的持久化对象从二级缓存中清除,释放对象所占用的资源。
sessionFactory.evict(Customer.class, new Integer(1));
evict(Class arg0) 将指定类的所有持久化对象从二级缓存中清除,释放其占用的内存资源。
sessionFactory.evict(Customer.class);
evictCollection(String arg0) 将指定类的所有持久化对象的指定集合从二级缓存中清除,释放其占用的内存资源。
sessionFactory.evictCollection("Customer.orders");
4.二级缓存的配置
常用的二级缓存插件
EHCache org.hibernate.cache.EhCacheProvider
OSCache org.hibernate.cache.OSCacheProvider
SwarmCahe org.hibernate.cache.SwarmCacheProvider
JBossCache org.hibernate.cache.TreeCacheProvider
<!-- EHCache的配置,hibernate.cfg.xml --><hibernate-configuration><session-factory><!-- 设置二级缓存插件EHCache的Provider类--><property name="hibernate.cache.provider_class"> org.hibernate.cache.EhCacheProvider </property><!-- 启动"查询缓存" --><property name="hibernate.cache.use_query_cache"> true </property></session-factory></hibernate-configuration>
<!-- ehcache.xml --><?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<!--
缓存到硬盘的路径
-->
<diskStore path="d:/ehcache"></diskStore>
<!--
默认设置
maxElementsInMemory : 在內存中最大緩存的对象数量。
eternal : 缓存的对象是否永远不变。
timeToIdleSeconds :可以操作对象的时间。
timeToLiveSeconds :缓存中对象的生命周期,时间到后查询数据会从数据库中读取。
overflowToDisk :内存满了,是否要缓存到硬盘。
-->
<defaultCache maxElementsInMemory="200" eternal="false"
timeToIdleSeconds="50" timeToLiveSeconds="60" overflowToDisk="true"></defaultCache>
<!--
指定缓存的对象。
下面出现的的属性覆盖上面出现的,没出现的继承上面的。
-->
<cache name="com.suxiaolei.hibernate.pojos.Order" maxElementsInMemory="200" eternal="false"
timeToIdleSeconds="50" timeToLiveSeconds="60" overflowToDisk="true"></cache>
</ehcache>
<!-- *.hbm.xml --><?xml version="1.0" encoding='UTF-8'?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" ><hibernate-mapping><class><!-- 设置该持久化类的二级缓存并发访问策略 read-only read-write nonstrict-read-write transactional--><cache usage="read-write"/></class></hibernate-mapping>
若存在一对多的关系,想要在在获取一方的时候将关联的多方缓存起来,需要在集合属性下添加<cache>子标签,这里需要将关联的对象的hbm文件中必须在存在<class>标签下也添加<cache>标签,不然Hibernate只会缓存OID。
<hibernate-mapping><class name="com.suxiaolei.hibernate.pojos.Customer" table="customer"><!-- 主键设置 --><id name="id" type="string"><column name="id"></column><generator class="uuid"></generator></id><!-- 属性设置 --><property name="username" column="username" type="string"></property><property name="balance" column="balance" type="integer"></property><set name="orders" inverse="true" cascade="all" lazy="false" fetch="join"><cache usage="read-only"/><key column="customer_id" ></key><one-to-many class="com.suxiaolei.hibernate.pojos.Order"/></set></class></hibernate-mapping>
@Test
public void test6() {
Session session=HibernateUtil.getSession();
session.beginTransaction();
Emp e=null;
for(int i=0;i<1000;i++){
e=new Emp(...);
session.save(e);
if(i%30==0){
session.flush();//每30条刷新缓存
session.clear();//紧接着清空缓存
}
}
session.getTransaction().commit();
HibernateUtil.closeSession();
/*
向Emp表插入1000条数据,每次批量插入30条Emp数据
如果希望提高批量插入的性能,可以在hibernate.hbm.xml文件中,
把hibernate.jdbc.batch_size属性设置为30,但要注意的是:
(1) Emp对象的主键生成器不能为identity,否则Hibernate会自动关闭
jdbc的批量执行操作,batch_size的设置就不起作用了
(2)在批量增删改时,可以 禁止使用二级缓存,因为使用了二级缓存,
一级缓存的对象会先复制到二级缓存中,再保存到数据库,这用会导致大量不必要的开销
一级缓存随着Session的开启而产生,随着session的关闭而结束,要合理的管理好缓存,
提高程序的效率,可以通过clear()、evict()方法来清除缓存中的对象
* */
} |
配置二级缓存
1:导入jar包:
commons-logging.jar
ehcache-1.2.3.jar
2:将ehcache.xml 复制到src 目录下,打开配置要二级缓存的实体
<ehcache>
<!-- Sets the path to the directory where cache .data files are created.
If the path is a Java System Property it is replaced by
its value in the running VM.
The following properties are translated:
user.home - User's home directory
user.dir - User's current working directory
java.io.tmpdir - Default temp file path -->
<diskStore path="java.io.tmpdir"/>
<!--Default Cache configuration. These will applied to caches programmatically created through
the CacheManager.
The following attributes are required for defaultCache:
maxInMemory - Sets the maximum number of objects that will be created in memory
eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the element
is never expired.
timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
if the element is not eternal. Idle time is now - last accessed time
timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
if the element is not eternal. TTL is now - creation time
overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
has reached the maxInMemory limit.
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<!--Predefined caches. Add your cache configuration settings here.
If you do not have a configuration for your cache a WARNING will be issued when the
CacheManager starts
The following attributes are required for defaultCache:
name - Sets the name of the cache. This is used to identify the cache. It must be unique.
maxInMemory - Sets the maximum number of objects that will be created in memory
eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the element
is never expired.
timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used
if the element is not eternal. Idle time is now - last accessed time
timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used
if the element is not eternal. TTL is now - creation time
overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache
has reached the maxInMemory limit.
-->
<!-- Sample cache named sampleCache1
This cache contains a maximum in memory of 10000 elements, and will expire
an element if it is idle for more than 5 minutes and lives for more than
10 minutes.
If there are more than 10000 elements it will overflow to the
disk cache, which in this configuration will go to wherever java.io.tmp is
defined on your system. On a standard Linux system this will be /tmp"
--个性化--写包的全名
-->
<cache name="entity.Emp"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<!-- 包名.实体名 -->
<!-- Sample cache named sampleCache2
This cache contains 1000 elements. Elements will always be held in memory.
They are not expired. -->
<cache name="entity.Dept"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/> -->
<!-- Place configuration for your caches following -->
</ehcache>
|
3:将下面的代码复制到 hibernate.cfg.xml
@Test
public void test6() {
Session session=HibernateUtil.getSession();
Dept e=(Dept)session.get(Dept.
class, 10);
HibernateUtil.closeSession();
session=HibernateUtil.getSession();
Dept e2=(Dept)session.get(Dept.
class, 10);
HibernateUtil.closeSession();
//在控制台只输出一条select语句说明二级缓存配置成功
有时考虑到内存开销的问题,需要关闭与二级缓存的交互,当使用session进行批量操作,可以使用
//
session.setCacheMode(CacheMode.IGNORE);//
关闭与二级缓存的交互 } |
@Test
public void test7() {
Session session=HibernateUtil.getSession();
session.setCacheMode(CacheMode.IGNORE);//
关闭与二级缓存的交互
Dept e=(Dept)session.get(Dept.class, 10);
HibernateUtil.closeSession();
session=HibernateUtil.getSession();
Dept e2=(Dept)session.get(Dept.class, 10);
HibernateUtil.closeSession();
//在控制台只输出两条select语句说明关闭与二级缓存的交互成功
} |