使用Hibernate 开发租房系统

4 篇文章 0 订阅
                                                                          使用Hibernate 开发租房系统
Oracle 是一个数据管理系统 ,和SQL Server一样是关系型数据库,安全性高,可为大型数据库提供更好的支持。
    Oracle  数据库的主要特点:
      1.支持多用户大事务量的事务处理
      2.在保存数据库安全性和完整性方面性能优越
      3.支持分布式数据处理,将分布在不同物理位置的数据库用通信网络连接起来,
          在分布式数据库管理系统的控制下 ,组成一个逻辑上统一的数据库,完成数据处理任务。
    4.具有可移植性,Oracle可以在Window、Linux 等多个操作系统平台上使用,而SQL Server 只能在window下使用
物理概念:是实实在在的实体,如某某文件
逻辑概念: 是逻辑名,如数据库、表空间
  Oracle的基本概念:
1:数据库,
     他是磁盘上存储数据的集合
 -----》
2:全局数据库名
     用于区分一个数据库的标志,它由数据库名称和域名构成
3:数据库实例
    每个数据库都对应一个数据库实例,数据库实例就是通过内存共享运行状态的一组服务器后台进程
4:表空间

 我们知道oarcle数据库真正存放数据的是数据文件(data files),Oarcle表空间(tablespaces)实际上是一个逻辑的概念,他在物理上是并不存在的,那么把一组data files 捻在一起就成为一个表空间。

表空间属性:

一个数据库可以包含多个表空间,一个表空间只能属于一个数据库

一个表空间包含多个数据文件,一个数据文件只能属于一个表空间

表这空间可以划分成更细的逻辑存储单元



----》每个Oracle数据库都是由若干个表空间构成的,这里的表空间(安照我的理解就是一个文件夹,文件下有多个表,多个表就是一个数据文件,如可以每两个表构成一个数据文件)
一个数据文件中可能存储很多个表的数据,而一个表的数据也可能存放在多个数据文件中,即数据库表和数据文件不存在一对一的关系
5:控制文件:
 ----》扩展名是.ctl,是一个二进制文件。控制文件存储的信息很多,其中包括数据文件和
日志文件的名称和位置。控制文件是数据库启动和运行所必须的文件
6:模式和模式对象:
----》模式是数据库对象的集合,如:表、索引、等
Oracle常用的3个服务:
1:OracleServiceSID服务:是Oracle数据库服务,此服务是由对应名为SID(系统标识符)的数据库实例创建的,其中SID 是在安装Oracle 11g 时输入的数据库名称,该服务是默认自启动,可自动启动数据库,该服务必须启动。
2:OracleOraDb11g_home1TNSListener服务是监听器服务,是Oracle服务器端的监听程序
该服务只有在数据库需要远程访问的时候才需要。
3:OracleDBConsoleSID是数据库控制台服务:用于企业管理器的程序
字符数据类型:
 char 数据类型:当需要固定长度的字符串时用,长度可以是1~2000字节,如果未指明大小,则默认占用1字节,如果用户输入的值小于指定的长度,数据库则用空格填充至指定的长度,如果大于指定的长度则报错
 varchar2 数据类型:当支持可变长度的字符串,长度可以是1~4000字节,可以节省空间
如:定义varchar2(30),如果用户输入10个字节,则会缩为10字节,而不会占用30个字节。
nchar() 和char() 唯一的区别就是,nchar()使用来存储Unicode字符集类型(占2个字节)
注意:
如果要指定存储几个字符,而不是字节可用varchar2(10 char)
 数值数据类型:当number(规定最大的位数p,精确到小数点的位数s)
有效位数:从左边起,第一个不为零的数,即科学计数法,小数点和符号不算,如果精确后的值的有效位<=p就正确,否则错误:
如:123.89         number(4,2)  精确后的值为123.89 ,有效位数为5 大于4,出错
12.897     number(4,2) 精确后的值为12.90 有效位数为4等于4,正确

Oracle 中的伪列:相当于一个列,但并未存储在表中,可以查到但不能增删改
rowid:返回该行地址
能以最快的方式访问表中一行
能显示表的行是如何存储的
可以作为表中行的唯一标识
rowNum:是一个总是从1开始的伪列,<n 是可以的,但是>=n 是不行的
数据定义语言:DDL:create、alter、 truncate(截断)、drop
truncate tableA只删除表中的记录而不删除表中的结构
 数据操纵语言:DML:增删改查
事务控制语言:TCL:commit、savepoint、 rollback
数据控制语言:DCL:grant(授权)to 、revoke(撤销授权)from
如果列别名中有特殊字符如:空格,要用双引号引起来
集合操作符:
union:联合查询:          
--按工资段统计人数--使用联合查询:将两个查询的结果合并起来,查询的结果构成一张新表,并删除重复的行
 --工资水平   员工人数
    --1000以下    2
    --工资水平   员工人数-----重复的行,干掉
    --1000-2000    6
SELECT * FROM 
(
  SELECT '1000以下' as 工资水平,count(1) AS 员工人数 FROM emp where sal<1000
  union
  SELECT '2000-3000' as 工资水平,count(1) AS 员工人数 
  FROM emp where sal between 2000 and 3000
  union
  SELECT '1000-2000' as 工资水平,count(1) AS 员工人数 FROM emp where sal between 1000 and 2000
  union
  SELECT '3000以上' as 工资水平,count(1) AS 员工人数 FROM emp where sal>3000
)  

可以对联合查询的结果进行排序 使用order by  时 必须要放在最后一个select 语句之后
union all:联合查询: 表示不干掉重复的行
intersect:交集操作符
  查找已退休但是还被公司聘用继续工作的员工编号
       select  empno from emp    intersect  select rempno from remp
minus:减集操作符

SQL Server 中不支持intersect 和minus 操作符,
但是可以用exists 和 not exists 来实现相同的效果
 select * from emp e where not exists(select * from remp r where c.empno=r.rempno)
拿c.empno 去remp 表里一个个匹配,如果没有匹配成功,not exists 返回真,则查找

如查找没有参加过考试的学生姓名就可以用这种方法--sql 语句有多种写法

"||" 操作符为连接操作符 相当于SQL server 的 “+”  
在Oracle 里头,字符串相加用 || 数字相加+

alter table dept 
add(deptId number(5)); 

alter table dept 
drop (deptId); 
--有时候报缺失右括号:可能的原因是:1:真的缺失右括号2是:有语法错误,可能是函数名称写错  
--作业
create tablespace tp_hr 
datafile  'E:orcl_datas\A_hr.dbf'--这个文件夹要存在
size 10autoextend on
next 32m  maxsize 2048m;
create tablespace tp_orders 
datafile  'E:orcl_datas\A_oe.dbf'
size 10autoextend on
next 32m  maxsize 2048m;
alter user A_oe
default tablespace tp_orders;
create sequence dept_seq
start with 60
increment by 10 
maxvalue 10000;
drop sequence dept_seq;
select * from dept;
insert into dept values(dept_seq.nextval,'zz','guangdong');
insert into dept values(dept_seq.nextval,'bb','hunan');
drop sequence dept_seq;
create table deptBak as select * from dept;
drop table deptBak;
select * from deptBak;
insert into deptBak values(dept_seq.nextval,'cc','hunan');
insert into deptBak values(dept_seq.nextval,'dd','hunan');
--为客户编号创建反向键索引
 create index index_reverse_customer_id on customers(customer_id) reverse;  
