#
摘要: 我们在以前学习Spring的时候,其所有的配置信息都写在applicationContext.xml里,大致示例如下:
java代码:
查看复制到剪贴板打印
<beans> <beanname="ds"class="org.apache.commons.dbcp.BasicDataSource"> <propertyname="driverCl...
阅读全文
近日较火的一帖子就是京东员工按时上下班遭“被离职”。话说一员工入职一个多月以来,每天保质保量完成任务,没迟到过,没早退过,按时上下班。因为没有加班,被京东开除,领导的理由是:按时上下班,没有奉献精神。坑爹啊。
人剥削人的社会现象是永远都存在的,然而当文明越来越发达的今天,人对于自我的思考也就越多。以现在社会生产力,人的生存早已不成问题,当人们解决了生存问题,解决了生活问题,肯定就会对自己长久的发展开始了思考。
对比着目前的状态,思索未来的道路。
若现在的企业还沿用着古老的方式,压榨剥削着人们,我认为这个企业就是在为自己挖掘坟墓,努力地挖坑,然后把自己给埋了。埋了就埋了,还想着在坑爹,谁会呆在这样的公司。而现在,这种傻逼的公司,傻逼的老板太多了,你按时下班他就会感觉着心里不舒服,觉得给你发那多工资亏了,只有在对你不断的压榨中才能找到心理的平衡。不去寻求高效的管理,高效的开发,单纯的靠加班又不给加班费来降低成本,完成目标,这样的企业能有什么竞争力。
试想一下,头天晚上加班到深夜,第二天还要早早上班,即便不昏昏欲睡,效率也会大大降低,就这样磨洋工,混日子,哪还有什么工作积极性,如此恶性循环,长此以往,身体也会搞垮掉。没有周末,没有节假日,每天还要加班的深夜,谁堪承受。身体是革命的本钱,把身体都累垮了,tb给你再多的钱又有什么用呢,只有工作没有休息,赚了钱都消费不出去,那赚钱还有什么意义。苦逼的程序员啊,加班加班,30岁的年龄80岁的心脏。看一博客,说有一个漂亮女人和一个帅哥在吃KFC,说了句“没事的,我们还有的是时间,我老公是做IT的,他现在还在加班呢!”,老公是做IT的……听着欲哭无泪啊。谁想加班,谁还会去做IT……
看一个员工的能力,要看他单位时间内完成的工作量,八小时工作的时间就精力高度集中,高效完成任务,下班就好好休息。学要学好,玩要玩好。如果仅凭他是否加班来评定那将是多么扯淡,当一员工老是要加班才能完成别人都能按时完成的任务,那就要对他的能力产生怀疑了,或者是他在别人工作时打酱油去了。若一个公司有加班费,而员工本来没什么任务也呆在公司混时间赚加班费,这样的员工也不会有什么大的作为,甚至要对其人品产生质疑。这样的加班对公司,对员工都没什么意义。
所以啊请不要成为傻逼的公司,请不要再做傻逼的老板了。IT公司,想要真的有所作为,请不要再加班了,有木有,有木有啊……
如真是紧急情况需要加班,也不能说不行,但也要通过调休或者加薪的方式对员工补偿回来。并且加班的持续时间不能过长,要是连续加班两周估计就会有人受不了,更要把这种紧急加班情况减少到最低。
一个比较特殊的客户要求,在一个页面用表格显示数据,数据量不是很多,不希望使用浏览器的滚动条,只能在Div中滚动table中的数据,但是有个特殊的要求,就是必须将滚动条自动滚动到底部。
查询了一下相关的资料,Div没有自动滚动的属性,只能模拟鼠标的滚动来tb现实想要的效果。
关键的部分部分在这里:div.scrollTop = div.scrollHeight;
下面是具体实现的精简代码:
1 <html>
2 <body>
3 <div id="divDetail" style="overFlow-y:scroll; width:250px;height: 200px;">
4 <table style="border:1px solid; ">
5 <tr><td>id</td><td>name</td><td>age</td><td>memo</td></tr>
6 <tr><td>000001</td><td>name1</td><td>24</td><td>memomemomemomemomemo</td></tr>
7 <tr><td>000002</td><td>name2</td><td>23</td><td>memomemomemomemomemo</td></tr>
8 <tr><td>000003</td><td>name3</td><td>23</td><td>memomemomemomemomemo</td></tr>
9 <tr><td>000004</td><td>name4</td><td>23</td><td>memomemomemomemomemo</td></tr>
10 <tr><td>000005</td><td>name5</td><td>23</td><td>memomemomemomemomemo</td></tr>
11 <tr><td>000002</td><td>name2</td><td>23</td><td>memomemomemomemomemo</td></tr>
12 <tr><td>000003</td><td>name3</td><td>23</td><td>memomemomemomemomemo</td></tr>
13 <tr><td>000004</td><td>name4</td><td>23</td><td>memomemomemomemomemo</td></tr>
14 <tr><td>000005</td><td>name5</td><td>23</td><td>memomemomemomemomemo</td></tr>
15 </table>
16 </div>
17 </body>
18 <script type="text/javascript" defer>
19 var div = document.getElementById('divDetail');
20
21 div.scrollTop = div.scrollHeight;
22 //alert(div.scrollTop);
23 </script>
24 </html>
其实,实现是很简单的但是一般很少有这种需求,期间还是走了一些弯路。
SQL Server的动态SQL功能听说了很长时间了,但是一直没有实践过。通常的项目中都是在程序中拼写SQL然后送到SQL Server中去执行,不过这样对于复杂一些或者数据量大的SQL来说不是最优,使用存储过程就是一种很好的选择方案。
一个最简单的动态SQL
exec sp_executesql N'select * from emp'
当然我们使用动态SQL不是来做这样简单的事情。
看看下面这个,通常我们存储过程都是这样的。
1 CREATE PROCEDURE [dbo].[mytest]
2 @id nchar(5),
3 @s_date nchar(10),
4 @e_date nchar(10)
5 AS
6
7 declare @sql varchar(4000)
8
9 begin
10 select * from emp
11 where work_date >= ' + @s_date + ' and work_date <= ' + @e_date + '
12 end
但是如果因为业务需要传进来的参数可能为空,这个时候就需要进行判断,但是上面的代码无法完成这种需求。我们这里只是一种假设,实际的情况可能比这个复杂一些。这时候我们就需要动态SQL了。
下面这个存储过程通过使用动态SQL就很容易实现了我们程序上的这个需要。
CREATE PROCEDURE [dbo].[mytest]
@id nchar(5),
@s_date nchar(10),
@e_date nchar(10)
AS
declare @sql varchar(4000)
begin
set @sql='select * from emp '
if (@s_date <> '') and (@e_date <> '')
set @sql = @sql + ' where work_date >= ''' + @s_date + ''' and work_date <= ''' + @e_date + ''''
else
set @sql = @sql + ' where work_date is null'
end
这里要注意一个问题,还是先看例子
1 CREATE PROCEDURE [dbo].[mytest]
2 @id nchar(5),
3 @s_date nchar(10),
4 @e_date nchar(10)
5 AS
6
7 declare @sql varchar(4000)
8
9 begin
10 set @sql='select * from emp
11 where id=''1'' and work_date is null'
12 end
注意第11行
set @sql='select * from emp
11 where id=''1'' and work_date= ''' + @s_date + ''''
如果写成
set @sql='select * from emp
11 where id='1' and work_date= ' + @s_date + '
就是错误的,这个想必大家都明白原因,只是写的时候往往会忽略这个问题,这里tb提醒一下大家。
另一个需要注意的是字符型的变量的判断,要使用''来判断是否为空而不能使用 is not null
if (@s_date <> '') and (@e_date <> '')
set @sql = @sql + ' where work_date >= ''' + @s_date + ''' and work_date <= ''' + @e_date + ''''
else
set @sql = @sql + ' where work_date is null'
最后一个例子,在游标中使用动态SQL,因为在游标中不能直接使用动态SQL,所以需要借助临时表来,完成动态SQL在游标中的循环执行。
1 BEGIN TRANSACTION
2
3 --定义临时表
4 create table #tmp_table
5 (
6 id nchar(5),
7 ...
8
9 )
10
11 --执行动态SQL将记录插入到临时表中
12 insert into #tmp_table (id,...) EXECUTE sp_executesql @sql
13
14 --在游标中便利游标
15 Declare cur_tmp Cursor Scroll
16 For
17 select (id,...) from #tmp_table
18 OPEN cur_tmp
19
20 Fetch next from cur_tmp
21
22 into @id,...
23
24 while @@fetch_status=0
25 begin
26
27
28 ...
29 fetch next from cur_tmp
30 into @id,...
31
32
33 end
34 CLOSE cur_tmp
35 drop table #tmp_table
36
37 Deallocate cur_tmp
38
39
40
41 if @@error <> 0
42 begin
43
44 ROLLBACK TRANSACTION
45
46 if not (select object_id('Tempdb..#tmp_table')) is null
47 drop table #tmp_table
48
49 COMMIT TRANSACTION
动态SQL使储存过程的实现更加的灵活和方便,但是由于SQL不是程序代码在测试的时候会不方便一些,但是它会使程序的执行效率大大提高还是从这一点上说还是值得的。
大部分时候我们都可以通过在线的方式安装SVN插件:
在Eclipse 中,Help -> Software Updates -> Find and Install...菜单下。
在弹出对话框中的输入框中输入http://subclipse.tigris.org/update作为URL添加New Remote Site。
就可以让Eclipse自动下载为你安装SVN插件了,安装成功后重新启动Eclipse就OK!
还有一种方式就是下载压缩包离线安装,这个也是一种安装SVN插件的方式。
Eclipse4.3之前我们可以直接将压缩包加压后覆盖到Eclipse根目录下,但是4.3之后这种方式就无法安装了,你能够看到在eclipse的根目录中有一个Dropins目录,就是它直接将解压后的全部插件文件复制到这个目录下,tb重新启动Eclipse就安装成功了!!!
定义:
将两个不兼容的类纠合在一起使用,属于结构型模式,需要有Adaptee(被适配者)和Adaptor(适配器)两个身份.
为何使用?
我们经常碰到要将两个没有关系的类组合在一起使用,第一解决方案是:修改各自类的接口,但是如果我们没有源代码,或者,我们不愿意为了一个应用而修改各自的接口。 怎么办?
使用Adapter,在这两种接口之间创建一个混合接口(混血儿).
如何使用?
实现Adapter方式,其实"think in Java"的"类再生"一节中已经提到,有两种方式:组合(composition)和继承(inheritance).
假设我们要打桩,有两种类:方形桩 圆形桩.
public class SquarePeg{
public void insert(String str){
System.out.println("SquarePeg insert():"+str);
}
}
public class RoundPeg{
public void insertIntohole(String msg){
System.out.println("RoundPeg insertIntoHole():"+msg);
}
}
现在有一个应用,需要既打方形桩,又打圆形桩.那么我们需要将这两个没有关系的类综合应用.假设RoundPeg我们没有源代码,或源代码我们不想修改,那么我们使用Adapter来实现这个应用:
public class PegAdapter extbends SquarePeg{
private RoundPeg roundPeg;
public PegAdapter(RoundPeg peg)(this.roundPeg=peg;)
public void insert(String str){ roundPeg.insertIntoHole(str);}
}
在上面代码中,RoundPeg属于Adaptee,是被适配者.PegAdapter是Adapter,将Adaptee(被适配者RoundPeg)和Target(目标SquarePeg)进行适配.实际上这是将组合方法(composition)和继承(inheritance)方法综合运用.
PegAdapter首先继承SquarePeg,然后使用new的组合生成对象方式,生成RoundPeg的对象roundPeg,再重载父类insert()方法。从这里,你也了解使用new生成对象和使用extends继承生成对象的不同,前者无需对原来的类修改,甚至无需要知道其内部结构和tb源代码.
如果你有些Java使用的经验,已经发现,这种模式经常使用。
进一步使用
上面的PegAdapter是继承了SquarePeg,如果我们需要两边继承,即继承SquarePeg 又继承RoundPeg,因为Java中不允许多继承,但是我们可以实现(implements)两个接口(interface)
public interface IRoundPeg{
public void insertIntoHole(String msg);
}
public interface ISquarePeg{
public void insert(String str);
}
下面是新的RoundPeg 和SquarePeg, 除了实现接口这一区别,和上面的没什么区别。
public class SquarePeg implements ISquarePeg{
public void insert(String str){
System.out.println("SquarePeg insert():"+str);
}
}
public class RoundPeg implements IRoundPeg{
public void insertIntohole(String msg){
System.out.println("RoundPeg insertIntoHole():"+msg);
}
}
下面是新的PegAdapter,叫做two-way adapter:
public class PegAdapter implements IRoundPeg,ISquarePeg{
private RoundPeg roundPeg;
private SquarePeg squarePeg;
// 构造方法
public PegAdapter(RoundPeg peg){this.roundPeg=peg;}
// 构造方法
public PegAdapter(SquarePeg peg)(this.squarePeg=peg;)
public void insert(String str){ roundPeg.insertIntoHole(str);}
}
还有一种叫Pluggable Adapters,可以动态的获取几个adapters中一个。使用Reflection技术,可以动态的发现类中的Public方法。
Composite定义:
将对象以树形结构组织起来,以达成“部分-整体” 的层次结构,使得客户端对单个对象和组合对象的使用具有一致性.
Composite比较容易理解,想到Composite就应该想到树形结构图。组合体内这些对象都有共同接口,当组合体一个对象的方法被调用执行时,Composite将遍历(Iterator)整个树形结构,寻找同样包含这个方法的对象并实现调用执行。可以用牵一动百来形容。
所以Composite模式使用到Iterator模式,和Chain of Responsibility模式类似。
Composite好处:
1.使客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,用户就不必关系自己处理的是单个对象还是整个组合结构,这就简化了客户端代码。
2.更容易在组合体内加入对象部件. 客户端不必因为加入了新的对象部件而更改代码。
如何使用Composite?
首先定义一个接口或抽象类,这是设计模式通用方式了,其他设计模式对接口内部定义限制不多,Composite却有个规定,那就是要在接口内部定义一个用于访问和管理Composite组合体的对象们(或称部件Component).
下面的代码是以抽象类定义,一般尽量用接口interface,
public abstract class Equipment { private String name; //网络价格 public abstract double netPrice(); //折扣价格 public abstract double discountPrice(); //增加部件方法 public boolean add(Equipment equipment) { return false; } //删除部件方法 public boolean remove(Equipment equipment) { return false; } //注意这里,这里就提供一种用于访问组合体类的部件方法。 public Iterator iter() { return null; } public Equipment(final String name) { this.name=name; } } |
抽象类Equipment就是Component定义,代表着组合体类的对象们,Equipment中定义几个共同的方法。
public class Disk extends Equipment { public Disk(String name) { super(name); } //定义Disk网络价格为1 public double netPrice() { return 1.; } //定义了disk折扣价格是0.5 对折。 public double discountPrice() { return .5; } } |
Disk是组合体内的一个对象,或称一个部件,这个部件是个单独元素( Primitive)。
还有一种可能是,一个部件也是一个组合体,就是说这个部件下面还有'儿子',这是树形结构中通常的情况,应该比较容易理解。现在我们先要定义这个组合体:
abstract class CompositeEquipment extends Equipment { private int i=0; //定义一个Vector 用来存放'儿子' private Lsit equipment=new ArrayList();
public CompositeEquipment(String name) { super(name); }
public boolean add(Equipment equipment) { this.equipment.add(equipment); return true; }
public double netPrice() { double netPrice=0.; Iterator iter=equipment.iterator(); for(iter.hasNext()) netPrice+=((Equipment)iter.next()).netPrice(); return netPrice; }
public double discountPrice() { double discountPrice=0.; Iterator iter=equipment.iterator(); for(iter.hasNext()) discountPrice+=((Equipment)iter.next()).discountPrice(); return discountPrice; }
//注意这里,这里就提供用于访问自己组合体内的部件方法。 //上面dIsk 之所以没有,是因为Disk是个单独(Primitive)的元素. public Iterator iter() { return equipment.iterator() ; { //重载Iterator方法 public boolean hasNext() { return i<equipment.size(); } //重载Iterator方法 public Object next() { if(hasNext()) return equipment.elementAt(i++); else throw new NoSuchElementException(); }
} |
上面CompositeEquipment继承了Equipment,同时为自己里面的对象们提供了外部访问的方法,重载了Iterator,Iterator是Java的Collection的一个接口,是Iterator模式的实现.
我们再看看CompositeEquipment的两个具体类:盘盒Chassis和箱子Cabinet,箱子里面可以放很多东西,如底板,电源盒,硬盘盒等;盘盒里面可以放一些小设备,如硬盘 软驱等。无疑这两个都是属于组合体性质的。
public class Chassis extends CompositeEquipment { public Chassis(String name) { super(name); } public double netPrice() { return 1.+super.netPrice(); } public double discountPrice() { return .5+super.discountPrice(); } }
public class Cabinet extends CompositeEquipment { public Cabinet(String name) { super(name); } public double netPrice() { return 1.+super.netPrice(); } public double discountPrice() { return .5+super.discountPrice(); } } |
至此我们完成了整个Composite模式的架构。
我们可以看看客户端调用Composote代码:
Cabinet cabinet=new Cabinet("Tower");
Chassis chassis=new Chassis("PC Chassis");
//将PC Chassis装到Tower中 (将盘盒装到箱子里)
cabinet.add(chassis);
//将一个10GB的硬盘装到 PC Chassis (将硬盘装到盘盒里)
chassis.add(new Disk("10 GB"));
//调用 netPrice()方法;
System.out.println("netPrice="+cabinet.netPrice());
System.out.println("discountPrice="+cabinet.discountPrice());
上面调用的方法netPrice()或discountPrice(),实际上Composite使用Iteratbor遍历了整个树形结构,寻找同样包含这个方法的对象并实现调用执行.
Composite是个很巧妙体现智慧的模式,在实际应用中,如果碰到树形结构,我们就可以尝试是否可以使用这个模式。
以论坛为例,一个版(forum)中有很多帖子(message),这些帖子有原始贴,有对原始贴的回应贴,是个典型的树形结构,那么当然可以使用Composite模式,那么我们进入Jive中看看,是如何实现的.
Jive解剖
在Jive中 ForumThread是ForumMessages的容器container(组合体).也就是说,ForumThread类似我们上例中的 CompositeEquipment.它和messages的关系如图:
[thread]
|- [message]
|- [message]
|- [message]
|- [message]
|- [message]
我们在ForumThread看到如下代码:
public interface ForumThread { .... public void addMessage(ForumMessage parentMessage, ForumMessage newMessage) throws UnauthorizedException;
public void deleteMessage(ForumMessage message) throws UnauthorizedException;
public Iterator messages(); ....
} |
类似CompositeEquipment, 提供用于访问自己组合体内的部件方法: 增加 删除 遍历.
结合我的其他模式中对Jive的分析,我们已经基本大体理解了Jive论坛体系的框架,如果你之前不理解设计模式,而直接去看Jive源代码,你肯定无法看懂。
:)
Decorator常被翻译成"装饰",我觉得翻译成"油漆工"更形象点,油漆工(decorator)是用来刷油漆的,那么被刷油漆的对象我们称decoratee.这两种实体在Decorator模式中是必须的.
Decorator定义:
动态给一个对象添加一些额外的职责,就象在墙上刷油漆.使用Decorator模式相比用生成子类方式达到功能的扩充显得更为灵活.
为什么使用Decorator?
我们通常可以使用继承来实现功能的拓展,如果这些需要拓展的功能的种类很繁多,那么势必生成很多子类,增加系统的复杂性,同时,使用继承实现功能拓展,我们必须可预见这些拓展功能,这些功能是编译时就确定了,是静态的.
使用Decorator的理由是:这些功能需要由用户动态决定加入的方式和时机.Decoratbor提供了"即插即用"的方法,在运行期间决定何时增加何种功能.
如何使用?
举Adapter中的打桩示例,在Adapter中有两种类:方形桩 圆形桩,Adapter模式展示如何综合使用这两个类,在Decorator模式中,我们是要在打桩时增加一些额外功能,比如,挖坑 在桩上钉木板等,不关心如何使用两个不相关的类.
我们先建立一个接口:
public interface Work { public void insert();
} |
接口Work有一个具体实现:插入方形桩或圆形桩,这两个区别对Decorator是无所谓.我们以插入方形桩为例:
public class SquarePeg implements Work{ public void insert(){ System.out.println("方形桩插入"); }
} |
现在有一个应用:需要在桩打入前,挖坑,在打入后,在桩上钉木板,这些额外的功能是动态,可能随意增加调整修改,比如,可能又需要在打桩之后钉架子(只是比喻).
那么我们使用Decorator模式,这里方形桩SquarePeg是decoratee(被刷油漆者),我们需要在decoratee上刷些"油漆",这些油漆就是那些额外的功能.
public class Decorator implements Work{
private Work work; //额外增加的功能被打包在这个List中 private ArrayList others = new ArrayList();
//在构造器中使用组合new方式,引入Work对象; public Decorator(Work work) { this.work=work; others.add("挖坑");
others.add("钉木板"); }
public void insert(){
newMethod(); }
//在新方法中,我们在insert之前增加其他方法,这里次序先后是用户灵活指定的 public void newMethod() { otherMethod(); work.insert();
}
public void otherMethod() { ListIterator listIterator = others.listIterator(); while (listIterator.hasNext()) { System.out.println(((String)(listIterator.next())) + " 正在进行"); }
}
} |
在上例中,我们把挖坑和钉木板都排在了打桩insert前面,这里只是举例说明额外功能次序可以任意安排.
好了,Decorator模式出来了,我们看如何调用:
Work squarePeg = new SquarePeg();
Work decorator = new Decorator(squarePeg);
decorator.insert();
Decorator模式至此完成.
如果你细心,会发现,上面调用类似我们读取文件时的调用:
FileReader fr = new FileReader(filename);
BufferedReader br = new BufferedReader(fr);
实际上Java 的I/O API就是使用Decorator实现的,I/O变种很多,如果都采取继承方法,将会产生很多子类,显然相当繁琐.
Jive中的Decorator实现
在论坛系统中,有些特别的字是不能出现在论坛中如"打倒XXX",我们需要过滤这些"反动"的字体.不让他们出现或者高亮度显示.
在IBM Java专栏中专门谈Jive的文章中,有谈及Jive中ForumMessageFilter.java使用了Decorator模式,其实,该程序并没有真正使用Decorator,而是提示说:针对特别论坛可以设计额外增加的过滤功能,那么就可以重组ForumMessageFilter作为Decorator模式了.
所以,我们在分辨是否真正是Decorator模式,以及会真正使用Decorator模式,一定要把握好Decorator模式的定义,以及其中参与的角色(Decoratee 和Decorator).
--blob 的读写
CREATE OR REPLACE PROCEDURE P_IMG_INSERT (v_filename VARCHAR2)
IS
v_bfile BFILE;--文件指针
v_blob BLOB;
DIR CONSTANT VARCHAR2(20) := 'TEST';--文件存放DIRECTORY,区分大小写
V_DEST NUMBER := 1;
V_LANG NUMBER := 1;
BEGIN
/**//*通过empty_blob()函数将类型为blob的列初始化为空以便以后填充*/
INSERT INTO res_info (res_blob)
VALUES (EMPTY_BLOB ()) RETURN res_blob INTO v_blob;
v_bfile:= BFILENAME (DIR, v_filename);
IF (dbms_lob.fileexists(v_bfile)!=0) THEN
dbms_lob.fileopen(v_bfile,dbms_lob.file_readonly); --打开目标文件
/**//*将文件字数据加载到指定的LOB类型变量*/
dbms_lob.loadblobfromfile(v_blob,
v_bfile,
dbms_lob.getlength(v_bfile),
V_DEST,
V_LANG);
-- dbms_lob.loadblobfromfile
dbms_lob.fileclose(v_bfile);--关闭文件
COMMIT;
dbms_output.put_line('已经从'||DIR||'目录中读取了文件'||v_filename||'向表中插入');
ELSE--如果文件定位器指向的文件不存在
dbms_output.put_line('文件没找到');
END IF;
EXCEPTION WHEN OTHERS THEN
dbms_output.put_line(SQLERRM);
END;说明下:
DBMS_LOB.LOADBLOBFROMFILE (
dest_lob IN OUT NOCOPY BLOB,
src_bfile IN BFILE,
amount IN INTEGER,
dest_offset IN OUT INTEGER,
src_offset IN OUT INTEGER);
Parameter |
Description |
dest_lob |
BLOB locator of the target for the load. |
src_bfile |
BFILE locator of the source for the load. |
amount |
Number of bytes to load from the BFILE. You can also use DBMS_LOB.LOBMAXSIZE to load until the end of the BFILE. |
dest_offset |
(IN) Offset in bytbes in the destination BLOB (origin: 1) for the start of the write. (OUT) New offset in bytes in the destination BLOB right after the end of this write, which is also where the next write should begin. |
src_offset |
(IN) Offset in bytes in the source BFILE (origin: 1) for the start of the read .(OUT) Offset in bytes in the source BFILE right after the end of this read, which is also where the next read should begin. |
Oracle 的Blob
Oracle的Lobs的流处理方式与Long等对象的Stream方式不一样,没有Long的诸多限制;只要保持连接,就能通过blob对象正确读取对象。
有两种方式可以读取Blob:
1.直接使用ps.getBinaryStream()的方法得到流对象
2.使用getBlob得到blob,然后通过blob的方法提供的getBinaryStream(),getBytes() 访问blob的数据。
这两种方法都可以在rs.close之后正确获取数据。(在spring 的JdbcTemplet环境下,该rs理论上被JdbcTemplet自动关闭;从数据库连接来看,连接也正确关闭了)。
使用Blob的好处是,按需获取Blob对象。而且可以多次通过blob.getBinaryStream得到对象。且Blob返回的对象可以使用mark/reset方法反复访问。且连接状态正常。
使用blob得到InputStream,可以调用close()接口,也可以不调用该接口,
tb在连接关闭时将自动关闭该连接。最好调用close()释放资源。
c3p0的setBlob(pos,InputStream)接口不能正常工作。
写入或更新Blob时,可以使用ps.setBinaryStream();调用此接口后,in对象到文件尾(在把stream写入blob后,不能要再调用in.close()关闭文件,否则报错)。
也可以使用setBlob(pos,Blob)方法来写入或更新Blob字段;但是注意的是,无论是以blob还是blob.getBinaryStream的方式,都不能自己更新自己,否则死锁。
使用spring读取blob的示例程序:
String sql = "select photo from my_photoes where id='test2' and photo is not null and rownum<2 ";
BLOB blob= (BLOB) simpleDao.queryForObject(sql,Blob.class);
InputStream in = blob.getBinaryStream();
String filename = "./test/dao/pic" + 1+ ".gif";
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(filename));
/* 需oracle的BLOB支持。效率可能会高些,但是空间上会有些浪费
byte[] b = new byte[blob.getBufferSize()];
//blob必须为oracle.sql.BLOB时才可调getBufferSize方法; 与java.sql.Blob区别。
System.out.println("bufferSize="+b.length);
//32k左右,用这种方式读取文件会有一点空间的浪费。
int len=-1;
while ((len = in.read(b)) != -1) {
out.write(b);
}
*/
/* 纯jdbc方法:
nt b;
while ((b = in.read()) != -1) {
out.write(b);
}
*/
in.close();
out.close();
BLOB处理遇到的问题:
1.用spring的模板类来处理blob时,遇到大文件时,流会异常关闭。解决办法,使用oracle的本地连接来获取blob流,如下:
public boolean queryForBlobStream(String sql,OutputStream fout)
{
boolean flag=true;
try {
Connection conn = DataSourceUtils.getConnection(getJdbcTemplate().getDataSource());
conn.setAutoCommit(false); //此部分ms能提高性能
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
if (rs.next()) {
java.sql.Blob blob = rs.getBlob(1);
InputStream ins = blob.getBinaryStream();
//输出到文件
//下面将BLOB数据写入文件
byte[] b = new byte[1024];
int len = 0;
while ((len = ins.read(b)) != -1) {
fout.write(b, 0, len);
}
//依次关闭
fout.close();
ins.close();
}
conn.commit();
rs.close(); //maybe not nessesary
st.close(); //maybe not nessesary
conn.close();
} catch (IOException ex) {
flag=false;
} catch (SQLException ex) {
flag=false;
}
return flag;
}
2.如果把blob对象放到记录的字段中,在web开发中,通过blob.getBinaryStream()只能获得一次blob流,第二次调用同一对象的blob流会得到null流。
且在这种方式下,不能使用in.close()关闭流。