--为地域创建位图索引
create bitmap index index_bitmap_nls_territory  on customers(nls_territory);
-- 为名和姓氏创建组合索引
create index indx_t on customers(customer_last_name,customer_first_name);
--根据订单表创建范围分区表
create table rangeOrder 
(
  ORDER_ID     NUMBER(12) not null,
  ORDER_DATE   DATE not null,
  ORDER_MODE   VARCHAR2(8),
  CUSTOMER_ID  NUMBER(6) not null,
  ORDER_STATUS NUMBER(2),
  ORDER_TOTAL  NUMBER(8,2),
  SALE_REP_ID  NUMBER(6),
  PROMOTION_ID NUMBER(6)
)
partition by range(order_date)
(
   partition p1 values less than (to_date('2005/01/01','yyyy/mm/dd')),
   partition p2 values less than (to_date('2006/01/01','yyyy/mm/dd')),
   partition p3 values less than (to_date('2007/01/01','yyyy/mm/dd')),
   partition p4 values less than (to_date('2008/01/01','yyyy/mm/dd')),
   partition p5 values less than (to_date('2009/01/01','yyyy/mm/dd')),
   partition p6 values less than (maxvalue)
);
 select * from rangeOrder partition (p6);
 delete from rangeOrder partition (p6);

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)即可。

问题解决

 ----------------------------------------------------------------------------
 --练习
     --1.请将这些表建立在tb_bank表空间,由A_bank用户管理
     --使用system 用户登录
      create tablespace tb_bank 
      datafile  'E:orcl_datas\A_bank.dbf'
      size 10autoextend on
      next 32m  maxsize 2048m; 
     
       create user A_bank
       identified by A_bank 
       default tablespace  tb_bank
       temporary tablespace temp;
       grant connect ,resource ,create synonym,create public synonym to A_bank;
       
       --以下使用A_bank用户登录
      -- 2.为四个表建立主外键关系及各种约束
      
           /*客户表*/
    drop table Customers;
    Create  table Customers
    (
       cId number not null primary key ,
       cName varchar2(20) check (length(cName)>=2) not null,--客户姓名(建立约束只能是2 个字以上) 
       cSex number(1) check (cSex in (0,1)),--性别(建立约束只能是0 ,1)
       cBirthday date,--出生日期
       cIdentity varchar2(20) unique--证件号码(unique);
    );
     insert into Customers values(SEQ_Customers.Nextval,'张三',1,to_date('1992-09-09','yyyy-mm-dd'),'1111');
     insert into Customers values(SEQ_Customers.Nextval,'张莉莉',0,to_date('1999-08-09','yyyy-mm-dd'),'2222');
     select * from Customers;
     
        /*卡表*/
        
    drop table Cards;
    Create table Cards
    (
      cardid number not null primary key,
      cardNo char(3) not null unique,--卡号(建立唯一约束)
      cid number references Customers(cId) not null ,--客户ID
      siteName varchar(20),--网点名称
      balance number(10,2) check(balance >0),--余额(>=0 )
      state number(1)  default 1, check(state in(1,2,3))--位图索引,状态:1正常 2冻结 3已经销卡 (建立约束,只能是1,2,3,默认是1)
    );
     create bitmap index state on Cards (state);
    insert into Cards values(SEQ_Cards.Nextval,'123',7,'东风路网点',800.00,default);--如果有默认值,插入数据时要写default;
    insert into Cards values(SEQ_Cards.Nextval,'124',8,'北京路网点',10000.00,default);--如果有默认值,插入数据时要写default;
    select * from Cards;
        /*交易记录*/
    Create table Records
    (
      Rid number not null primary key ,--主键
      cardid number not null references Cards(cardid),--卡ID
      amount number(10,2) not null check(amount>0),--金额(必须大于0)
      inOrOut number(1) not null check(inOrOut in (0,1)),--收入或支出(只允许是0,1)
      recordType number(1) not null check(recordType in (1,2,3)),--帐务类型 1.现金 2.转账 3.工资 
      recordTime date--时间  
    );
    insert into Records values(SEQ_Records.Nextval,1,400.00,0,1,trunc(sysdate,'dd'));
    insert into Records values(SEQ_Records.Nextval,2,800.00,0,2,trunc(sysdate-13,'dd'));
    insert into Records values(SEQ_Records.Nextval,2,900.00,1,2,trunc(sysdate-10,'dd'));
     insert into Records values(SEQ_Records.Nextval,2,900.00,1,2,trunc(add_months(sysdate,-6),'dd'));
      insert into Records values(SEQ_Records.Nextval,2,1000.00,1,2,trunc(add_months(sysdate,-3),'dd'));
       insert into Records values(SEQ_Records.Nextval,2,1000.00,1,2,trunc(add_months(sysdate,3),'dd'));
        insert into Records values(SEQ_Records.Nextval,1,400.00,0,1,trunc(add_months(sysdate,3),'dd'));
        insert into Records values(SEQ_Records.Nextval,1,400.00,1,1,trunc(add_months(sysdate,-3),'dd'));
    select * from  records;
    /*历史记录表(开卡,销卡)*/
    Create table Historys
    (
      Id number not null primary key ,--主键
      cardid number not null references Cards(cardid),--卡id
      oprType number(1) check(oprType in (0,1)),--操作类型:1-开卡 0-销卡(只允许是0,1)
      oprTime date,--操作时间  
      oprName varchar2(20)--操作人
    );
    insert into Historys values(SEQ_Historys.Nextval,1,1,trunc(sysdate-20,'dd'),'刘全宽');
    insert into Historys values(SEQ_Historys.Nextval,2,0,trunc(sysdate-25,'dd'),'林建辉');
    select * from Historys;
     
    -- 创建4个序列为4张表新增时候作主键自增长 
     
     create sequence SEQ_Customers
      minvalue 1
     maxvalue 10000
     start with 1
     increment by 1
     cache 20;
     
      create sequence SEQ_Cards
      minvalue 1
     maxvalue 10000
     start with 1
     increment by 1
     cache 20;
     
    create sequence SEQ_Records
      minvalue 1
     maxvalue 10000
     start with 1
     increment by 1
     cache 20;
     
     create sequence SEQ_Historys
      minvalue 1
     maxvalue 10000
     start with 1
     increment by 1
     cache 20;
     
    
    --6.先建立私有同义词Customers-> cus Cards->ca Records->rec Historys->ht 方便查询
     -- grant create synonym  to A_bank;
       create synonym cus for Customers ;
       create synonym ca for Cards ;
       create synonym rec for Records ;
        create synonym ht for Historys ;
    
    --4.创建这些表和索引
    --规划哪些表要做成分区表,告诉老师理由
    /*
       ||交易记录表和历史记录表需要做成分区表
       
      ||因为交易记录表里有交易时间 recordTime ,需要建立范围分区,
        交易类型recordType ,需要建立列表分区
        
       ||历史记录表oprTime  需要建立范围分区,
    */
      
       
    --2)规划在哪些列应该建立索引,告诉老师理由,建立哪种类型的索引
     
     /*
      ||除主键列之外,位图索引的优点是适合低基数列(如:卡表的state )
       ||B 树索引适用于变化大,高基数列(如:客户表的客户名称)
       ||反向键索引:(解决热点问题,大家要查询的集中在某一地方 ,并发的负担很高,磁盘负担很大)
      */
      
     --1)查询本月内取款最多的用户有哪些
select sum(amount) ,c.cName ,r.cardid 
from  Records r ,customers  c, cards cd 
where  r.cardid=cd.cardid and c.cid=cd.cid and
r.inOrOut =1 and  r.recordTime between trunc(sysdate,'month') and trunc(last_day(sysdate)) 
group by c.cName, r.cardid ;
 --2)查询本年发生交易笔数最多的前10 名是哪些用户,并把排名打印出来
 select * from 
 (select rank() over(  partition by r.cardid order by r.cardid desc) rk,c.* 
   from  Records r ,customers  c, cards cd 
     where  r.cardid=cd.cardid and c.cid=cd.cid and to_char(r.recordTime,'yyyy')=to_char(sysdate,'yyyy')
     
 )where rk<=10;
 --3)根据卡号查询交易记录,按时间从高到低分页查询
select * from
 (
    select b.* ,rownum r from 
       (select rd.* from Cards cd,Records rd where rownum <=5 and cd.cardid=rd.cardid and cd.cardno='123'  order by rd.recordtime  desc) b
  )
where r>=0
 --4)查询本年各季度取款金额
 --方法一:
select '第一季度' as 季度, sum(amount) as 总金额
from Records r where inOrOut=1 and to_char(r.recordTime,'yyyy')=to_char(sysdate,'yyyy')
and r.recordTime between trunc(add_months(sysdate,-6),'q')  and trunc(add_months(sysdate,-3),'q')
union
select '第二季度' as 季度, sum(amount) as 总金额
from Records r where inOrOut=1 and to_char(r.recordTime,'yyyy')=to_char(sysdate,'yyyy')
and r.recordTime between trunc(add_months(sysdate,-3),'q')  and trunc(add_months(sysdate,0),'q')
union
select'第三季度' as 季度, sum(amount) as 总金额
from Records r where inOrOut=1 and to_char(r.recordTime,'yyyy')=to_char(sysdate,'yyyy')
and r.recordTime between trunc(add_months(sysdate,0),'q')  and trunc(add_months(sysdate,3),'q')
union
select '第四季度' as 季度, sum(amount) as 总金额
from Records r where inOrOut=1 and to_char(r.recordTime,'yyyy')=to_char(sysdate,'yyyy')
and r.recordTime between trunc(add_months(sysdate,3),'q')  and trunc(add_months(sysdate,+6),'q')
--方法二:
select to_char(r.recordTime,'q') as 季度,sum(amount) as 总金额
from  Records r where inOrOut=1 and to_char(r.recordTime,'yyyy')=to_char(sysdate,'yyyy')
group by to_char(r.recordTime,'q') 
select sysdate from dual--其中  sysdate 是系统函数
两个字符串型数字相加会自动转换
Oracle分页:
select * from (select b.*, rownum r from (select * from stuinfo where 
rownum <=10
 order by stuage) b) a where r>=6 


/*创建一个新的表,和原表一样,或是原表的一部分*/
   create table newStuInfo
as
select stuname, stuno from stuinfo  
/*创建一个新的表,和原表的字段一样,但不要数据*/
 create table newStuInfo
as
select * from stuinfo   where 1=2;  
Oracle 函数的分类:
Oracle 将函数大致分为单行函数聚合函数、和分析函数
单行函数可以大致分为字符函数日期函数数字函数转换函数以及其他函数
--转换函数
select sysdate from dual
select trunc(to_date('2017-1-1','yyyy-mm-dd')-sysdate,0) from dual

select (to_date('2017-1-1','yyyy-mm-dd')-sysdate)/7 from dual
select trunc(sysdate,'day') from dual
select round(sysdate,'year') from dual

select add_months(sysdate ,-5) from dual
select to_char(sysdate,'yyyy')-18 from dual
select to_char(sysdate,'yyyy"年"fmMM"月"fmDD"日"hh24:mi:ss')from dual 
使用了填充模式fm 格式掩码来避免空格填充和数字零填充 

不使用fm,则输出

select last_day(sysdate)+1 from dual
select trunc(last_day(sysdate)+1,'dd') as 下月初 from dual

select  extract  (month from   timestamp   to_date('2016-3-3','yyyy-mm-dd')) 月份 from dual; 
--如果是字符串要声明为 时间戳 ,--使用时间戳一定要包含时分秒,要不然要声明为 date
select extract(
month  
from  timestamp '2001-2-16 2:38:40' ) 月 from dual; 
select extract (year from  date  '2001-2-16') 年份 from dual; 
--如果已经是时间就不用写关键字
select extract (month from  sysdate ) 月份 from dual; 

select * from scott.emp
select ename ,sal+nvl(comm,0) from scott.emp--为空取0
select ename ,sal+nvl2(comm,1000,500) from scott.emp--为空取500 非空去1000
select decode(to_char(hiredate,'MM'),'01','一月','02','二月''03','三月') from scott.emp  
--如果有两并列,第三名就没了,直接跳第四,第一第二第四

****/
--删除stuName,stuAge 列重复的行,也就是防止重复录入
delete from stuInfo  where rowid not in
(
   select max(rowid) from stuInfo group by stName,stuAge having (count(1)>=1)
);--能用rowid 伪列的会提高效率    

--取出stuName、stuAge列不存在重复数据的记录
--反过来想,就是要找出行数小于2的
select stuName,stuAge from stuInfo group by stuName,stuAge having(count(stuName||stuAge)<2)    
其他单行函数:
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,则返回二月
........
  
--分析函数:
是对一组查询结果进行运算,然后获得结果,从这个意义上说,分析函数非常类似于聚合函数,区别在于分析函数每个组返回多行,聚合函数每个组返回一行
语法:
函数名([参数]) over([分区子句] [排序子句])
--这里的partition by 相当于group by  
--排名会跳跃  例如1,2,4  
select ename ,sal, rank() over(partition by deptno order by sal desc) from scott.emp
--dense 意为“密集的”,遇到相同排名时,不会跳跃
select ename ,sal, dense_rank() over(order by sal desc) from scott.emp
select ename ,sal, row_number() over(order by sal desc) from scott.emp--不会出现并列  

--在case....when ...then  end
nvl()
nvl2()
decode
empno(主键)
rowid (优化手段)
   
   
分页 top top  或者row_number() 各个数据库是不同的(MySQL limit 5,10)
rownum (嵌套3层)
日期处理:getdate() datediff() dateadd() datepart()
sysdate
 日期的加减
天数差*24*60... 数学运算达到目标
月份months_between()  add_months()
忽略部分尾数 trunc 
 round next_day() last_day()  

--人性化intersect 的使用
select * from emp;
select ename ,job from emp intersect select ename ,job from retireEmp
select * from emp exists(select null from retireEmp where retireEmp .ename=emp.ename and retireEmp .job=emp.job);
select ename ,job from emp
 minus
 select ename ,job from retireEmp
 
 select * from emp e left join retireEmp r on r.ename=e.empname
 where r.empno is null    

 

Sql语句:

 

 

 

 

 

Sql语句:

 

 

 

 

 

 

 

Sql语句:

 

 

 

 

 

 

Sql语句:

 

 

 

 

 

Sql语句:

 

 

 

 

 

 

Sql语句:

 

 

 

 

 

 

Sql语句:

 

 

 

 

 

Sql语句:

 

 

 

 

 

Sql语句:

 

 

 

 

 

 

 

SQL语句:

 

 

 

 

Sql语句:

 

 

 

 

 

SQL语句:

 

 

 

 

 

Sql语句:




答案:

--找出收入最高的前3位员工,--?并列的也找出来
 select  b.ename ,b.sal from (select distinct e.ename ,e.sal from emp e  order by e.sal desc) b where rownum<=4 order by b.sal desc
 
 select * from 
  (select ename ,sal, rank() over(order by sal desc) topTree from emp)
  where topTree<=4;--只有第一第二,第四
 
 /*select *
  from (select e.*,
               row_number() over(partition by e.deptno order by sal desc) rn --这里的partition by 相当于group by,其中partition by 可以不写
          from emp e)
 where rn <= 3;*/
 
--找出每个部门的前三名
 select * from (select rank() over(partition by emp.deptno order by emp.sal desc) rk,emp.* from emp) t
where t.rk<=3;  
--按收入排序,取6-10行的数据
select * from
 (
    select b.* ,rownum r from 
       (select * from emp  where rownum <=10 order by emp.sal ) b
  )
where r>=6
--为每个部门的员工 排 收入的名次
--e.sal+nvl(comm,0)
select e.deptno 部门, e.ename 员工姓名 ,e.sal 薪资 from emp e group by e.deptno, e.ename ,e.sal order by e.deptno ,e.sal desc
--请找出员工的领导信息
  --A员工---领导B
  --B员工---领导B
select e.ename 员工, d.dname 领导 from emp e join dept d on e.deptno=d.deptno;
--按工资段统计人数
    
SELECT * FROM 
(
  SELECT 1000||'以下' as 工资水平,count(1) AS 员工人数 FROM emp where sal<1000
  union
  SELECT 2000||'-'||3000 as 工资水平,count(1) AS 员工人数 
  FROM emp where sal between 2000 and 3000
  union
  SELECT 1000||'-'||2000 as 工资水平,count(1) AS 员工人数 FROM emp where sal between 1000 and 2000
  union
  SELECT 3000||'以上' as 工资水平,count(1) AS 员工人数 FROM emp where sal>3000
)
--人数超过3人的部门有哪些?
select e.deptno,  count(0) 人数 from emp e  group by e.deptno having (count(0)>3);
--平均工资大于2500元的部门有哪些?
select e.deptno  from emp e group by e.deptno having (avg(e.sal)>2500);
--
--打印出各部门各工资段的人数
--统计收入水平人数
SELECT * FROM 
(
  SELECT '0元以上' as 工资水平,count(1) AS 人数 FROM emp where sal>0
  union
  SELECT '1000元以上' as 工资水平,count(1) AS 人数  FROM emp where sal >=1000
  union
  SELECT '2000元以上' as 工资水平,count(1) AS 人数 FROM emp where sal >=2000
   union
  SELECT '3000元以上包括3000' as 工资水平,count(1) AS 人数 FROM emp where sal >=3000
)
 select * from emp
 select * from dept
 --查找同时担任10号和20号的部门的领导--分身术
 select r.mgr from ((select  * from emp  where emp.deptno=10 ) r
   join (select  * from emp  where emp.deptno=20 ) a on r.mgr=a.mgr)  






分身术:

--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 数据库应用        


表空间:是数据库逻辑结构的一个重要组件,表空间可以存放各种应用对象,如:表、索引
表空间的分类:
   1:永久性表空间:
         一般用于保存表、视图、过程、索引等的数据
 2: 临时性表空间:
    3:撤销表空间:
表空间的目的:
对不同的用户分配不同的表空间,方便用户对数据的操作、对模式对象的管理
可以将不同的数据文件创建到不同的磁盘中,有利于管理磁盘的空间
,有利于提高IO的性能,有利于备份和回复数据
--创建表空间:
create tablespace tb_driver 
datafile  'D:orcl_datas\driver.dbf'--这个文件夹要存在
size 10autoextend on;

create tablespace tb_manager 
datafile  'E:orcl_datas\driver.dbf'--这个文件夹要存在
size 10autoextend on;
next 32m  maxsize 2048m;

1:Scott:
   Scott用户是Oracle数据库的一个示范用户,一般在数据库安装是创建,Scott 用户模式包含四个示范表,分别是emp 、dept、 等使用Users 表空间存储模式对象   通常情况下,出于安全考虑,对不同数据表需要设置不同的访问权限,此时就要创建不同的用户。Oracle中的create user 命令用于创建新用户,每一个用户都有一个默认的表空间和临时空间。 如果没有指定,Oracle就将users 设为默认表空间,将temp 设置为临时表空间
2:System:
System用户是Oracle数据库中默认的系统管理员,他拥有DBA权限。该用户拥有Oracle 管理工具使用的内部表和视图。 通常通过System用户管理Oracle数据库的用户、权限和存储等。 不建议在System用户中创建用户表 System用户不能以SYSOPER或SYSSBA角色登录系统,只能以默认即normal的方式登录
3:Sys:
Sys用户是Oracle中的一个超级用户。数据库中所有数据字典和视图都存储在Sys用户中,数据字典存储了用来管理数据库对象的所有信息,是Oracle数据库中非常重要的系统信息,Sys用户主要用来维护系统信息和管理实例。 Sy s用户只能用 SYSOPER或SYSSBA角色登录系统




Oracle 新建用户

create user zhangsan 
 identified by 123 
default   tablespace  users
temporary tablespace temp;
grant   connect  , resource   to  zhangsan;

Oracle 11g中修改被锁定的用户:scott

提示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

序列:

序列是用来生成唯一、连续的整数的数据库对象。序列通常用来自动生成主键或唯一键的值。序列可以按升序排也可以按降序排列,简单的说就是自动增长列,流水号

创建序列:

--弄一个自动增长器
 create sequence seq
 minvalue  --必须小于或等于 start  with 而且要小于 maxvalue 
 maxvalue 999999999
 start with 1   --指定要生成的第一个序列号,升序默认值为序列的最小值,降 序默认值为序列的最大值
 increment by  --增长幅度,如果n>0 升序排,n<0 降序排
 cache 20  --默认缓存20个序列 , 写与不写一个样
   nocycle ;   --默认的的选项,写与不写一个样
 访问 序列:
   创建了序列之后可以用nextval 和 currval 伪列来访问该序列的值。
   可以从伪列中选择值,但是不能操纵他们的值。
   nextval :
   创建序列后第一次使用nextval时,将返回该序列的初始值。以后再使用nextval 时,
   将使用increment by 子句来增加序列的值,并返回这个新值
  currval:
返回序列的当前值,即最后一次引用nextva时返回的值
 insert into driver_student values(seq .nextval,'张三丰');
  insert into driver_student values(seq .nextval,'陈强');
  select seq .Currval from dual
更改 序列:
  基本与创建序列一致,其中 start with 参数不能改,如果非要更改,只能先删除突这个序列然后
再创建一个相同名称的的序列:drop sequence seq;
使用序列:
  不适合将它用作并行或者远程环境里的主关键字,因为各自的环境里的序列可能会生成相同的数字,从而导致冲突的发生。所以在不需要并行的环境中。可选择使用序列作为主关键字
   还可以使用sys_guid() 生成32位唯一的编码作为主键。它源自不需要对数据库进行访问的时间戳和机器标识符,但管理 sys_guid() 生成的值比较困难
SQL> select sys_guid() from dual;
 
SYS_GUID()
--------------------------------
9FBE3B8426954B2F8E425C67FE21D988  

  使用序列设置主关键字时,在数据库迁移时需要特别注意:
   由于迁移后的表中已经存在数据,如果不修改序列的起始值,将会在表中插入重复的数据,
     违背主键约束,所以在创建序列时要修改序列的起始值

同义词:synonym
  同义词的用途:
  》》简化SQL语句
  》》隐藏对象的名称和所有者
  》》为分布式数据库的远程对象提供了位置的透明性。
  》》提供对对象的公共访问
  同义词可分为:
  私有同义词和公有同义词
 私有同义词只能被当前模式的用户访问。私有同义词名称不可与当前模式的对象名称相同,
要在 当前模式下创建私有同义词,用户必须拥有create synonym 系统权限,若要在其他用户模式下
创建私有同义词, 用户必须拥有create any synonym 系统权限
create   or  replace synonym syn for emp ;--为哪张表创建同义词
先在system模式下为用户授予 create synonym 权限
connect system/system;
grant create synonym to A_oe; 
  --以A_hr登录
connect A_hr/A_hr;   
  --赋予A_oe 查看 A_hr的employee表的权利
  grant  select  on    A_hr.employee to  A_oe;  
   --在A_oe 模式下为  A_hr.employee 表创建同义词
    create synonym sy_emp for A_hr.Employee;
 --查看所创建的同义词:
    select * from sy_emp;

公有同义词:
  公有同义词可被所有的数据库用户访问。公有同义词可以隐藏数据库对象的所有者和名称,并降低SQL语句的复杂性。要创建公有同义词必须拥有create public synonym
系统权限。

公有同义词和私有同义词的区别:
  私有同义词只能在当前模式下访问,且不能与当前模式的对象同名。
  公有同义词可被所有的数据库用户访问,可与 当前模式的对象同名。
问题:对象(如表)、私有同义词、公有同义词三者是否可以同名?
答:对象和私有同义词不能同名,对象和公有同义词同名时,数据库优先选择对象作为目标,
私有同义词和公有同义词同名时,数据库优先选择私有同义词作为目标
删除同义词:
要删除同义词,用户必须有相应的权限,drop synonym sy_emp;
已经创建了公有同义词,但在其他用户访问时报没有此表或视图的解决方法:
    》》》仅有公有同义词是不够的,其他用户还应该有访问此表的权限
 查看 同义词:
SELECT * FROM ALL_SYNONYMS WHERE SYNONYM_name=Upper('p_sy_dept');  
索引是一种与表相关联的、有序的、可以快速访问数据的可选结构,是数据库优化的手段
索引:数据库优化的手段
原理是:有顺序。能够快速定位,这个就像书本的目录页,有对应的第几页,像数组,通过下标定位, 查询比较快!
理解生活你就理解程序
缺点:--就是数组的缺点
增删改的局限---如果有十个副本,就会形成十个负担
索引是与表关联的可选结构,是一种快速访问数据的途径,可提高数据库的性能。数据库可以明确地创建索引,以加快对表执行的SQL语句的速度。当索引键作为查询条件时,该索引将直接指向包含这些值的行的位置。即便删除索引,也无需修改任何SQL的定义。
索引的分类:
                                          物理分类                       逻辑分类
                                  分区或非分区索引                   单列或组合索引(最多包含32列)
                                   B树索引(标准索引)                  唯一或非唯一索引
                                    正常或反向键索引                   基于函数索引
                                             位图索引 
B树索引:变化大
B树索引通常 也称为标准索引。索引的顶部为根,其中包含指定索引中下一级的项。下一级为分支块
,分支块又指向索引中下一级的块,最低一级为也节点,其中包含指定表行的索引项, 叶块为双向链接,有助于按关键字值的升序和降序扫描索引。B树索引类似于SQL Server 中 非聚集索引。
create unique index index_unique_grande on salgrade(grade); 
反向键索引:(解决热点问题,大家要查询的集中在某一地方 ,并发的负担很高,磁盘负担很大)
与常规B树索引相反,反向键索引在保持在保持列顺序 的同时反转索引列的字节、
反向键索引通过反向键索引的数据值来实现。
优点:对于连续增长的索引列,反向索引列,
      可以将索引数据分散在多个索引块间,减少IO 瓶颈的发生,
       反向键索引通常建立在一些值连续增长的列上,
       如系统生成的员工编号,但不能执行范围搜索
--创建反向索引:
缺点:
增删改的局限---
 create index index_reverse_empno on employee(empno) reverse;  
位图索引:
位图索引的优点是适合低基数列(如:男女 工种,只有几种的)

数据库索引优缺点

创建索引可以大大提高系统的性能:
第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 
第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。 
第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 
第四,在使用分组和排序 子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。 
第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。 

增加索引也有许多不利的方面:
第一,创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 
第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。 
第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 
创建索引的原则:
索引是建立在数据库表中的某些列的上面。因此,在创建索引的时候,应该仔细考虑在哪些列上可以创建索引,在哪些列上不能创建索引。一般来说,应该在这些列上创建索引,例如: 

在经常需要搜索的列上,可以加快搜索的速度; 
在作为主键的列上,强制该列的唯一性和组织表中数据的排列结构; 
在经常用在连接的列上,这 些列主要是一些外键,可以加快连接的速度; 
在经常需要根据范围进行搜索的列上创建索引,因为索引已经排序,其指定的范围是连续的; 
在经常需要排序的列上创 建索引,因为索引已经排序,这样查询可以利用索引的排序,加快排序查询时间; 
在经常使用在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.

Look for cases where:

§ 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)提高数据的安全性:将不同的分区分布在不同的磁盘,可以减少所有分区数据同时损坏的可能性

符合以下条件的表可以建成分区表:
  (1)数据量大于2GB
   (2)新旧数据有明显的界限
注意:要分区的表不能具有long 和long raw 数据类型
分区表的分类:
范围分区、列表分区、散列分区(hash 分区)、复合分区、间隔分区、虚拟分区
其中间隔分区和虚拟分区是 Oracle 11 grid 才有的
新建表的时候,有必要建成分区表吗,可以建成分区表吗?
create table driver_scores
(
  id number(6) primary key ,
  driverid number(6),
  result number(3),
  examdate date,
  
)partition by range(examdate)
(
     partition p1 values less then (to_date('2015-1-1','yyyy-mm-dd')),
          partition p2 values less then (to_date('2015-1-1','yyyy-mm-dd')),
               partition p3 values less then (to_date('2015-1-1','yyyy-mm-dd')),
                    partition p4 values less then (maxvalue) tb_driver
);
create table driver_scores2
(
  id number(6) primary key ,
  driverid number(6),
  result number(3),
  examdate date,
  
)partition by range(examdate)
(
     partition p1 values less then (to_date('2015-1-1','yyyy-mm-dd'))
          partition p2 values less then (to_date('2015-1-1','yyyy-mm-dd'))
               partition p3 values less then (to_date('2015-1-1','yyyy-mm-dd'))
                    partition p4 values less then (maxvalue) tb_driver;
)
as select * from driver _scores 
--分区好处:搜索快捷,把数据在安排插入,磁头,按范围来划分
drop tablespace tb_manager including contents and datafiles
--刘洋登录管理驾驶员信息
create table driver_student 
(
   id number(6) primary key ,
   name nvarchar2(20),
   
   
)tablespace tb_driver;--一个用户是可以管理几个表空间的                                                                 



首先:是在system 模式下创建新用户,然后授权 grant connect ,resource to 新用户
然后切换到新用户 --connect 新用户名/密码

                       第三章:PL/SQL 编程
PL/SQL也是一种程序语言,叫做过程化SQL语言(Procedural Language/SQL)。PL/SQL是Oracle数据库对SQL语句的扩展。在普通SQL语句的使用上增加了编程语言的特点,所以PL/SQL就是把数据操作和查询语句组织在PL/SQL代码的过程性单元中,通过逻辑判断、循环等操作实现复杂的功能或者计算的程序语言。

是Oracle对标准数据库语言SQL的过程化扩充,它将数据库技术和过程化程序设计语言联系起来,是一种应用开发语言,可使用循环,分支处理数据,将SQL的数据操纵功能与过程化语言数据处理功能结合起来.PL/SQL的使用,使SQL成为一种高级程序设计语言,支持高级语言的块操作,条件判断,循环语句,嵌套等,与数据库核心的数据类型集成,使SQL 的程序设计效率更高.
⒈1 PL/SQL的作用
使用PL/SQL可以编写具有很多高级功能的程序,虽然通过多个SQL语句可能也能实现同样的功能,但是相比而言,PL/SQL具有更为明显的一些优点:
⒈能够使一组SQL语句的功能更具模块化程序特点;
⒉采用了过程性语言控制程序的结构;逻辑结构判断、循环等
⒊可以对程序中的错误进行自动处理,使程序能够在遇到错误的时候不会被中断;
⒋具有较好的可移植性,可以移植到另一个Oracle数据库中;
⒌集成在数据库中,调用更快;
⒍减少了网络的交互,有助于提高程序性能。
通过多条SQL语句实现功能时,每条语句都需要在客户端和服务端传递,而且每条语句的执行结果也需要在网络中进行交互,占用了大量的网络带宽,消耗了大量网络传递的时间,而在网络中传输的那些结果,往往都是中间结果,而不是我们所关心的。
而使用PL/SQL程序是因为程序代码存储在数据库中,程序的分析和执行完全在数据库内部进行,用户所需要做的就是在客户端发出调用PL/SQL的执行命令,数据库接收到执行命令后,在数据库内部完成整个PL/SQL程序的执行,并将最终的执行结果反馈给用户。在整个过程中网络里只传输了很少的数据,减少了网络传输占用的时间,所以整体程序的执行性能会有明显的提高。
⒈2 PL/SQL程序的基本结构
PL/SQL块由四个基本部分组成:声明、执行体开始、异常处理、执行体结束。
下面是四个部分的基本结构:
DECLARE —— 可选部分
变量、常量游标、用户定义异常的声明
……
BEGIN —— 必要部分
SQL语句和PL/SQL语句构成的执行程序
……
EXCEPTION —— 可选部分
程序出现异常时,捕捉异常并处理异常
……
END;—— 必须部分

在数据库执行PL/SQL程序时,PL/SQL语句和SQL语句是分别进行解析和执行的。PL/SQL块被数据库内部的PL/SQL引擎提取,将SQL语句取出送给Oracle的SQL引擎处理,两种语句分别在两种引擎中分析处理,在数据库内部完成数据交互、处理过程。
变量:
PL/SQL程序包括了四个部分,在四个部分中,声明部分主要用来声明变量并且初始化变量,在执行部分可以为变量赋新值,或者在表达式引用变量的值,在异常处理部分同样可以按执行部分的方法使用变量。另外,在PL/SQL程序使用时可以通过参数变量把值传递到PL/SQL块中,也可以通过输出变量或者参数变量将值传出PL/SQL块。
⒈定义的标识符名称应该遵循命名规则,在后面将会提到主要的命名规则;
⒉在声明常量和变量的时候可以为其设置初始化值,也可以强制设置not null;
⒊可以使用赋值运算符(:=)或DEFAULT保留字来初始化标识符,为标识符赋初始值;
⒋在声明标识符时,每行只能声明一个标识符。
在PL/SQL中主要使用下面三种类型的变量(或者常量):
⒈简单变量;
⒉复合(组合)变量;
外部变量
三种变量分别用于存放不同特性的数据。

⒈语句可以写在多行,就像SQL语句一样;
⒉各个关键字、字段名称等等,通过空格分隔;
⒊每条语句必须以分号结束,包括PL/SQL结束部分的END关键字后面也需要分号;
标识符需要遵循相应的命名规定;
⑴名称最多可以包含30个字符;
⑵不能直接使用保留字,如果需要,需要使用双引号括起来;
⑶第一个字符必须以字母开始;
⑷不要用数据库的表或者科学计数法表示;
还有一些语法相关的规则:
⒈在PL/SQL程序中出现的字符值和日期值必须用单引号括起;
⒉数字值可以使用简单数字或者科学计数法表示;
⒊在程序中最好养成添加注释的习惯,使用注释可以使程序更清晰,使开发者或者其他人员能够很快的理解程序的含义和思路。在程序中添加注释可以采用:
⑴/*和*/之间的多行注释;
⑵以--开始的单行注释。
declare 
   v_ename varchar2(20);
   v_rate number(7,2);
   c_rate_incr constant number(7,2) :=1.10;
   begin
     --方法一:通过select into 给变量赋值
      select ename,sal*c_rate_incr into v_ename,v_rate from employee where empno=7788;
       dbms_output.put_line(v_ename);
   end;  
--select into 语句查询结果只能返回一条语句并赋值到变量中保存,返回多条或者零条语句则报错
    过程
语法:create [or replaceprocedure procedure_name(para1 datatype1,para2 datatype2,...)
is 或 as
pl/sql block;
--1.建立过程:不带任何参数
create or replace procedure out_time is
begin
  dbms_output.put_line(systimestamp);
end;
2.调用过程
set serveroutput on
exec out_time
set serveroutput on
call out_time();  
3.建立过程:带有IN参数
create or replace procedure add_employee
(
  eno  number,
  name varchar2,
  sal number,
  job varchar2 default 'clerk',
  dno number
 )
 is
  e_integrity exception;
  pragma exception_init(e_integrity, -2291);
   begin
    insert into imp(empno,ename,sal,job,deptno) values (eno,name,sal,job,dno);
  exception
  when dup_val_on_index then
  raise_application_error(-20000, '雇员号不能重复');
  when e_integrity then
  raise_application_error(-20001, '部门不存在');
  end;  
4.建立过程:带有OUT参数
create or replaceprocedure qry_employee
(eno number,name outvarchar2,salary out number)
is
begin
selectename,sal into name,salary from emp where empno=eno;
exception
when no_date_found then
raise_application_error(-20000,'该雇员不存在');
end;
当在应用程序中调用该过程时,必须要定义变量接受输出参数的数据
sql>var name varchar2(10)
var salary number
exec qry_employee(7788,:name,:salary)
print name salary
5.建立过程:带有INOUT参数(输入输出参数)
create or replace procedure compute
(num1 in outnumber,num2 in out number)
is
v1 number;
v2 number;
begin
v1:num1/num2;
v2:mod(num1,num2);
num1:=v1;
num2:=v2;
end;
sql>var n1 number
var n2 number
exec :n1:=100
exec :n2:=30
exec ecmpute(:n1,:n2)
print n1 n2
6.为参数传递变量和数据
位置传递,名称传递,组合传递三种
1.位置传递:在调用子程序时按照参数定义的顺序为参数指定相应的变量或数值
exec add_dept(40,'sales','new york');
exec add_dept(10);
2.名称传递:在调用子程序时指定参数名,并使用关联符号=>为其提供相应的数值或变量
execadd_dept(dname=>'sales',dno=>50);
exec add_dept(dno=>30);
3.组合传递:同时使用位置传递和名称传递
exec add_dept(50,loc=>'new york');
execadd_dept(60,dname=>'sales',loc=>'newyork');
7.查看过程原代码
oracle会将过程名,源代码以及其执行代码存放到数据字典中.执行时直接按照其执行代码执行
可查询数据字典(user_source)
select text from user_source where name='add_dept';
删除过程
dropp rocedure add_dept;

异常处理
PL/SQL处理异常不同于其他程序语言的错误管理方法,PL/SQL的异常处理机制与ADA很相似,有一个处理错误的全包含方法。
PL/SQL处理异常不同于其他程序语言的错误管理方法,PL/SQL的异常处理机制与ADA很相似,有一个处理错误的全包含方法。当发生错误时,程序无条件转到异常处理部分,这就要求代码要非常干净并把错误处理部分和程序的其它部分分开。oracle允许声明其他异常条件类型以扩展错误/异常处理。这种扩展使PL/SQL的异常处理非常灵活。
当一个运行时错误发生时,称为一个异常被抛出。PL/SQL程序编译时的错误不是能被处理得异常,只有在运行时的异常能被处理。在PL/SQL程序设计中异常的抛出和处理是非常重要的内容。[1  

其中执行部分即begin 部分不能省略,其他两个部分可以省略
PL/SQL 特别的的运算符:
<> ,!=,~=,^= 不等于
:= 赋值号
=>关系号
..范围运算符

 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循环

   declare 
     v_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 语句。

格式:

 

     CURSOR  cursor_name [ (parameter[, parameter ] …)] 
            [ RETURN datatype ]
     IS  
        select_statement;

 

游标参数只能为输入参数,其格式为: 

 

parameter_name  [ IN ]  datatype  [ {:= | DEFAULT} expression ]

 

在指定数据类型时,不能使用长度约束。如NUMBER(4),CHAR(10等都是错误的。

[RETURN datatype]是可选的,表示游标返回数据的数据。如果选择,则应该严格与select_statement中的选择列表在次序和数据类型上匹配。一般是记录数据类型或带“%ROWTYPE”的数据。

 

打开游标:就是执行游标所对应的SELECT 语句,将其查询结果放入工作区,并且指针指向工作区的首部,标识游标结果集合。如果游标查询语句中带有FOR UPDATE选项,OPEN 语句还将锁定数据库表中游标结果集合对应的数据行。

格式:

 

OPEN  cursor_name [ ([parameter => ]  value [ , [parameter => ]  value]…)];

 

在向游标传递参数时,可以使用与函数参数相同的传值方法,即位置表示法和名称表示法。PL/SQL 程序不能用OPEN 语句重复打开一个游标。

 

l 提取游标数据:就是检索结果集合中的数据行,放入指定的输出变量中。 

格式:

 

FETCH  cursor_name  INTO  {variable_list  |  record_variable };

 

 

执行FETCH语句时,每次返回一个数据行,然后自动将游标移动指向下一个数据行。当检索到最后一行数据时,如果再次执行FETCH语句,将操作失败,并将游标属性%NOTFOUND置为TRUE。所以每次执行完FETCH语句后,检查游标属性%NOTFOUND就可以判断FETCH语句是否执行成功并返回一个数据行,以便确定是否给对应的变量赋了值。

l 对该记录进行处理;

l 继续处理,直到活动集合中没有记录;

l 关闭游标:当提取和处理完游标结果集合数据后,应及时关闭游标,以释放该游标所占用的系统资源,并使该游标的工作区变成无效,不能再使用FETCH 语句取其中数据。关闭后的游标可以使用OPEN 语句重新打开。

格式:

 

CLOSE  cursor_name;

 

     注:定义的游标不能有INTO 子句。

 

例1. 查询前10名员工的信息。

 

DECLARE
    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

[sql]   view plain  copy
  1. begin  
  2.   declare     
  3.     cursor c_dept is select * from dept;  
  4.     type dept_record is table of dept%rowtype;  
  5.     v_dept dept_record;  
  6.   begin  
  7.     open c_dept;  
  8.     fetch c_dept bulk collect into v_dept;  
  9.     close c_dept;  
  10.     for i in 1.. v_dept.count loop  
  11.         dbms_output.put_line('部门名称:'||v_dept(i).dname||'   部门地址:'||v_dept(i).loc);       
  12.     end loop;  
  13.   end;  
  14. end;  


2、使用limit子句限制提取的行数

[sql]   view plain  copy
  1. begin  
  2.   declare     
  3.     cursor c_dept is select * from dept;  
  4.     type dept_record is table of dept%rowtype;  
  5.     v_dept dept_record;  
  6.   begin  
  7.     open c_dept;  
  8.     loop  
  9.       exit when c_dept%NOTFOUND;  
  10.       fetch c_dept bulk collect into v_dept limit 3;    
  11.       dbms_output.put_line('-----------------------------------------------------------');     
  12.         for i in 1.. v_dept.count loop  
  13.             dbms_output.put_line('部门名称:'||v_dept(i).dname||'   部门地址:'||v_dept(i).loc);       
  14.         end loop;  
  15.     end loop;  
  16.     close c_dept;  
  17.   end;  
  18. end;  

例2. 游标参数的传递方法。

 

DECLARE
  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   10IS
     SELECT  DEPARTMENT_NAME, LOCATION_ID  FROM  DEPARTMENTS 
     WHERE  DEPARTMENT_ID  <=  dept_no;
   CURSOR  c3(dept_no  NUMBER   DEFAULT   10IS  
     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。

 

DECLARE
   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:没有参数且没有返回值的游标。

 

DECLARE
   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:有参数且没有返回值的游标。

 

DECLARE
   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:有参数且有返回值的游标。

 

DECLARE
   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:基于游标定义记录变量。

 

DECLARE
    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循环语句自动提取下一行数据供程序处理,当提取完结果集合中的所有数据行后结束循环,并自动关闭游标。

格式:

 

   FOR  index_variable  IN  cursor_name [ (value[, value ] …)] LOOP
     --  游标数据处理代码
   END  LOOP;

 

其中:

index_variable为游标FOR 循环语句隐含声明的索引变量,该变量为记录变量,其结构与游标查询语句返回的结构集合的结构相同。在程序中可以通过引用该索引记录变量元素来读取所提取的游标数据,index_variable中各元素的名称与游标查询语句选择列表中所制定的列名相同。如果在游标查询语句的选择列表中存在计算列,则必须为这些计算列指定别名后才能通过游标FOR 循环语句中的索引变量来访问这些列数据。

注:不要在程序中对游标进行人工操作;不要在程序中定义用于控制FOR循环的记录。

 

例8:

DECLARE
    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 循环语句为游标传递参数。

 

DECLARE
   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循环语句中使用子查询来实现游标的功能。

 

 

 

BEGIN
     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 ;

 

4.1.2 处理隐式游标

显式游标主要是用于对查询语句的处理,尤其是在查询结果为多条记录的情况下;而对于非查询语句,如修改、删除操作,则由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表中删除该部门。

 

 

DECLARE
    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属性来了解修改了多少行。

DECLARE
   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  column_list  FROM  table_list  FOR   UPDATE   [ OF column[, column ] …]  [ NOWAIT ]

 

    如果另一个会话已对活动集中的行加了锁,那么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;

 

DECLARE  
    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元;

 

DECLARE
   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中,可以在块、子程序和包的声明区域内定义游标变量类型。

语法格式为:

 

TYPE ref_type_name  IS  REF  CURSOR
  [  RETURN return_type ] ;

 

其中:ref_type_name为新定义的游标变量类型名称;

  return_type 为游标变量的返回值类型,它必须为记录变量。

 

在定义游标变量类型时,可以采用强类型定义和弱类型定义两种。强类型定义必须指定游标变量的返回值类型,而弱类型定义则不说明返回值类型。

声明一个游标变量的两个步骤:

步骤一:定义一个REF CURSOU数据类型,如:

TYPE ref_cursor_type IS REF CURSOR;

步骤二:声明一个该数据类型的游标变量,如:

cv_ref REF_CURSOR_TYPE;

 

例:创建两个强类型定义游标变量和一个弱类型游标变量:

 

DECLARE
    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;

 

4.2.2  游标变量操作

与游标一样,游标变量操作也包括打开、提取和关闭三个步骤。

1. 打开游标变量

打开游标变量时使用的是OPEN…FOR 语句。格式为:

 

OPEN  {cursor_variable_name  |  :host_cursor_variable_name}
FOR  select_statement;

 

其中:cursor_variable_name为游标变量,host_cursor_variable_name为PL/SQL主机环境(如OCI: ORACLE Call Interface,Pro*c 程序等)中声明的游标变量。

OPENFOR 语句可以在关闭当前的游标变量之前重新打开游标变量,而不会导致CURSOR_ALREAD_OPEN异常错误。新打开游标变量时,前一个查询的内存处理区将被释放。

 

2. 提取游标变量数据

使用FETCH语句提取游标变量结果集合中的数据。格式为:

 

 

FETCH  {cursor_variable_name  |  :host_cursor_variable_name}
INTO  {variable  [ , variable ]|  record_variable};

 

其中:cursor_variable_namehost_cursor_variable_name分别为游标变量和宿主游标变量名称;variablerecord_variable分别为普通变量和记录变量名称。

 

3. 关闭游标变量

CLOSE语句关闭游标变量,格式为:

 

 

CLOSE  {cursor_variable_name  |  :host_cursor_variable_name}

 

其中:cursor_variable_namehost_cursor_variable_name分别为游标变量和宿主游标变量名称,如果应用程序试图关闭一个未打开的游标变量,则将导致INVALID_CURSOR异常错误。

 

例15:强类型参照游标变量类型

 

DECLARE
    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
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 '11 ));
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子句)

 

DECLARE
-- 定义一个游标数据类型
   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  IN12 );
   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子句)

 

 

DECLARE
-- 定义一个与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>

 <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>      
新建java类的时候,实现javax.servlet.Filter接口即可;  
Open Session In View 模式
 在web 应用中,使用jsp 从这个对象导航到与之关联的对象或集合数据,这些关联的对象或集合数据如果是被延迟加载的,Hibernate就会抛出LazyInitializationException
因为在调用完hibernate后,session对象已经关闭了,针对这个问题,hibernate社区提出了
OpenSessionInView模式:
     在用户每一次请求过程中始终保持一个Session对象打开
         使用OpenSessionInView的目的就是为了阻止它那么快的session
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 {
        
    }
}
使用Hibernate 实现按主键查询
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();
   }  
session.save(dept);//新增
 若是修改或删除得先从数据库里查出来
Dept d =(Dept)session.load(Dept.class,11);//修改
d.setDeptName("质量部");
session.getTransaction().commit();
JAVA对象的三种形态
瞬时状态(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会在以下时间点清理缓存。

  1. 当应用程序调用org.hibernate.Transaction的commit()方法的时候.commit方法先清理缓存,然后再向数据库提交事务。Hibernate之所以把清理缓存的时间点安排在事务快结束时,一方面是因为可以减少访问数据库的频率,还有一方面是因为可以尽可能缩短当前事务对数据库中相关资源的锁定时间。
  2. 当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,就会清理缓存,使得Session缓存与数据库已经进行了同步,从而保证查询结果返回的是正确的数据。
  3. 当应用程序显示调用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() 方法



一、参数绑定

无论是HQL查询还是SQL查询还是讲要讲到的命名查询,都支持两种参数绑定形式——按位置、按名称绑定(以下均以hql查询为例)
1.按照位置绑定参数
此时在HQL语句中使用“?”占位符来定义参数位置,如:
[java]   view plain  copy   派生到我的代码片
  1. Query query=session.createQuery("from Dept where deptno=?");   
传递的时候以setXXX()的形式来传递,参数位置从0开始:
[java]   view plain  copy   派生到我的代码片
  1. //参数是什么类型的就调用set类型的方法  
  2.         query.setInteger(0,20);  
  3.         //...省略  
2.按照命名绑定参数
此时在HQL语句中使用“:参数名字”(英文冒号+名字)的形式定义参数,如:
[java]   view plain  copy   派生到我的代码片
  1. Query query=session.createQuery("from Dept where deptno=:deptNo");  
传递的时候还是以setXXX()的形式传递,此时就不是按位置了:
[java]   view plain  copy   派生到我的代码片
  1. query.setInteger("deptNo",20);  
  2.         //...省略  
3.绑定各种类型的参数
①使用通用的方式绑定参数:

当不知道参数的类型是何种类型的时候,使用setParameter()方法进行绑定:
[java]   view plain  copy   派生到我的代码片
  1. Query query=session.createQuery("from Dept where deptno=?");  
  2.         query.setParameter(0,20);  
②绑定命名参数和一个对象的属性值
如果查询中包含多个对象的时候,为了方便可能会将查询条件封装成一个类,此时再绑定参数就有了简便的方法
使用命名参数+setProperties()方法进行绑定,如:
[java]   view plain  copy   派生到我的代码片
  1. class DeptCondition{  
  2.         private int selectNo;  
  3.         private String selectName;  
  4.         //省略get、set  
  5.     }  
测试类:
[java]   view plain  copy   派生到我的代码片
  1. DeptCondition d=new DeptCondition();  
  2.     d.setSelectName("sal");  
  3.     d.setSelectNo(20);  
  4.     Query query=session.createQuery("from Dept where deptName=:selectName and deptNo=:selectNo");  
  5.     query.setProperties(d);  
  6.     List<Dept> list=query.list();  
注意:命名参数的名字一定要和对象的属性名字一致

二、投影和分页

1.投影
在上文中简单提了下select子句。有时候可能不需要查询对象的全部属性,此时就可以使用select 子句来选取某些属性进行查询,获取的查询结果可以分为三种形式
①将结果封装成Object对象
[java]   view plain  copy   派生到我的代码片
  1. Query query=session.createQuery("select deptName from Dept");  
  2.     List<String> list=query.list();  
  3.     for (String string : list) {  
  4.         System.out.println(string);  
  5.     }  
②将结果封装成Object数组
这个前面已经接触过了
[java]   view plain  copy   派生到我的代码片
  1. Query query=session.createQuery("select empNo,empName from Emp");  
  2.     List<Object[]> list=query.list();  
  3.     for (Object[] item : list) {  
  4.         System.out.println(item[0]+"\t"+item[1]);  
  5.     }  
③将结果封装成新的对象
比如我这里查询empNo和empName,我可以新写一个类EmpExt(或者现有的),主要是要求如下带参构造和无参构造
这种方式返回的结果更加符合面向对象 的风格
[java]   view plain  copy   派生到我的代码片
  1. Query query=session.createQuery("select new EmpExt(empNo,empName) from Emp");  
  2.     List<EmpExt> list=query.list();  
2.分页
分页在以前是个很头痛的问题,如果查询条件多了的话实现起来会相当复杂。Hibernate提供了简单的分页方法
Query接口的setFirstResult(arg0)用于设置第一条记录的位置setMaxResults(arg0)用于设置查询的最大记录条数
[java]   view plain  copy   派生到我的代码片
  1. int pageIndex=1;  
  2.     int pageSize=2;  
  3.     Query query=session.createQuery("from Emp");  
  4.     query.setFirstResult((pageIndex-1)*pageSize);  
  5.     query.setMaxResults(pageSize);  
  6.     //...  
另外,Query还有一个uniqueResult()方法,此方法用于获取唯一结果,如果查询的记录中的条数不唯一的时候,
调用此方法会抛出异常

三、命名查询

命名查询就是在映射文件中定义好查询语句,然后在程序中调用即可,HQL查询和SQL查询都支持
1.HQL查询中的命名查询

Emp.hbm.xml映射文件中的配置如下:
[html]   view plain  copy   派生到我的代码片
  1. <hibernate-mapping package="com.ljh.entity">  
  2.         <class name="Emp" table="emp">  
  3.             <!--省略-->  
  4.         </class>  
  5.         <!--   
  6.             query元素和class元素是一个级别的  
  7.             另外,加<![CDATA[语句]]>的作用:  
  8.             如果查询语句中含有where子句并且有“<”和“>”的时候,会对标签进行  
  9.             转义,通过此方式声明了里面的语句只是以字符串的形式存在  
  10.         -->  
  11.         <query name="getEmpByNo">  
  12.             <![CDATA[from Emp where empNo=:no]]>  
  13.         </query>    
  14.     </hibernate-mapping>  
测试类:
[java]   view plain  copy   派生到我的代码片
  1. //使用getNamedQuery方法查询  
  2.     Query query=session.getNamedQuery("getEmpByNo");  
  3.     query.setInteger("no"7788);  
  4.     Emp e=(Emp)query.uniqueResult();  
  5.     System.out.println(e.getEmpName());  
2.原生sql中的命名查询
sql查询的命名查询同样是跟class是同一个级别的,使用sql-query元素
[html]   view plain  copy   派生到我的代码片
  1. <sql-query name="getEmpByNo">  
  2.         <!--  
  3.             注意此处使用return元素  
  4.             alias:别名  
  5.             class:将要封装成此属性值对应的对象   
  6.          -->  
  7.         <return alias="e" class="com.ljh.entity.Emp"></return>  
  8.         <!-- 原生sql语句 -->  
  9.         <![CDATA[select * from emp e  where e.empno=:no]]>  
  10.     </sql-query>    
注意,return元素的作用:
               现在通过getNamedQuery()方法进行查询,无论是HQL还是sql查询,返回的都是Query接口的对象,不会再出现SQLQuery对象
如果使用原生sql查询,结果则是上文讲的Object[]类型的,如果此时想使用SQLQuery特有的方法将查询结果封装成
对应的对象,但是没有SQLQuery对象是调用不出来的;所以return元素的作用就很明显了,就是将结果封装成对应的对象
alias表示查询的别名



                                                                                   第六章:    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.load()方法不会执行访问dept表的select 语句
       只会返回一个Dept 代理类的对象, 不会被初始化,它的deptNo为120,其余属性都为null
       只有当你去访问他的属性时才会执行访问dept表的select 语句,这时才会被初始化
     */  
//如果既要使用延迟加载(不希望把下属拖出来),又要一开始就初始化,可以使用
    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>
     */
    /*for (Emp e :emps ) {
        System.out.println(e.getEname());
    }*/
    
    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

<property name="cache.use_second_level_cache">true</property>
    <property name="cache.provider_class">
        org.hibernate.cache.EhCacheProvider
    </property>  
 @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语句说明关闭与二级缓存的交互成功
    }  
Hibernate查询性能优化技巧http://blog.csdn.net/he90227/article/details/38062845
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值