2009年5月20日
--查询表空间、表空间大小及表空间对应物理路径select a.tablespace_name,b.file_name,a.block_size,a.block_size,b.bytes/1024 /1024 "Sum MB" from dba_tablespaces a,dba_data_files b where a.tablespace_name=b.tablespace_name; --查询表空间使用情况 SELECT UPPER(F.TABLESPACE_NAME) "表空间名", D.TOT_GROOTTE_MB "表空间大小(M)", D.TOT_GROOTTE_MB - F.TOTAL_BYTES "已使用空间(M)", TO_CHAR(ROUND((D.TOT_GROOTTE_MB - F.TOTAL_BYTES) / D.TOT_GROOTTE_MB * 100,2),'990.99') || '%' "使用比", F.TOTAL_BYTES "空闲空间(M)", F.MAX_BYTES "最大块(M)" FROM (SELECT TABLESPACE_NAME, ROUND(SUM(BYTES) / (1024 * 1024), 2) TOTAL_BYTES, ROUND(MAX(BYTES) / (1024 * 1024), 2) MAX_BYTES FROM SYS.DBA_FREE_SPACE GROUP BY TABLESPACE_NAME) F, (SELECT DD.TABLESPACE_NAME, ROUND(SUM(DD.BYTES) / (1024 * 1024), 2) TOT_GROOTTE_MB FROM SYS.DBA_DATA_FILES DD GROUP BY DD.TABLESPACE_NAME) D WHERE D.TABLESPACE_NAME = F.TABLESPACE_NAME ORDER BY 1; --查询表空间的free space select tablespace_name, count(*) as extends, round(sum(bytes) / 1024 / 1024, 2) as MB, sum(blocks) as blocks from dba_free_space group by tablespace_name; --查询表空间的总容量 select tablespace_name, sum(bytes) / 1024 / 1024 as MB from dba_data_files group by tablespace_name; --查询表空间使用率 select total.tablespace_name, round(total.MB, 2) as Total_MB,考试大论坛 round(total.MB - free.MB, 2) as Used_MB, round((1 - free.MB / total.MB) * 100, 2) || '%' as Used_Pct from (select tablespace_name, sum(bytes) / 1024 / 1024 as MB from dba_free_space group by tablespace_name) free, (select tablespace_name, sum(bytes) / 1024 / 1024 as MB from dba_data_files group by tablespace_name) total where free.tablespace_name = total.tablespace_name; 1.查找当前表级锁的SQL如下:select sess.sid, sess.serial#, lo.oracle_username, lo.os_user_name, ao.object_name, lo.locked_mode from v$locked_object lo, dba_objects ao, v$session sess where ao.object_id = lo.object_id and lo.session_id = sess.sid; 2.杀掉锁表进程:alter system kill session '436,35123'; 3.RAC环境中锁查找:SELECT inst_id,DECODE(request,0,'Holder: ','Waiter: ')||sid sess, id1, id2, lmode, request, type,block,ctime FROM GV$LOCK WHERE (id1, id2, type) IN (SELECT id1, id2, type FROM GV$LOCK WHERE request>0) ORDER BY id1, request; 4.监控当前数据库谁在运行什么SQL语句select osuser, username, sql_text from v$session a, v$sqltext b where a.sql_address =b.address order by address, piece; 5.找使用CPU多的用户sessionselect a.sid,spid,status,substr(a.program,1,40) prog, a.terminal,osuser,value/60/100 value from v$session a,v$process b,v$sesstat c where c.statistic#=12 and c.sid=a.sid and a.paddr=b.addr order by value desc; 6.查看死锁信息SELECT (SELECT username FROM v$session WHERE SID = a.SID) blocker, a.SID, 'is blocking', (SELECT username FROM v$session WHERE SID = b.SID) blockee, b.SID FROM v$lock a, v$lock b WHERE a.BLOCK = 1 AND b.request > 0 AND a.id1 = b.id1 AND a.id2 = b.id2; 7.具有最高等待的对象SELECT o.OWNER,o.object_name, o.object_type, a.event, SUM (a.wait_time + a.time_waited) total_wait_time FROM v$active_session_history a, dba_objects o WHERE a.sample_time BETWEEN SYSDATE - 30 / 2880 AND SYSDATE AND a.current_obj# = o.object_id GROUP BY o.OWNER,o.object_name, o.object_type, a.event ORDER BY total_wait_time DESC; SELECT a.session_id, s.osuser, s.machine, s.program, o.owner, o.object_name, o.object_type, a.event, SUM (a.wait_time + a.time_waited) total_wait_time FROM v$active_session_history a, dba_objects o, v$session s WHERE a.sample_time BETWEEN SYSDATE - 30 / 2880 AND SYSDATE AND a.current_obj# = o.object_id AND a.session_id = s.SID GROUP BY o.owner, o.object_name, o.object_type, a.event, a.session_id, s.program, s.machine, s.osuser ORDER BY total_wait_time DESC; 8.查询当前连接会话数select s.value,s.sid,a.username from v$sesstat S,v$statname N,v$session A where n.statistic#=s.statistic# and name='session pga memory' and s.sid=a.sid order by s.value; 9.等待最多的用户SELECT s.SID, s.username, SUM (a.wait_time + a.time_waited) total_wait_time FROM v$active_session_history a, v$session s WHERE a.sample_time BETWEEN SYSDATE - 30 / 2880 AND SYSDATE GROUP BY s.SID, s.username ORDER BY total_wait_time DESC; 10.等待最多的SQLSELECT a.program, a.session_id, a.user_id, d.username, s.sql_text, SUM (a.wait_time + a.time_waited) total_wait_time FROM v$active_session_history a, v$sqlarea s, dba_users d WHERE a.sample_time BETWEEN SYSDATE - 30 / 2880 AND SYSDATE AND a.sql_id = s.sql_id AND a.user_id = d.user_id GROUP BY a.program, a.session_id, a.user_id, s.sql_text, d.username; 11.查看消耗资源最多的SQLSELECT hash_value, executions, buffer_gets, disk_reads, parse_calls FROM V$SQLAREA WHERE buffer_gets > 10000000 OR disk_reads > 1000000 ORDER BY buffer_gets + 100 * disk_reads DESC; 12.查看某条SQL语句的资源消耗SELECT hash_value, buffer_gets, disk_reads, executions, parse_calls FROM V$SQLAREA WHERE hash_Value = 228801498 AND address = hextoraw('CBD8E4B0'); 13.查询会话执行的实际SQLSELECT a.SID, a.username, s.sql_text FROM v$session a, v$sqltext s WHERE a.sql_address = s.address AND a.sql_hash_value = s.hash_value AND a.status = 'ACTIVE' ORDER BY a.username, a.SID, s.piece; 14.显示正在等待锁的所有会话SELECT * FROM DBA_WAITERS; DDL-------------------------------------------------------------- /*注意点: 1.如果在PL/SQL 等工具里打开的话,直接修改下面的代码中[斜体加粗部分]执行 2.确保路径存在,比如【D:\oracle\oradata\Oracle9i\】也就是你要保存文件的路径存在 /*分为四步 */ /*第1步:创建临时表空间 */ create temporary tablespace user_temp tempfile 'D:\oracle\oradata\Oracle9i\user_temp.dbf' size 50m autoextend on next 50m maxsize 20480m extent management local; /*第2步:创建数据表空间 */ create tablespace user_data logging datafile 'D:\oracle\oradata\Oracle9i\user_data.dbf' size 50m autoextend on next 50m maxsize 20480m extent management local; /*第3步:创建用户并指定表空间 */ create user username identified by password default tablespace user_data temporary tablespace user_temp; /*第4步:给用户授予权限 */ grant connect,resource,dba to username;
摘自: http://www.douban.com/note/235086917/http://jackleechina.iteye.com/blog/1595397为什么一般要采用事件监听而不是直接对元素的事件属性(如:onclick、onmouseover)赋值? 原来用事件属性只能赋值一种方法,即: button1.onclick = function() { alert(1); }; button1.onclick = function() { alert(2); }; 这样后面的赋值语句就将前面的onclick属性覆盖了而使用添加事件监听的方式就可以实现并行。特别是当团队合作时,事件并行的需求增多,比如:监听document对象的鼠标事件或者window对象的载入事件等。使用事件属性则很容易造成事件覆盖掉 使用事件监听有两种方式:attachEvent和addEventListener attachEvent与addEventListener区别 适应的浏览器版本不同,同时在使用的过程中要注意 attachEvent方法 按钮onclick addEventListener方法 按钮click attachEvent方法, (ie系列) addEventListener方法 Mozilla系列 例子如下:
1<!DOCTYPE html> 2<html> 3 4<SCRIPT LANGUAGE="JavaScript"> 5function method1(){ 6 alert("method1"); 7} 8function method2(){ 9 alert("method2"); 10} 11function method3(){ 12 alert("method3"); 13} 14</SCRIPT> 15<body> 16<input type="button" value="dom 元素事件属性绑定的按钮" id="button1"/> 17<input type="button" value="IE浏览器: attachEvent进行事件绑定的按钮" id="btn1"/> 18<input type="button" value="火狐浏览器: addEventListener进行事件绑定的按钮" id="btn2"/> 19 20<SCRIPT LANGUAGE="JavaScript"> 21 /**//**方法一: 使用元素的事件属性. [这种方式事件只可绑定一次,最后绑定的执行]**/ 22 button1.onclick = function() { alert("1-1"); }; 23 button1.onclick = function() { alert("1-2"); }; 24 /**//**方法二: 使用attachEvent注册事件. 格式如下object.attachEvent(event,function);**/ 25 var btn1Obj = document.getElementById("btn1"); 26 btn1Obj.attachEvent("onclick", method1); 27 btn1Obj.attachEvent("onclick", method2); 28 btn1Obj.attachEvent("onclick", method3); 29 /**//**方法三: addEventListener. 格式如下element.addEventListener(type,listener,useCapture);**/ 30 var btn2Obj = document.getElementById("btn2"); 31 btn2Obj.addEventListener("click",method1,false); 32 btn2Obj.addEventListener("click",method2,false); 33 btn2Obj.addEventListener("click",method3,false); 34 //执行顺序为method1->method2->method3 35</SCRIPT> 36<br/>attachEvent按照注册倒叙执行: 执行顺序为method3->method2->method1 37<br/>addEventListener按照注册顺序执行: 执行顺序为method1->method2->method3 38</body> 39</html> 相关衍生阅读:
摘要: 摘自http://zhangjunhd.blog.51cto.com/113473/20629/
1.Servlet过滤器
1.1 什么是过滤器
过滤器是一个程序,它先于与之相关的servlet或JSP页面运行在服务器上。过滤器可附加到一个或多个servlet或JSP页面上,并且可以检查进入这些资源的请求信息。在这之后,过滤器可以作如下的选择:
①以常规的方式调用资源(即,调... 阅读全文
摘要: 摘自 http://www.sandzhang.com/blog/2010/04/07/mysql-show-status-explained-detail/
要查看MySQL运行状态,要优化MySQL运行效率都少不了要运行show status查看各种状态,下面是参考官方文档及网上资料整理出来的中文详细解释:
如有问题,欢迎指正
状态名
作用域
... 阅读全文
摘要: 代码中反复开关自动提交没有必要. 其他方面写得还是很不错的.清晰.摘自 http://wangqinqin.iteye.com/blog/547277
PreparedStatement:
1) addBatch()将一组参数添加到PreparedStatement对象内部。
2) executeBatch()将一批参数提交给数据库来执行,如果全部命令执行成功... 阅读全文
摘自 http://neoremind.net/2010/12/preparedstatement_diff/
JDBC中Statement与PreparedStatement的区别
1. statement每次执行sql语句,相关数据库都要执行sql语句的编译;preparedstatement是预编译的, 采用Cache机制(预编译语句,放在Cache中,下次执行相同SQL语句时,则可以直接从Cache中取出来,有利于sql生成查询计划。),对于批量处理可以大大提高效率. 也叫JDBC存储过程。
例如,如果要执行两条sql语句
SELECT colume FROM TABLE WHERE colume=1;
SELECT colume FROM TABLE WHERE colume=2;
会生成两个执行计划
一千个查询就生成一千个执行计划!
PreparedStatement用于使用绑定变量重用执行计划
SELECT colume FROM TABLE WHERE colume=:x;
通过set不同数据只需要生成一次执行计划,可以重用
是否使用绑定变量对系统影响非常大,生成执行计划极为消耗资源
两种实现 速度差距可能成百上千倍
后者使用了PreparedStatement对象,而前者是普通的Statement对象。PreparedStatement对象不仅包含了SQL语句,而且大多数情况下这个语句已经被预编译过,因而当其执行时,只需DBMS运行SQL语句,而不必先编译。当你需要执行Statement对象多次的时候,PreparedStatement对象将会大大降低运行时间,当然也加快了访问数据库的速度。
这种转换也给你带来很大的便利,不必重复SQL语句的句法,而只需更改其中变量的值,便可重新执行SQL语句。选择PreparedStatement对象与否,在于相同句法的SQL语句是否执行了多次,而且两次之间的差别仅仅是变量的不同。如果仅仅执行了一次的话,在对数据库只执行一次性存取的时侯,用 Statement 对象进行处理,PreparedStatement 对象的开销比Statement大,对于一次性操作并不会带来额外的好处。
2. PrepareStatement中执行的SQL语句中是可以带参数的,也就是说可以替换变量,尽量采用使用?号的方式传递参数,增加代码的可读性又可以预编译加速;而Statement则不可以。
3. 防止SQL注入。在SQL中包含特殊字符或SQL的关键字(如:’ or 1 or ‘)时,Statement将出现不可预料的结果(出现异常或查询的结果不正确),可用PreparedStatement来解决。
SQL注入或者说SQL注入攻击就是利用Statement的漏洞完成的,例如用个用户登录,那么form表单有用户名和密码
那么我提交时,在用户名输入框内 输入 “aaa’ or ’a’=’a” 密码框随便输入,那么这样意味着 sql的
查询语言就是 “select * from 表 where 用户名=’aaa’ or ’a’=’a’ and 密码=’123’ ”,这样查询出来所有的数据或者是混乱。那么不被授权的用户照样可以登录,岂不是被黑了?!实际中现在java程序员早都不用这种方式写查询了,一般都用PreparedStatement来查询,或干脆就用hibernate之类的持久层框架,这样通过sql注入就无从谈起了。
摘自:http://ryxxlong.iteye.com/blog/552884
如何修改mysql root密码 忘记MySQL ROOT密码是在MySQ使用中很常见的问题,可是有很多朋友并不会重置ROOT密码,那叫苦啊,特写此文章与大家交流:
1、编辑MySQL的配置文件:my.ini 一般在MySQL安装目录下有my.ini即MySQL的配置文件。 在此配置文件的最后添加如下一行: skip-grant-tables 保存退出编辑。
2、然后重启MySQL服务 在命令行下执行: net stop MySQL net start MySQL
3、设置新的ROOT密码 然后用命令提示符cd到对应安装目录的bin下执行: MySQL -u root -p MySQL或mysql -u root -p 直接回车无需密码即可进入数据库了。 此时,在命令行下执行 use mysql; 现在我们执行如下语句把root密码更新为: update user set password=PASSWORD("root") where user='root'; (注意:此时不用使用mysqladmin -u root -p password '你的新密码'这条命令修改密码,因为'skip-grant-tables'配置, 不信的话,你可以试用一下,它肯定会报如下所示的错误: F:\Documents and Settings\long>mysqladmin -u root -p password 'root' Enter password: Warning: single quotes were not trimmed from the password by your command line client, as you might have expected. mysqladmin: You cannot use 'password' command as mysqld runs with grant tables disabled (was started with --skip-grant-tables). Use: "mysqladmin flush-privileges password '*'" instead) exit 退出MySQL。
4、还原配置文件并重启服务
然后修改MySQL配置文件把刚才添加的那一行'skip-grant-tables'删除。 再次重起MySQL服务,密码修改完毕。 用新密码root试一下吧,又能登入重新进入mysql了?
附mysql修改密码的一些方法: 1. 用MYSQL的grant语句,例如 mysql -h hostname –u root 命令登录到mysqld server 用grant 命令改变口令: mysql -h 192.168.1.101 -u root 上边的192.168.1.101 是偶的mysqld 运行机器,你换成自己的,这样登录上去,就可以修改密码了, 其实没必要这么麻烦,直接mysql -u root就可以了。 GRANT ALL ON *.* TO 'root'@'localhost' IDENTIFIED BY 'root' WITH GRANT OPTION
2. mysqladmin -u 用户名 -p 旧密码 password 新密码 例1:给root 加个密码root。首先进入cmd中,然后键入 以下命令,至于在CMD下能否使用mysqladmin, 就要看你的Windows环境变量PATH中是否添加“E:\Program Files\MySQL\MySQL Server 5.1\bin;”(请改为你自己的安装路径)了。) mysqladmin -u root password root 注:因为开始时root 没有密码,所以-p 旧密码一项就可以省略了。 例2:再将root 的密码改为admin。 mysqladmin –u root -proot password admin(注意-p 不要和后面的密码分 开写,要写在一起,不然会出错,错误如下所示: F:\Documents and Settings\long>mysqladmin -u root -p root password admin Enter password: **** mysqladmin: Unknown command: 'root') 当然你也可以这样写:mysqladmin –u root -p password admin回车, 然后再输入你的旧密码,这样也是完全也可以的,看你的爱好了. 例3:再将root用户的密码去掉. F:\Documents and Settings\long>mysqladmin -u root -p password ; Enter password: root 此时,root用户又没有密码了.可以通过下面的方法设置: F:\Documents and Settings\long>mysql -u root mysql>set password for 'root'@'localhost'=password('root');(语法:SET PASSWORD FOR '用户名'@'主机' = PASSWORD('密码')) mysql>set password for 'root'@'%'=password('root'); //本条可选,这是在配置mysql数据库,如果你选择了允许root通过远程登录进来时,你在mysql数据库下的user表中, use mysql; select * from user;可以看到有两条记录,如果你没有配置这一项的话,只会第一条记录! Host User Password 'localhost', 'root', '*9C9F4927129ECC3209D8550DC8B67156FDBF9418', ... '%', 'root', '*81F5E21E35407D884A6CD4A731AEBFB6AF209E1B', ... 通过以上设置,root的密码将变为root这样就完成了根用户root密码的设置工作。
3. use mysql; update user set password =password('yourpass') where user='root'
(注:下面的这些方法我本人没有试过,不知对不对,我只是转载了一下:) 下面的方法都在mysql提示符下使用,且必须有mysql的root权限: 方法4 mysql> INSERT INTO mysql.user (Host,User,Password) VALUES('%','jeffrey',PASSWORD('biscuit')); mysql> FLUSH PRIVILEGES 确切地说这是在增加一个用户,用户名为jeffrey,密码为biscuit。 在《mysql中文参考手册》里有这个例子,所以我也就写出来了。 注意要使用PASSWORD函数,然后还要使用FLUSH PRIVILEGES。
方法5 和方法三一样,只是使用了REPLACE语句 mysql> REPLACE INTO mysql.user (Host,User,Password) VALUES('%','jeffrey',PASSWORD('biscuit')); mysql> FLUSH PRIVILEGES
方法6 使用SET PASSWORD语句, mysql> SET PASSWORD FOR jeffrey@"%" = PASSWORD('biscuit'); 你也必须使用PASSWORD()函数, 但是不需要使用FLUSH PRIVILEGES。
方法7 使用GRANT ... IDENTIFIED BY语句 mysql> GRANT USAGE ON *.* TO jeffrey@"%" IDENTIFIED BY 'biscuit'; 这里PASSWORD()函数是不必要的,也不需要使用FLUSH PRIVILEGES。
注:mysql 新设置用户或更改密码后需用flush privileges刷新MySQL的系统权限相关表, 否则会出现拒绝访问,还有一种方法,就是重新启动mysql服务器,来使新设置生效。
怎么查看端口占用情况?
开始--运行--cmd 进入命令提示符 输入netstat -ano 即可看到所有连接的PID 之后在任务管理器中找到这个PID所对应的程序如果任务管理器中没有PID这一项,可以在任务管理器中选"查看"-"选择列"
经常,我们在启动应用的时候发现系统需要的端口被别的程序占用,如何知道谁占有了我们需要的端口,很多人都比较头疼,下面就介绍一种非常简单的方法,希望对大家有用
假如我们需要确定谁占用了我们的9050端口
1、Windows平台 在windows命令行窗口下执行:
C:\>netstat -aon|findstr "9050"
TCP 127.0.0.1:9050 0.0.0.0:0 LISTENING 2016
看到了吗,端口被进程号为2016的进程占用,继续执行下面命令:
C:\>tasklist|findstr "2016"
tor.exe 2016 Console 0 16,064 K
很清楚吧,tor占用了你的端口。
JSON 即 JavaScript Object Natation,它是一种轻量级的数据交换格式,非常适合于服务器与 JavaScript 的交互。本文将快速讲解 JSON 格式,并通过代码示例演示如何分别在客户端和服务器端进行 JSON 格式数据的处理。 管有许多宣传关于 XML 如何拥有跨平台,跨语言的优势,然而,除非应用于 Web Services,否则,在普通的 Web 应用中,开发者经常为 XML 的解析伤透了脑筋,无论是服务器端生成或处理 XML,还是客户端用 JavaScript 解析 XML,都常常导致复杂的代码,极低的开发效率。实际上,对于大多数 Web 应用来说,他们根本不需要复杂的 XML 来传输数据,XML 的扩展性很少具有优势,许多 AJAX 应用甚至直接返回 HTML 片段来构建动态 Web 页面。和返回 XML 并解析它相比,返回 HTML 片段大大降低了系统的复杂性,但同时缺少了一定的灵活性。 现在, JSON 为 Web 应用开发者提供了另一种数据交换格式。让我们来看看 JSON 到底是什么,同 XML 或 HTML 片段相比,JSON 提供了更好的简单性和灵活性。
JSON 数据格式解析
和 XML 一样,JSON 也是基于纯文本的数据格式。由于 JSON 天生是为 JavaScript 准备的,因此,JSON 的数据格式非常简单,您可以用 JSON 传输一个简单的 String,Number,Boolean,也可以传输一个数组,或者一个复杂的 Object 对象。
String,Number 和 Boolean 用 JSON 表示非常简单。例如,用 JSON 表示一个简单的 String “ abc ”,其格式为:
这与绝大多数编程语言的表示方法一致,例如:
Boolean 类型表示为 true 或 false 。此外,JavaScript 中的 null 被表示为 null ,注意,true 、false 和 null 都没有双引号,否则将被视为一个 String 。
JSON 还可以表示一个数组对象,使用 [] 包含所有元素,每个元素用逗号分隔,元素可以是任意的 Value,例如,以下数组包含了一个 String,Number,Boolean 和一个 null:
Object 对象在 JSON 中是用 {} 包含一系列无序的 Key-Value 键值对表示的,实际上此处的 Object 相当于 Java 中的 Map<String, Object> ,而不是 Java 的 Class 。注意 Key 只能用 String 表示。
例如,一个 Address 对象包含如下 Key-Value:
city:Beijing street:Chaoyang Road postcode:100025(整数) |
用 JSON 表示如下:
{"city":"Beijing","street":" Chaoyang Road ","postcode":100025} |
其中 Value 也可以是另一个 Object 或者数组,因此,复杂的 Object 可以嵌套表示,例如,一个 Person 对象包含 name 和 address 对象,可以表示如下:
{"name":"Michael","address": {"city":"Beijing","street":" Chaoyang Road ","postcode":100025} } |
JavaScript 处理 JSON 数据
上面介绍了如何用 JSON 表示数据,接下来,我们还要解决如何在服务器端生成 JSON 格式的数据以便发送到客户端,以及客户端如何使用 JavaScript 处理 JSON 格式的数据。
我们先讨论如何在 Web 页面中用 JavaScript 处理 JSON 数据。我们通过一个简单的 JavaScript 方法就能看到客户端如何将 JSON 数据表示给用户:
function handleJson() { var j={"name":"Michael","address": {"city":"Beijing","street":" Chaoyang Road ","postcode":100025} }; document.write(j.name); document.write(j.address.city); } |
假定服务器返回的 JSON 数据是上文的:
{"name":"Michael","address": {"city":"Beijing","street":" Chaoyang Road ","postcode":100025} } |
只需将其赋值给一个 JavaScript 变量,就可以立刻使用该变量并更新页面中的信息了,相比 XML 需要从 DOM 中读取各种节点而言,JSON 的使用非常容易。我们需要做的仅仅是发送一个 Ajax 请求,然后将服务器返回的 JSON 数据赋值给一个变量即可。有许多 Ajax 框架早已包含了处理 JSON 数据的能力,例如 Prototype(一个流行的 JavaScript 库:http://prototypejs.org)提供了 evalJSON() 方法,能直接将服务器返回的 JSON 文本变成一个 JavaScript 变量:
new Ajax.Request("http://url", { method: "get", onSuccess: function(transport) { var json = transport.responseText.evalJSON(); // TODO: document.write(json.xxx); } }); |
服务器端输出 JSON 格式数据
下面我们讨论如何在服务器端输出 JSON 格式的数据。以 Java 为例,我们将演示将一个 Java 对象编码为 JSON 格式的文本。
将 String 对象编码为 JSON 格式时,只需处理好特殊字符即可。另外,必须用 (" ) 而非 (' ) 表示字符串:
static String string2Json(String s) { StringBuilder sb = new StringBuilder(s.length()+20); sb.append('\"'); for (int i=0; i<s.length(); i++) { char c = s.charAt(i); switch (c) { case '\"': sb.append("\\\""); break; case '\\': sb.append("\\\\"); break; case '/': sb.append("\\/"); break; case '\b': sb.append("\\b"); break; case '\f': sb.append("\\f"); break; case '\n': sb.append("\\n"); break; case '\r': sb.append("\\r"); break; case '\t': sb.append("\\t"); break; default: sb.append(c); } } sb.append('\"'); return sb.toString(); }
|
将 Number 表示为 JSON 就容易得多,利用 Java 的多态,我们可以处理 Integer,Long,Float 等多种 Number 格式:
static String number2Json(Number number) { return number.toString(); }
|
Boolean 类型也可以直接通过 toString() 方法得到 JSON 的表示:
static String boolean2Json(Boolean bool) { return bool.toString(); }
|
要将数组编码为 JSON 格式,可以通过循环将每一个元素编码出来:
static String array2Json(Object[] array) { if (array.length==0) return "[]"; StringBuilder sb = new StringBuilder(array.length << 4); sb.append('['); for (Object o : array) { sb.append(toJson(o)); sb.append(','); } // 将最后添加的 ',' 变为 ']': sb.setCharAt(sb.length()-1, ']'); return sb.toString(); }
|
最后,我们需要将 Map<String, Object> 编码为 JSON 格式,因为 JavaScript 的 Object 实际上对应的是 Java 的 Map<String, Object> 。该方法如下:
static String map2Json(Map<String, Object> map) { if (map.isEmpty()) return "{}"; StringBuilder sb = new StringBuilder(map.size() << 4); sb.append('{'); Set<String> keys = map.keySet(); for (String key : keys) { Object value = map.get(key); sb.append('\"'); sb.append(key); sb.append('\"'); sb.append(':'); sb.append(toJson(value)); sb.append(','); } // 将最后的 ',' 变为 '}': sb.setCharAt(sb.length()-1, '}'); return sb.toString(); }
|
为了统一处理任意的 Java 对象,我们编写一个入口方法 toJson(Object) ,能够将任意的 Java 对象编码为 JSON 格式:
public static String toJson(Object o) { if (o==null) return "null"; if (o instanceof String) return string2Json((String)o); if (o instanceof Boolean) return boolean2Json((Boolean)o); if (o instanceof Number) return number2Json((Number)o); if (o instanceof Map) return map2Json((Map<String, Object>)o); if (o instanceof Object[]) return array2Json((Object[])o); throw new RuntimeException("Unsupported type: " + o.getClass().getName()); }
|
我们并未对 Java 对象作严格的检查。不被支持的对象(例如 List)将直接抛出 RuntimeException 。此外,为了保证输出的 JSON 是有效的,Map<String, Object> 对象的 Key 也不能包含特殊字符。细心的读者可能还会发现循环引用的对象会引发无限递归,例如,精心构造一个循环引用的 Map,就可以检测到 StackOverflowException :
@Test(expected=StackOverflowError.class) public void testRecurrsiveMap2Json() { Map<String, Object> map = new HashMap<String, Object>(); map.put("key", map); JsonUtil.map2Json(map); }
|
好在服务器处理的 JSON 数据最终都应该转化为简单的 JavaScript 对象,因此,递归引用的可能性很小。
最后,通过 Servlet 或 MVC 框架输出 JSON 时,需要设置正确的 MIME 类型(application/json)和字符编码。假定服务器使用 UTF-8 编码,则可以使用以下代码输出编码后的 JSON 文本:
response.setContentType("application/json;charset=UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter pw = response.getWriter(); pw.write(JsonUtil.toJson(obj)); pw.flush();
|
小结
JSON 已经是 JavaScript 标准的一部分。目前,主流的浏览器对 JSON 支持都非常完善。应用 JSON,我们可以从 XML 的解析中摆脱出来,对那些应用 Ajax 的 Web 2.0 网站来说,JSON 确实是目前最灵活的轻量级方案。
JNDI是 Java 命名与目录接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之一,不少专家认为,没有透彻理解JNDI的意义和作用,就没有真正掌握J2EE特别是EJB的知识。 那么,JNDI到底起什么作用?
要了解JNDI的作用,我们可以从“如果不用JNDI我们怎样做?用了JNDI后我们又将怎样做?”这个问题来探讨。
没有JNDI的做法: 程序员开发时,知道要开发访问MySQL数据库的应用,于是将一个对 MySQL JDBC 驱动程序类的引用进行了编码,并通过使用适当的 JDBC URL 连接到数据库。 就像以下代码这样:
Connection conn=null; try {Class.forName("com.mysql.jdbc.Driver",true, Thread.currentThread().getContextClassLoader()); conn=DriverManager.getConnection("jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue"); /* 使用conn并进行SQL操作 */...... conn.close(); } catch(Exception e) {e.printStackTrace();} finally {if(conn!=null) {try {conn.close();} catch(SQLException e) { }}}
这是传统的做法,也是以前非Java程序员(如Delphi、VB等)常见的做法。这种做法一般在小规模的开发过程中不会产生问题,只要程序员熟悉Java语言、了解JDBC技术和MySQL,可以很快开发出相应的应用程序。
没有JNDI的做法存在的问题: 1、数据库服务器名称MyDBServer 、用户名和口令都可能需要改变,由此引发JDBC URL需要修改; 2、数据库可能改用别的产品,如改用DB2或者Oracle,引发JDBC驱动程序包和类名需要修改; 3、随着实际使用终端的增加,原配置的连接池参数可能需要调整; 4、......
解决办法: 程序员应该不需要关心“具体的数据库后台是什么?JDBC驱动程序是什么?JDBC URL格式是什么?访问数据库的用户名和口令是什么?”等等这些问题,程序员编写的程序应该没有对 JDBC 驱动程序的引用,没有服务器名称,没有用户名称或口令 —— 甚至没有数据库池或连接管理。而是把这些问题交给J2EE容器来配置和管理,程序员只需要对这些配置和管理进行引用即可。
由此,就有了JNDI。
用了JNDI之后的做法: 首先,在在J2EE容器中配置JNDI参数,定义一个数据源,也就是JDBC引用参数,给这个数据源设置一个名称;然后,在程序中,通过数据源名称引用数据源从而访问后台数据库。 具体操作如下(以JBoss为例): 1、配置数据源 在JBoss的 D:\jboss420GA\docs\examples\jca 文件夹下面,有很多不同数据库引用的数据源定义模板。将其中的 mysql-ds.xml 文件Copy到你使用的服务器下,如 D:\jboss420GA\server\default\deploy。 修改 mysql-ds.xml 文件的内容,使之能通过JDBC正确访问你的MySQL数据库,如下: <?xml version="1.0" encoding="UTF-8"?> <datasources> <local-tx-datasource> <jndi-name>MySqlDS</jndi-name> <connection-url>jdbc:mysql://localhost:3306/lw</connection-url> <driver-class>com.mysql.jdbc.Driver</driver-class> <user-name>root</user-name> <password>rootpassword</password> <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name> <metadata> <type-mapping>mySQL</type-mapping> </metadata> </local-tx-datasource> </datasources>
这里,定义了一个名为MySqlDS的数据源,其参数包括JDBC的URL,驱动类名,用户名及密码等。
2、在程序中引用数据源:
Connection conn=null; try { Context ctx=new InitialContext(); Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用数据源 DataSource ds=(Datasource)datasourceRef; conn=ds.getConnection(); /* 使用conn进行数据库SQL操作 */...... c.close(); } catch(Exception e) {e.printStackTrace();} finally {if(conn!=null) { try { conn.close(); } catch(SQLException e) { }}} 直接使用JDBC或者通过JNDI引用数据源的编程代码量相差无几,但是现在的程序可以不用关心具体JDBC参数了。 在系统部署后,如果数据库的相关参数变更,只需要重新配置 mysql-ds.xml 修改其中的JDBC参数,只要保证数据源的名称不变,那么程序源代码就无需修改。
由此可见,JNDI避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。
JNDI的扩展: JNDI在满足了数据源配置的要求的基础上,还进一步扩充了作用:所有与系统外部的资源的引用,都可以通过JNDI定义和引用。
所以,在J2EE规范中,J2EE 中的资源并不局限于 JDBC 数据源。引用的类型有很多,其中包括资源引用(已经讨论过)、环境实体和 EJB 引用。特别是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另外一项关键角色:查找其他应用程序组件。
EJB 的 JNDI 引用非常类似于 JDBC 资源的引用。在服务趋于转换的环境中,这是一种很有效的方法。可以对应用程序架构中所得到的所有组件进行这类配置管理,从 EJB 组件到 JMS 队列和主题,再到简单配置字符串或其他对象,这可以降低随时间的推移服务变更所产生的维护成本,同时还可以简化部署,减少集成工作。 外部资源”。
总结: J2EE 规范要求所有 J2EE 容器都要提供 JNDI 规范的实现。JNDI 在 J2EE 中的角色就是“交换机” —— J2EE 组件在运行时间接地查找其他组件、资源或服务的通用机制。在多数情况下,提供 JNDI 供应者的容器可以充当有限的数据存储,这样管理员就可以设置应用程序的执行属性,并让其他应用程序引用这些属性(Java 管理扩展(Java Management Extensions,JMX)也可以用作这个目的)。JNDI 在 J2EE 应用程序中的主要角色就是提供间接层,这样组件就可以发现所需要的资源,而不用了解这些间接性。
在 J2EE 中,JNDI 是把 J2EE 应用程序合在一起的粘合剂,JNDI 提供的间接寻址允许跨企业交付可伸缩的、功能强大且很灵活的应用程序。这是 J2EE 的承诺,而且经过一些计划和预先考虑,这个承诺是完全可以实现的。
在Oracle中,要按特定条件查询前N条记录,用个rownum就搞定了。 select * from emp where rownum <= 5 结果只查询5条记录, oracle会自动展现一个rownum的属性表示记录排序号
而select * from emp where rownum > 5 ;则是失败的。 因为:rownum是oracle预处理字段,默认标序是1,只有记录集已经满足条件后才会进行后续编号。由于第一条记录rownum默认是1,而你的条件是rownum>=6 对第一条记录比较它的rownum肯定不大于6 所以不满足条件 oracle舍弃第一条记录将数据库第二条记录标序为1再进行比较 肯定都不满足rownum>=6 这样循环也就是说由于没有记录满足rownum>=6所以记录一直被舍弃,rownum一直是1 。
解决方案: 利用查询时,自动生成的rownum属性. 排序方法: select * from ( select a1.*, rownum rwn from emp a1 where rownum <=10 ) where rwn >= 6; 或者 select * from ( select qx.*,row_number() over(order by qx.empno) rwn from emp qx ) where rwn between 6 and 10 ------------------------------------------------------------------------- 使用注意:
1排序导致数据重复:
table1中表有字段(其余字段省) ID 主键 DATA_UPDATE_TIME 数据更新时间(只存储了年月日)
分页查询的语句如下 select * from ( select row_.*, rownum rownum_ from ( select p.id from table1 p order by p.DATA_UPDATE_TIME desc ) row_ where rownum <= ) where rownum_ >
以每页显示10条为例 第一次 rownum <= 10) where rownum_ > 0 第二次 rownum <= 20) where rownum_ > 10
发现有一条记录在两次查询结果中重复出现,不知道问题出在哪里,请忙帮看看。
另: DATA_UPDATE_TIME 的值有重复,不知道跟它有没有关系。 如果按ID排的话就不会出现这个问题
解答: 如果order by 不能唯一确定记录的顺序就会出现这个问题。 解决的方法是把分页部分全部拿到最外层进行。
- select * from (
- select row_.*, rownum rownum_
- from (
- select p.id from table1 p
- order by p.DATA_UPDATE_TIME desc
- ) row_
- )
- where rownum_ > ? and rownum_ <= ?
2 排序的id顺序: Oracle中的rownum的是在取数据的时候产生的序号,所以想对指定排序的数据去指定的rowmun行数据就必须注意了。 SQL> select rownum ,id,name from student order by name; ROWNUM ID NAME ---------- ------ --------------------------------------------------- 3 200003 李三 2 200002 王二 1 200001 张一 4 200004 赵四 可以看出,rownum并不是按照name列来生成的序号。系统是按照记录插入时的顺序给记录排的号,rowid也是顺序分配的。为了解决这个问题,必须使用子查询 SQL> select rownum ,id,name from (select * from student order by name); ROWNUM ID NAME ---------- ------ --------------------------------------------------- 1 200003 李三 2 200002 王二 3 200001 张一 4 200004 赵四 这样就成了按name排序,并且用rownum标出正确序号(有小到大)
------------ 参考Oracle的rownum原理和使用 http://tenn.iteye.com/blog/99339
在Oracle中,要按特定条件查询前N条记录,用个rownum就搞定了。 select * from emp where rownum <= 5 而且书上也告诫,不能对rownum用">",这也就意味着,如果你想用 select * from emp where rownum > 5 则是失败的。要知道为什么会失败,则需要了解rownum背后的机制: 1 Oracle executes your query.
2 Oracle fetches the first row and calls it row number 1.
3 Have we gotten past row number meets the criteria? If no, then Oracle discards the row, If yes, then Oracle return the row.
4 Oracle fetches the next row and advances the row number (to 2, and then to 3, and then to 4, and so forth).
5 Go to step 3.
了解了原理,就知道rownum>不会成功,因为在第三步的时候查询出的行已经被丢弃,第四步查出来的rownum仍然是1,这样永远也不会成功。
同样道理,rownum如果单独用=,也只有在rownum=1时才有用。
对于rownum来说它是oracle系统顺序分配为从查询返回的行的编号,返回的第一行分配的是1,第二行是2,依此类推,这个伪字段可以用于限制查询返回的总行数,而且rownum不能以任何表的名称作为前缀。 举例说明: 例如表:student(学生)表,表结构为: ID char(6) --学号 name VARCHAR2(10) --姓名 create table student (ID char(6), name VARCHAR2(100)); insert into sale values('200001',‘张一’); insert into sale values('200002',‘王二’); insert into sale values('200003',‘李三’); insert into sale values('200004',‘赵四’); commit; (1) rownum 对于等于某值的查询条件 如果希望找到学生表中第一条学生的信息,可以使用rownum=1作为条件。但是想找到学生表中第二条学生的信息,使用rownum=2结果查不到数据。因为rownum都是从1开始,但是1以上的自然数在rownum做等于判断是时认为都是false条件,所以无法查到rownum = n(n>1的自然数)。 SQL> select rownum,id,name from student where rownum=1;(可以用在限制返回记录条数的地方,保证不出错,如:隐式游标) SQL> select rownum,id,name from student where rownum=1; ROWNUM ID NAME ---------- ------ --------------------------------------------------- 1 200001 张一 SQL> select rownum,id,name from student where rownum =2; ROWNUM ID NAME ---------- ------ --------------------------------------------------- (2)rownum对于大于某值的查询条件 如果想找到从第二行记录以后的记录,当使用rownum>2是查不出记录的,原因是由于rownum是一个总是从1开始的伪列,Oracle 认为rownum> n(n>1的自然数)这种条件依旧不成立,所以查不到记录 SQL> select rownum,id,name from student where rownum >2; ROWNUM ID NAME ---------- ------ --------------------------------------------------- 那如何才能找到第二行以后的记录呀。可以使用以下的子查询方法来解决。注意子查询中的rownum必须要有别名,否则还是不会查出记录来,这是因为rownum不是某个表的列,如果不起别名的话,无法知道rownum是子查询的列还是主查询的列。 SQL>select * from(select rownum no ,id,name from student) where no>2; NO ID NAME ---------- ------ --------------------------------------------------- 3 200003 李三 4 200004 赵四 SQL> select * from(select rownum,id,name from student)where rownum>2; ROWNUM ID NAME ---------- ------ --------------------------------------------------- (3)rownum对于小于某值的查询条件 如果想找到第三条记录以前的记录,当使用rownum<3是能得到两条记录的。显然rownum对于rownum<n((n>1的自然数)的条件认为是成立的,所以可以找到记录。 SQL> select rownum,id,name from student where rownum <3; ROWNUM ID NAME ---------- ------ --------------------------------------------------- 1 200001 张一 2 200002 王二 综上几种情况,可能有时候需要查询rownum在某区间的数据,那怎么办呀从上可以看出rownum对小于某值的查询条件是人为true的,rownum对于大于某值的查询条件直接认为是false的,但是可以间接的让它转为认为是true的。那就必须使用子查询。例如要查询rownum在第二行到第三行之间的数据,包括第二行和第三行数据,那么我们只能写以下语句,先让它返回小于等于三的记录行,然后在主查询中判断新的rownum的别名列大于等于二的记录行。但是这样的操作会在大数据集中影响速度。 SQL> select * from (select rownum no,id,name from student where rownum<=3 ) where no >=2; NO ID NAME ---------- ------ --------------------------------------------------- 2 200002 王二 3 200003 李三 (4)rownum和排序 Oracle中的rownum的是在取数据的时候产生的序号,所以想对指定排序的数据去指定的rowmun行数据就必须注意了。 SQL> select rownum ,id,name from student order by name; ROWNUM ID NAME ---------- ------ --------------------------------------------------- 3 200003 李三 2 200002 王二 1 200001 张一 4 200004 赵四 可以看出,rownum并不是按照name列来生成的序号。系统是按照记录插入时的顺序给记录排的号,rowid也是顺序分配的。为了解决这个问题,必须使用子查询 SQL> select rownum ,id,name from (select * from student order by name); ROWNUM ID NAME ---------- ------ --------------------------------------------------- 1 200003 李三 2 200002 王二 3 200001 张一 4 200004 赵四 这样就成了按name排序,并且用rownum标出正确序号(有小到大)
仅为个人理解.请指正Hibernate Session, 其作用无需多言. 在运用中为避免资源消耗,一般都会手动封装一个HibernateUtil类(未使用Spring管理的前提下). 该类的作用使Hibernate加载配置文件config, 创建sessionFactory等只运行一次. 实际运用中,经常需要将当前线程和session绑定.一般的用法为使用ThreadLocal: 在HibernateUtil类中封装hibernate的管理.通过openSession取得 session,并将其放入ThreadLocal变量中. 这样业务逻辑中仅需通过工具类取得当前线程对应的session.使用完毕后,调用工具类closeSession方法将 session关闭,当前线程的ThreadLocal变量置为NULL. 保证线程归还线程池复用后,ThreadLocal为空,以免出现导致其他线程访问到本线程变量. 而后,Hibernate的SessionFactory提供获取session的新方法getCurrentSession (获得与当前线程绑定的session). 内部通过代理封装,此方式得到的session 不仅和当前线程绑定,也无需手动开关. 默认在事务提交之后,session自动关闭. 需注意的是,必须在事务开启的前提之下才可使用此种方式获得的session.此外hibernate.cfg.xml配置文件中也许配置 <property name="current_session_context_class">thread</property> 基于线程 末了,引入Spring之后.sessionfactory的创建等都交给spring管理.Spring也提供了HibernateTemplate,HibernateDaoSupport这样的封装方法. 用户可以不再考虑session的管理,事务的开启关闭.只需配置事务即可. 而所谓session关闭后,因延迟加载导致前台无法显示的问题以往解决方式为强制全部加载,现在也可通过在web.xml中配置 org.springframework.orm.hibernate3.support.OpenSessionInViewFilter来解决. ------------------------------以下内容为工地资料------------------------------------------------------------------------------- 1 OpenSession : 手动打开,需手动关闭.[所以代码中充斥着try catch --sf.openSession --打开事务,提交-回滚 finall关闭session的代码]
2 threadlocal : hibernate给出的提示. 在HibernateUtil工具类中,new出threadlocal ,放入opensession.这样可以使当前线程绑定session.
使用后需关闭session,将threadlocal中session变量置为null .
3 getCurrentSession: hibernate3的新特性. 无需手动关闭session,自动获取当前线程的session,若无则新建之. 需在配置文件中配置thread属性.表明和当前线程绑定.
参考网友资料,getCurrentSession模式,内部开启了session自动提交的功能且使用getCurrentSession的session,及时做load操作,也需要打开事务.
以下为ThreadLocal的参考资料
http://space.itpub.net/9252210/viewspace-594453 今天在做数据导出的时候,由于用户名的密码使用的是特殊字符,所以遇到了错误 代码:“EXP-00056: 遇到 ORACLE 错误 12154”,网上查找原因,需要用引号扩起来,但是os不同,方式也不同: windows os: exp username/"""password"""@devdb --3个双引号扩密码 linux/unix os: exp 'username/"password"@devdb' --1个双引号扩密码,1个单引号扩全部 实验结果如下: 1.创建带有特殊字符密码的用户 C:\Documents and Settings\Home>sqlplus /nolog SQL*Plus: Release 10.2.0.1.0 - Production on 星期四 5月 7 17:37:36 2009 Copyright (c) 1982, 2005, Oracle. All rights reserved. SQL> connsys/oracle@devdbas sysdba 已连接。 SQL> create user exp identified by "12345!@#$%"; 用户已创建。 SQL> grant connect, resource to exp; 授权成功。 SQL> conn exp/"12345!@#$%"@devdb2 已连接。 SQL> create table table1 as select * from dual; 表已创建。 SQL> exit 2.windows os导出测试 C:\Documents and Settings\Home>expexp/12345!@#$%@devdbfile=c:\exp.dmp wner=exp Export: Release 9.2.0.1.0 - Production on 星期四 5月 7 17:39:42 2009 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved. EXP-00056: 遇到 ORACLE 错误 12154 ORA-12154: TNS: 无法处理服务名 EXP-00000: 导出终止失败 C:\Documents and Settings\Home>exp exp/"12345!@#$%"@devdb2file=c:\exp.dmp wner=exp Export: Release 9.2.0.1.0 - Production on 星期四 5月 7 17:39:57 2009 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved. EXP-00056: 遇到 ORACLE 错误 12154 ORA-12154: TNS: 无法处理服务名 EXP-00000: 导出终止失败 C:\Documents and Settings\Home>exp exp/"""12345!@#$%"""@devdb2file=c:\exp.dmp wner=exp Export: Release 9.2.0.1.0 - Production on 星期四 5月 7 17:41:54 2009 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved. 连接到: Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Production With the Partitioning, Real Application Clusters, OLAP, Data Mining and Real Application Testing options 已导出 ZHS16GBK 字符集和 AL16UTF16 NCHAR 字符集 . 正在导出 pre-schema 过程对象和操作 . 正在导出用户 EXP 的外部函数库名称 . 导出 PUBLIC 类型同义词 . 导出私有类型同义词 . 正在导出用户 EXP 的对象类型定义 即将导出 EXP 的对象 ... . 正在导出数据库链接 . 正在导出序号 . 正在导出群集定义 . 即将导出 EXP 的表通过常规路径 ... . . 正在导出表 TABLE1 1 行被导出 . 正在导出同义词 . 正在导出视图 . 正在导出存储的过程 . 正在导出运算符 . 正在导出引用完整性约束条件 . 正在导出触发器 . 正在导出索引类型 . 正在导出位图, 功能性索引和可扩展索引 . 正在导出后期表活动 . 正在导出实体化视图 . 正在导出快照日志 . 正在导出作业队列 . 正在导出刷新组和子组 . 正在导出维 . 正在导出 post-schema 过程对象和操作 . 正在导出统计 在没有警告的情况下成功终止导出。 3.linux/unix os导出测试 [oracle@rac2 ~]$ expexp/12345!@#$%@devdbfile=./exp.dmp wner=exp -bash:!@#$%@devdb: event not found [oracle@rac2 ~]$ exp exp/"""12345!@#$%"""@devdbfile=./exp.dmp wner=exp -bash:!@#$%"""@devdb: event not found [oracle@rac2 ~]$exp 'exp/"12345!@#$%"@devdb'file=./exp.dmp wner=exp Export: Release 10.2.0.4.0 - Production on Thu May 7 19:21:32 2009 Copyright (c) 1982, 2007, Oracle. All rights reserved. Connected to: Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Production With the Partitioning, Real Application Clusters, OLAP, Data Mining and Real Application Testing options Export done in US7ASCII character set and AL16UTF16 NCHAR character set server uses ZHS16GBK character set (possible charset conversion) . exporting pre-schema procedural objects and actions . exporting foreign function library names for user EXP . exporting PUBLIC type synonyms . exporting private type synonyms . exporting object type definitions for user EXP About to export EXP's objects ... . exporting database links . exporting sequence numbers . exporting cluster definitions . about to export EXP's tables via Conventional Path ... . . exporting table TABLE1 1 rows exported . exporting synonyms . exporting views . exporting stored procedures . exporting operators . exporting referential integrity constraints . exporting triggers . exporting indextypes . exporting bitmap, functional and extensible indexes . exporting posttables actions . exporting materialized views . exporting snapshot logs . exporting job queues . exporting refresh groups and children . exporting dimensions . exporting post-schema procedural objects and actions . exporting statistics Export terminated successfully without warnings.
关于group by 的应用问题 数据库内容为下面
写一SQL得出下面内容:
贴出SQL结果如下:(MySQL版本) create table gosin_temp(rq varchar(10),shengfu nchar(1)); insert into gosin_temp values('2009-05-09','胜'); insert into gosin_temp values('2009-05-09','胜'); insert into gosin_temp values('2009-05-09','负'); insert into gosin_temp values('2009-05-09','负'); insert into gosin_temp values('2009-05-10','胜'); insert into gosin_temp values('2009-05-10','负'); insert into gosin_temp values('2009-05-10','负'); select * from gosin_temp; 得到结果的SQL: select a1.rq,a1.胜,b1.负 from (select a.rq, count(a.shengfu) 胜 from gosin_temp a where a.shengfu='胜' group by a.rq) a1, (select b.rq, count(b.shengfu) 负 from gosin_temp b where b.shengfu='负' group by b.rq) b1 where a1.rq = b1.rq 类似的题目还有很多,如: 胜 负 1 a b 2 b a 3 b a 要求写一SQL语句,输出如下结果: 胜 负 a 1 2 b 2 1 其实都一样 只要熟悉使用group by 就不觉得难了。
摘自: http://www.iteye.com/topic/766418 ----------------------------------------- 1,什么是Servlet 2,Servlet有什么作用 3,Servlet的生命周期 4,Servlet怎么处理一个请求 5,Servlet与JSP有什么区别 6,Servlet里的cookie技术 7,Servlet里的过滤器 8,Servlet里的监听器 一,什么是Servlet? Servlet是一个Java编写的程序,此程序是基于Http协议的,在服务器端运行的(如tomcat), 是按照Servlet规范编写的一个Java类。
二,Servlet有什么作用? 主要是处理客户端的请求并将其结果发送到客户端。
三,Servlet的生命周期? Servlet的生命周期是由Servlet的容器来控制的,它可以分为3个阶段;初始化,运行,销毁。
初始化阶段: 1,Servlet容器加载servlet类,把servlet类的.class文件中的数据读到内存中。 2,然后Servlet容器创建一个ServletConfig对象。ServletConfig对象包含了Servlet的初始化配置信息。 3,Servlet容器创建一个servlet对象。 4,Servlet容器调用servlet对象的init方法进行初始化。
运行阶段: 当servlet容器接收到一个请求时,servlet容器会针对这个请求创建servletRequest和servletResponse对象。 然后调用service方法。并把这两个参数传递给service方法。Service方法通过servletRequest对象获得请求的 信息。并处理该请求。再通过servletResponse对象生成这个请求的响应结果。然后销毁servletRequest和 servletResponse对象。我们不管这个请求是post提交的还是get提交的,最终这个请求都会由service方法来处理。
web服务器接受到一个http请求后,web服务器会将请求移交给 servlet容器,servlet容器首先对所请求的URL进行解析并根据 web.xml 配置文件找到相应的处理servlet,同时将request、response对象传递给它,servlet通过request对象可知道客户端 的请求 者、请求信息以及其他的信息等,servlet在处理完请求后会把所有需要返回的信息放入response对象中并返回到客户端, servlet一旦处理 完请求,servlet容器就会刷新response对象,并把控制权重新返回给web服务器。 销毁阶段: 当Web应用被终止时,servlet容器会先调用servlet对象的destrory方法,然后再销毁servlet对象, 同时也会销毁与servlet对象相关联的servletConfig对象。我们可以在destroy方法的实现中,释放 servlet所占用的资源,如关闭数据库连接,关闭文件输入输出流等。
在这里该注意的地方: 在servlet生命周期中,servlet的初始化和和销毁阶段只会发生一次,而service方法执行的次数则取决于servlet被客户 端访问的次数 四,Servlet怎么处理一个请求? 当用户发送一个请求到某个Servlet的时候,Servlet容器会创建一个ServletRequst和ServletResponse对象。 在ServletRequst对象中封装了用户的请求信息,然后Servlet容器把ServletRequst和ServletResponse对象 传给用户所请求的Servlet,Servlet把处理好的结果写在ServletResponse中,然后Servlet容器把响应结果传 给用户。 五,Servlet与JSP有什么区别? 1,jsp经编译后就是servlet,也可以说jsp等于servlet。 2,jsp更擅长页面(表现)。servlet更擅长逻辑编辑。 (最核心的区别)。 3,在实际应用中采用Servlet来控制业务流程,而采用JSP来生成动态网页.在struts框架中, JSP位于MVC设计模式的视图层,而Servlet位于控制层。 六,Servlet里的cookie技术? cookies是一种WEB服务器通过浏览器在访问者的硬盘上存储信息的手段,是由Netscape公司开发出来的。 cookie技术的好处: 1,Cookie有效期限未到时,Cookie能使用户在不键入密码和用户名的情况下进入曾经浏览过的一些站点。 2,Cookie能使站点跟踪特定访问者的访问次数、最后访问时间和访问者进入站点的路径。 创建一个cookie Java代码 - //里面的两个参数分别是cookie的名和cookie的值
-
- response.addCookie(new Cookie("abc","10000000"));
使用cookie Java代码 - Cookie[] cook =request.getCookies();//用一个Cookie数组来接收
-
- for(int j=0;j<cook.length;j++){//通过循环来打印Cookie
-
- cook[j].getName()://取cookie的名
- cook[j].getValue()://去cookie的值
-
- }
七,Servlet里的过滤器? 过滤器的主要作用 1,任何系统或网站都要判断用户是否登录。 2,网络聊天系统或论坛,功能是过滤非法文字 3,统一解决编码 (2)怎么创建一个过滤器: 1,生成一个普通的class类,实现Filter接口(javax.servlet.Filter;)。 2,重写接口里面的三个方法:init,doFilter,destroy。 3,然后在web.xml配置过滤器。
八,Servlet里的监听器? 监听器的作用:自动执行一些操作。
三种servlet监听器: 对request的监听。对session的监听。对application的监听。
怎么创建一个session监听器: 1,生成一个普通的class类,如果是对session的监听,则实现HttpSessionListener。 2,然后重写里面的五个方法: Java代码 - public void sessionCreated(HttpSessionEvent arg0) {} // 创建
-
- public void sessionDestroyed(HttpSessionEvent arg0) {} // 销毁
-
- public void attributeAdded(HttpSessionEvent arg0) {} // 增加
-
- public void attributeRemoved(HttpSessionEvent arg0) {} // 删除
-
- public void attributeReplaced(HttpSessionEvent arg0) {} // 替换
这个写的不错.有的没看懂. 目前没时间细看,暂时先转载. 摘自: http://www.jdon.com/artichect/state.htm --------------------------------------------------- 板桥里人 http://www.jdon.com 2006/1/2(转载请保留) 这是一个实战中非常重要但是容易被忽视的概念,说它重要,是因为它比数据库重要;说它容易被忽视也是同样的原因,它经常被数据库概念替代。 如果你经验和经历中没有状态这个概念,极端地说:可能你的Java系统经验还未积累到一定程度,状态是每个Java程序员深入Java系统后必然碰到的问题。 本文我想试图表达的是:状态分两种:活动的状态对象和持久化的状态。而数据库中的数据只是状态的一种持久化结果,而Java系统 运行时,我们更多的可能是和一种活动的状态打交道,这种活动的状态存在内存中,而不是持久化到硬盘上,当然,需要时你可以通过数据库/文件持久化到硬盘上。 但是,如果你以数据库数据替代状态,那么就可能导致数据库的频繁访问,而且 你的系统会变成一个非对象化的、紧耦合、到处是分散数据块的糟糕系统。这样的系统并不比传统的两层结构好到哪里!也不会比Jsp里嵌入Java代码伪三层系统高明到什么地方。 什么是状态? 只要有对象就可能有状态,任何一个对象活动时,都有自己的状态属性,类的 字段属性极有可能成为状态,我们现在经常使用的Domain model其实就是一种 包含状态的对象,如果你对状态没有深入掌握,就不可能真正掌握对象系统特点,或者是Domain Model的执行情况。 对于初学者,经常会疑问:我是将数据放在HttpSession中还是Request中,这里 其实已经开始接触状态,一旦你接触状态,你就要开始小心,因为你可能会将内存泄漏的恶魔导引进来。 内存泄漏的恶魔爆发时刻取决于你状态的生存周期和系统并发访问量。 状态的生存周期也就是包含这个状态的对象的生命周期,在简单系统中,我们只 需要通过new创建对象,然后它的消亡就会依靠JVM垃圾回收机制回收,但是事情会这么简单吗? 状态的危险还会发生在多线程环境下,当多个线程对同一个内存中状态写操作时,这时怎么办?如果这个状 态持久化在数据库中,我们会依赖数据库提供的强大事务机制防止这种并发死锁,但是如果是在内存中,你就很难办,因此,我们就尽量避免发生这种多线程同时访 问一个状态的现象,而Singleton单例模式极容易发生这种现象,因此实践中,单例模式是J2EE开发中需要避免的,相关帖子讨论见: http://www.jdon.com/jive/article.jsp?forum=91&thread=17578 我们接触的Web容器或Jsp/Servlet本质就是一个多线程,这也是很多初学者不知道的, 因为多线程编程是复杂或困难的,因此才有jsp/Servlet这样的上层封装,但是我们使用他们 时,实际在进行多线程编程。 生命周期和多线程并发使得我们简单的面向对象系统变得异常复杂和难以掌握起来。下面我从这个两个角度,给出两种模式思维解决之道。 生命周期(Scope) 生命周期(Scope)就是指状态的活动周期,状态对象是什么时候被创建;然后什么时候被销毁,很显然,如果状态对象还没有被创建或已经被销毁,你再 访问这个状态对象可能失败,而状态的生命周期控制是可能散落在运行程序的各个地方,如果不象状态模式那样进行统一控制,有可能整个系统是危机四伏的。 状态的生命周期其实就是对象生命周期,更加细化地说:是Domain Model这个对象的生命周期。这在一个以领域模型为驱动的设计概念中不可回避的课题,而领域模型实战的复杂性就复杂在此。 状态的生命周期在J2EE中目前有三种:Request/Session和 Application,Request是每个客户端发出的一次请求,这是J2EE系统中最基本的事件激活单元, 当服务器端推出一个页面到客户端时,意味着这个Request的结束。那么如果我们的状态保存在Request中,意味着在request结束之前,这个 请求经历的任何一个环节都可以对这个状态(对象)进行操作。(掌握这个原理,对于你学习Struts和JSF很有帮助) 如果是Session,则一直和该客户端有关,只要是该客户端发出的每次request的任何环节都可以对这个状态(对象)进行操作。 如果是Application,则意味着这个状态是当前Web项目的全局状态。 这三种状态形式都是以将状态保存在内存中形式存在的,是和持久化状态相对的。是一种内存活动状态。 生命周期的选取当然是越短越好,这样,这个状态对象就可以被自动销毁,从而避免了 大访问量下的内存泄漏,但是在大访问量下,对象频繁创建和销毁是耗费性能的。 那么,我们可能经常使用HttpSession来保存状态,这时你极有可能造成内存泄漏,我经常在 Jdon论坛上看到将很多数据库数据暂时保存在HttpSession中想法,这是相当危险的,因为一旦并发用户很多,相当多的HttpSession包 含了状态,而状态中有可能有更多其他引用,因此内存很快会爆满,或者垃圾回收机制频繁启动,造成应用系统运行暂停或缓慢。 当你将状态放入HttpSession时,难道没有考虑将其手工消除吗?你要知道所有Web容器 (Tomcat/Weblogic等)都不会自动替你清除那些你可能不用的状态对象啊。如果每个人只管新增元素,不管重整或管理,这个系统能不变得混乱 吗?代码上这种现象我们是通过Refactoring等结构/行为模式来解决,那么在运行时的状态管理呢? 状态管理模式或者说对象管理模式正是解决这种问题的。 按照该模式,你必须手工自己管理放在HttpSession的状态,比如你为每个HttpSession 设立一个状态容器最大尺寸,当超过这个尺寸时,你需要将不用的状态从容器去除, 但是如果这个客户端在Session失效期内又来访问这个状态怎么办?那么你可能需要先临时将状态序列化保存到硬盘上,等Session失效期到达后再真正删除。 是不是觉得很麻烦? 捷径是有: 1. 尽量少使用HttpSession保存状态,这对集群环境也是有利的,见该贴讨论: http://www.jdon.com/jive/article.jsp?forum=121&thread=22282 那么这些状态放在哪里?使用Application的缓存中, 2. 使用状态管理中间件,目前有几个选择:EJB的有态Bean;NanoContainer之类状态相关的微容器。那么Spring可以吗?目前没有发现有 该功能,甚至在Spring容器内无法直接使用Session性质的状态,只能通过线程级别的ThreadLocal来实现(对不起,你又要开始回到远古 的汇编线程时代了);而Jdon框架则可以。 下面我们谈谈Application的状态,在这个范围内,一个对象状态可以被多个用户反复访问,在这个级别,状态类似数据库中数据,因为可以使用数据库来替代这个级别的状态,所以将状态放入缓存这个深层次技术被大多数初学者忽视了,甚至产生了对数据库依赖心理。 缓存中的状态 虽然我们将状态保存在Application中,但是我们不可避免还是遇到Session同样的状态管理问题,这个问题所幸的是有专门缓存中间件解决 了,当然,在一个多服务器集群系统,如果一个客户端在一个服务器中存放了状态,那么能否在另外一个服务器的内存中访问到呢?回答是肯定的,前提是你必须使 用分布式缓存系统。 目前分布式缓存系统是靠EJB服务器完成,当JBoss 5在2006变成完全解耦、可肢解时, 我们就可以使用原本只支持EJB的JBoss分布式缓存系统来支持我们的普通JavaBeans了(POJO)。这其中目前可能会花费一些力气,因为还没有一个统一的POJO构件接口标准,我相信以后 可能会有。 如果你不想花费力气,而且可能就只是一台服务器,可以通过双核芯片提升性能,那么单态缓存如果实现?很简单,使用一个缓存产品如OsCache等,将其设定保存在 Application中,或者在web.xml中进行一下简单的配置即可。 但是,这时你可能碰到另外一个问题:状态的唯一标识,如何通过唯一标识从缓存中那么 多对象状态中取出你要的那一个呢?比较琐碎。 有没有一个框架帮助你省却这些麻烦,当然推荐Jdon Framework,只要将包含状态的类(主要是Domain Model)继承特定的类或接口(接口在1.4版本实现)即可,这个类的对象运行时就会被缓存或从缓存中读取,再也无需你照料缓存了,就这么简单。 当然,Jdon Framework的底层缓存器是可以被替代,使用你喜欢的缓存产品,因为jdon Framework是基于Ioc设计,构件之间是完全解耦、可彻底肢解,能够通过配置替代和更换的。 如果你不明白这个道理,需要好好研究一下Ioc模式带给我们革命性的新变化。 从以上也可以看出:java复杂性还在于我们需要在编码时,却要想象其运行时的情形。而这种翻译联想没有深厚的实践功底,是很难顺利完成的。 状态管理中间件 自从J2EE开辟中间件时代以来,就有相当多的高级中间件提供与具体应用无关的通用功能,状态管理中间件很早就有之,EJB的有态Session Bean是一个代表。 一个中间件不但要有良好的松耦合设计,我们暂时称为静态设计;更要有优秀的动态设计,例如状态管理就属于一种动态设计。 当然,如果你比较谦虚,不但要选择一些静态设计很好的框架或中间件;而且还要依赖一些拥有良好的动态运行管理的中间件。 EJB无论是EJB1.X/EJB2.X/EJB3.X.在状态管理上要更加优秀,当然EJB3.X又吸收了优秀的静态设计概念,但是因为需要有一个具体服务器实现过程,这个过程中存在一些陷阱,如In-Box问题等。 Spring无疑是一个静态设计非常优秀框架,它一直在AOP上孜孜不倦,力图探索一条从AOP角度进行动态运行管理干预捷径,相信会有惊人结果,当然,这种细粒度的AOP需要实践检验,当然如果整入JDK 6.0就更好。 而Jdon Framework则试图在目前两者之间寻求了一个平衡,既有Ioc/AOP优秀的静态设计,虽然在AOP上不及Spring前卫;但提供了切实Session和Cache状态管理; 如果你不需要EJB的分布式多服务器集群功能;又不是AOP的超级粉丝,无疑使用Jdon Framework之类的框架无疑是简化方便的。 状态设计的难点 最后,我不得不重申,并不是有了良好的状态管理框架就可以高枕无忧了,状态的设计其实是我们每个项目必须面临的可变课题,如果状态复杂了可以使用状态模式对付,可惜往往状态不够复杂。 一个对象本身属性和状态是应该耦合在一起,还是进行分离,属性和状态没有明显的泾渭分明的界限,我们举一个例子: 论坛Forum这个对象,它有一些字段属性,如论坛名称、论坛描述,还有其他一些相关属性:如该论坛 的最新发帖;该论坛的发贴量,后两者好像也是论坛字段,但是他们可能经常变化的,应该属于状态,那么状态和Forum这个主体对象是什么关系?是将该论坛 的最新发帖和该论坛的发贴量两个字段并入Forum这个Domain Model中,还是应该单独建立一个状态对象?如果进行分离,分离的依据是什么? 当然,这里分离的依据是因为对象的生存周期不同。对于我们熟悉的课题,我们能够马上分辨出其中的生存周期,如果是不熟悉领域的建模呢? 所以,大家已经明白:状态设计的难点是:如何粒度细化地创建模型对象;然后分辨出其中动态的状态性质。这是域建模实战中一个难点。 很多人问我:你提倡的域建模、设计模式和框架是什么意思?为什么说他们是Java开发设计的三件宝呢?或者说三个典型知识点呢?我想通过本篇我已经通过状态这个概念稍微解释了域建模的一些特点。 当前,MDA中的四色原型模式Archetype将帮助我们更好地分辨出类的属性、状态和行为,这是一场带来以后十年的软件革命,
我对浏览器请求流程的理解: (1) 访问流程: (1.1) 系统运行在某web容器,如Tomcat(其运行和weblogic不同,Tomcat只有一个进程). 其预设有初始的线程数. (1.2) 浏览器打开某网站,网站及给其分配一个sessionID(页面隐式的发起request, 由Tomcat 某个特定的监听线程给予response一个sessionid). 该sessionid用以识别本次访问. (1.3) 用户点击登录/注册, 浏览器发起一个新的request, 由Tomcat线程池中空闲的线程进行 处理. 反馈结果于前台展现.如线程池中线程不足,则Tomcat每次按照一定规则创建出更多的空闲线程(其初始值,增加值,及最大值依据配置文件/JDK/硬件).
来源: http://www.iteye.com/topic/960652 http://godsend-jin.iteye.com/blog/1004386 -------------------------------------------------------------------- 最近在做登录和权限控制模块,用到了session,发现session的好多方法都不熟悉,而且以前也听说过JsessionId 之类session窃取的事, 对这些一直都是一知半解。今天索性google了很多资料,先上sun的官网去看session的文档了解一些方法,又找了别人关于session的看法。 总结如下: 1,session是什么? what session经常译为会话,以打电话为例,从开始拨号到挂断电话就是你会话的生存周期。 2,session 做什么用的 why? 首先举个例子: 咖啡店举行 消费满5杯咖啡赠送一杯的活动,可每个人一次消费5杯的时候非常少。这时候有3种办法: 1,店员看到每个顾客时都能记住其消费了多少杯,从而给其优惠,这是协议本身具有状态 2,给每个顾客一个卡片,上面记录顾客的每次消费,这是客户端保存状态 3,给每个顾客一个卡片,卡片上只有一个编号,顾客每次的消费记录在店里,这就是 服务端有状态 而http本身是无状态的,所以我们只能使用2,3中方法,来保存一些信息。 实际采用的是第3种方法,服务器段保存一次会话所有的信息,并生成一个唯一的id,这个id没有规律而且不会重复,将这个id传回到客户段, 保存到cookie中。每次访问服务器时,客户端都会偷偷将这个id传到服务器,服务器根据id查到这次会话保存的内容。就能实现会话中共享一些数据。 3,session怎样创建和销毁 ? how session是保存在内存中的,所以会有一些性能上的影响。因此本着这个原则,session是只有在使用到的时候才会被创建,如果始终没有用到 session,这个session是永远不会被创建的。 比如: 访问servlet ,只要你代码中没有 request.getSession()或request.getSession(true);这两行是等价的,那session是不会创建。 又 当你访问静态页面时,根本不会生成servlet,所以也不会创建session。 下面解释一些疑惑: session是第一次请求时创建的? 大家都知道 jsp是被编译成servlet才执行的,问题就在jsp编译的过程。 jsp中有个<%@ page session="true/false"%> 这个开关表示是否创建session,当你不写这行时,它会默认给你加上这句。所以会造成上面的疑惑。 当然还有一些标签中可能有getSession()操作,会产生一些不必要的session。 session只能在服务端销毁,有三种途径: 1,到达session的最大请求间隔时间时,2,session。invalidate() 3,服务器进程当掉。 这里也有一些疑惑: 浏览器关闭时,session就会注销。 首先浏览器关闭时,浏览器并没有给服务器发送任何消息,所以服务器不会知道浏览器何时关闭了。 上面我们知道取得session 是因为浏览器cookie中有sessionid,而普通cookie通常会是会话cookie,也就是说浏览器关闭时,这个cookie会被注销, 所以当你再访问服务器时就没有sessionid了,所以造成session关闭了的假象,如果昵称通过特殊方法将sessionid传递给服务器,你会发现session还在。 如果想让cookie保存时间长一些,就需要手动指定cookie的过期时间 4,实际项目中的难点: 1,浏览器禁用cookie 这就没办法保存sessionid了,可以采用url重写,转发,加隐藏字段等方法来将sessionid传给服务器。 如: baidu.com:jsessionid=adfasdfasdfasdfasdfafdadf?asdfasdf baidu.com?jsessionid=asdfasdfasdfadsfad&&adfdf 这根据服务器的不同实现,第一种可以将普通参数区分开。 2,多人共用session的问题 例: a 访问 baidu.com ,但他没有帐号,于是他将连接 baidu.com/login.jsp?jsessionid=adsfasdfad(这个a的sessionid) 发给B, B登录 后,a就相当于用b的帐号登录了。你们可以在在本地试试。 解决方法: 当发现通过sessionid从url指定时, 创建一个新的session,将旧session的信息复制到 新sessoin中,然后将新session注销。 就能防止上面那种情况了。 3,一个帐号多地方登录 比如: 你用abc帐号登录了baidu.com,有打开了一个浏览器,又用abc帐号登录了一次。当不设计敏感操作时,这无所谓,而当你做一些敏感操 作时就必须禁止这样情况,防止同时操作,造成重复操作,或者数据损坏。 解决方法: 监听session,将username和sessionid对应起来,当username再次登录时,注销掉以前的session,保存现在的session,这也是 一种比较不错的方案。 这是 sghcel 画的图,挺不错的: 其他: 1、session在何时被创建 2、session何时被删除
3、如何做到在浏览器关闭时删除session
4、有个HttpSessionListener是怎么回事 你可以创建这样的listener去监控session的创建和销毁事件,使得在发生这样的事件时你可以做一些相应的工作。注意是session的创建和销毁动作触发listener, 而不是相反。类似的与HttpSession有关的listener还有 HttpSessionBindingListener,HttpSessionActivationListener和 HttpSessionAttributeListener。 5、存放在session中的对象必须是可序列化的吗 6、如何才能正确的应付客户端禁止cookie的可能性 7、开两个浏览器窗口访问应用程序会使用同一个session还是不同的session 参见第三小节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器,不同的窗口打开方式以及不同的cookie存储方式都会对这个问题 的答案有影响。 8、如何防止用户打开两个浏览器窗口操作导致的session混乱 9、为什么在Weblogic Server中改变session的值后要重新调用一次session.setValue
10、HttpSession 和 hibernate session 有什么区别?
AOP术语介绍
1. 正常的编程为从上到下的调用,执行
2. 加入了安全性检查,日志这样的代码. 这是一个横切的问题,其于正常的业务毫无关系.
横切的问题会散布在代码的各个角落 .
3.这个横切就是横切性的关注点: Cross cutting concern
4. 将横切的关注点都放在一个类中(如动态代理项目中的SecurityHandler.java).这个类就叫做切面.
对横切关注点进行模块化,这个模块化的类就叫做切面类(Aspect对应的类) ,
5. 在切面类中对某个问题如日志或安全性检查的具体实现方法,叫做横切关注点的具体实现(称为Advice).
6. 这个Advice可以进行分类. :在业务方法执行之前,之后.异常时候……
7. Advice应用的目标方法范围(那些方法之前,之后,异常….)这个过滤范围叫做切入点Pointcut()
8 .植入
Advice应用的目标方法的过程叫做植入(Weave)
Spring只支持针对业务方法执行前,执行后进行植入. 即只支持方法级别的植入
植入的地方就叫做连接点.
Spring的Aop是使用代理模式.
横切问题的处理思路:
发现横切性的问题,将其模块化(切片).然后形成切片类,在其中实现这些横切性的功能.
对象: pickTicket 出库 1 : N pickTicketDetail 出库明细数据库: pickTicket的id作为pickTicketDetail 的外键(pickTicket.id)---------在对pickTicketDetail 某行数据进行insert/update/delete的事务提交给数据库进行持久化处理时,数据库会将pickTicket整个表给予只读锁. 此为避免修改pickTicketDetail 时,pickTicket的数据进行修改而出现pickTicketDetail中的pickTicket.id对应在pickTicket中不存在.(即确保数据库的数据完整性)发生场景: 事件A增删改某表A时,会对该表的所有外键对应的表给予只读锁.如此时其他事务B正提交数据库,类似的需要锁定事件A中已锁定的表. 此时2个事务都在等待对方表解锁.产生死锁. 可以处理方案: 1尽量缩短事务处理的时间.2对pickTicketDetail 增加pickTicket.id的索引. ------------ 相关参考:http://it.china-b.com/sjk/oracle/20090826/177376_1.html 测试: session 1: SQL> delete from emp where emp.deptno=10; session 2: SQL> delete from dept where deptno=40; 现象:在emp的字段deptno没有索引时session 2等待, 有索引则不等待. 结论:如果没有索引时,对父表的操作,会级联加一个TM S锁(level 4)到子表上; 如果有索引时,对父表的操作,会级联加一个TM RS锁(level 2)到子表上; 这时如果子表上本身就有个TM RX锁(这种锁很容易产生,insert update delete都会产生这种锁), TM S锁和TM RX锁是互斥的, TM RS锁和TM RX锁是相容的.
------- 最后:其实想记录的是"外键未加索引的问题" .此问题可以google下了解. 本文写的较乱,只为存疑记录.有待进一步了解学习.
摘自: http://www.iteye.com/topic/257191 ----------------- 今天下午研究了半天hashcode()和equals()方法,终于有了一点点的明白,写下来与大家分享(zhaoxudong 2008.10.23晚21.36)。 1. 首先equals()和hashcode()这两个方法都是从object类中继承过来的。 equals()方法在object类中定义如下: public boolean equals(Object obj) { return (this == obj); } 很明显是对两个对象的地址值进行的比较(即比较引用是否相同)。但是我们必需清楚,当String 、Math、还有Integer、Double。。。。等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法。比 如在String类中如下: public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = count; if (n == anotherString.count) { char v1[] = value; char v2[] = anotherString.value; int i = offset; int j = anotherString.offset; while (n-- != 0) { if (v1[i++] != v2[j++]) return false; } return true; } } return false; } 很明显,这是进行的内容比较,而已经不再是地址的比较。依次类推Double、Integer、Math。。。。等等这些类都是重写了equals()方法的,从而进行的是内容的比较。当然了基本类型是进行值的比较,这个没有什么好说的。 我们还应该注意,Java语言对equals()的要求如下,这些要求是必须遵循的: • 对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。 • 反射性:x.equals(x)必须返回是“true”。 • 类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。 • 还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。 • 任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。 以上这五点是重写equals()方法时,必须遵守的准则,如果违反会出现意想不到的结果,请大家一定要遵守。 2. 其次是hashcode() 方法,在object类中定义如下: public native int hashCode(); 说明是一个本地方法,它的实现是根据本地机器相关的。当然我们可以在自己写的类中覆盖hashcode()方法,比如String、 Integer、Double。。。。等等这些类都是覆盖了hashcode()方法的。例如在String类中定义的hashcode()方法如下: public int hashCode() { int h = hash; if (h == 0) { int off = offset; char val[] = value; int len = count; for (int i = 0; i < len; i++) { h = 31*h + val[off++]; } hash = h; } return h; } 解释一下这个程序(String的API中写到): s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 使用 int 算法,这里 s[i] 是字符串的第 i 个字符,n 是字符串的长度,^ 表示求幂。(空字符串的哈希码为 0。) 3.这里我们首先要明白一个问题: equals()相等的两个对象,hashcode()一定相等; equals()不相等的两个对象,却并不能证明他们的hashcode()不相等。换句话说,equals()方法不相等的两个对象,hashcode()有可能相等。(我的理解是由于哈希码在生成的时候产生冲突造成的)。 反过来:hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。解释 下第3点的使用范围,我的理解是在object、String等类中都能使用。在object类中,hashcode()方法是本地方法,返回的是对象的 地址值,而object类中的equals()方法比较的也是两个对象的地址值,如果equals()相等,说明两个对象地址值也相等,当然 hashcode()也就相等了;在String类中,equals()返回的是两个对象内容的比较,当两个对象内容相等时, Hashcode()方法根据String类的重写(第2点里面已经分析了)代码的分析,也可知道hashcode()返回结果也会相等。以此类 推,可以知道Integer、Double等封装类中经过重写的equals()和hashcode()方法也同样适合于这个原则。当然没有经过重写的 类,在继承了object类的equals()和hashcode()方法后,也会遵守这个原则。 4.谈到hashcode()和equals()就不能不说到hashset,hashmap,hashtable中的使用,具体是怎样呢,请看如下分析: Hashset是继承Set接口,Set接口又实现Collection接口,这是层次关系。那么hashset是根据什么原理来存取对象的呢? 在hashset中不允许出现重复对象,元素的位置也是不确定的。在hashset中又是怎样判定元素是否重复的呢?这就是问题的关键所在,经过一下午的查询求证终于获得了一点启示,和大家分享一下,在java的集合中,判断两个对象是否相等的规则是: 1),判断两个对象的hashCode是否相等 如果不相等,认为两个对象也不相等,完毕 如果相等,转入2) (这一点只是为了提高存储效率而要求的,其实理论上没有也可以,但如果没有,实际使用时效率会大大降低,所以我们这里将其做为必需的。后面会重点讲到这个问题。) 2),判断两个对象用equals运算是否相等 如果不相等,认为两个对象也不相等 如果相等,认为两个对象相等(equals()是判断两个对象是否相等的关键) 为什么是两条准则,难道用第一条不行吗?不行,因为前面已经说了,hashcode()相等时,equals()方法也可能不等,所以必须用第2条准则进行限制,才能保证加入的为非重复元素。 比如下面的代码: public static void main(String args[]){ String s1=new String("zhaoxudong"); String s2=new String("zhaoxudong"); System.out.println(s1==s2);//false System.out.println(s1.equals(s2));//true System.out.println(s1.hashCode());//s1.hashcode()等于s2.hashcode() System.out.println(s2.hashCode()); Set hashset=new HashSet(); hashset.add(s1); hashset.add(s2); /*实质上在添加s1,s2时,运用上面说到的两点准则,可以知道hashset认为s1和s2是相等的,是在添加重复元素,所以让s2覆盖了s1;*/ Iterator it=hashset.iterator(); while(it.hasNext()) { System.out.println(it.next()); } 最后在while循环的时候只打印出了一个”zhaoxudong”。 输出结果为:false true -967303459 -967303459 这是因为String类已经重写了equals()方法和hashcode()方法,所以在根据上面的第1.2条原则判定时,hashset认为它们是相等的对象,进行了重复添加。 但是看下面的程序: import java.util.*; public class HashSetTest { public static void main(String[] args) { HashSet hs=new HashSet(); hs.add(new Student(1,"zhangsan")); hs.add(new Student(2,"lisi")); hs.add(new Student(3,"wangwu")); hs.add(new Student(1,"zhangsan")); Iterator it=hs.iterator(); while(it.hasNext()) { System.out.println(it.next()); } } } class Student { int num; String name; Student(int num,String name) { this.num=num; this.name=name; } public String toString() { return num+":"+name; } } 输出结果为: 1:zhangsan 1:zhangsan 3:wangwu 2:lisi 问题出现了,为什么hashset添加了相等的元素呢,这是不是和hashset的原则违背了呢?回答是:没有 因为在根据hashcode()对两次建立的new Student(1,"zhangsan")对象进行比较时,生成的是不同的哈希码值,所以hashset把他当作不同的对象对待了,当然此时的 equals()方法返回的值也不等(这个不用解释了吧)。那么为什么会生成不同的哈希码值呢?上面我们在比较s1和s2的时候不是生成了同样的哈希码 吗?原因就在于我们自己写的Student类并没有重新自己的hashcode()和equals()方法,所以在比较时,是继承的object类中的 hashcode()方法,呵呵,各位还记得object类中的hashcode()方法比较的是什么吧!! 它是一个本地方法,比较的是对象的地址(引用地址),使用new方法创建对象,两次生成的当然是不同的对象了(这个大家都能理解吧。。。),造成 的结果就是两个对象的hashcode()返回的值不一样。所以根据第一个准则,hashset会把它们当作不同的对象对待,自然也用不着第二个准则进行 判定了。那么怎么解决这个问题呢?? 答案是:在Student类中重新hashcode()和equals()方法。 例如: class Student { int num; String name; Student(int num,String name) { this.num=num; this.name=name; } public int hashCode() { return num*name.hashCode(); } public boolean equals(Object o) { Student s=(Student)o; return num==s.num && name.equals(s.name); } public String toString() { return num+":"+name; } } 根据重写的方法,即便两次调用了new Student(1,"zhangsan"),我们在获得对象的哈希码时,根据重写的方法hashcode(),获得的哈希码肯定是一样的(这一点应该没有疑问吧)。 当然根据equals()方法我们也可判断是相同的。所以在向hashset集合中添加时把它们当作重复元素看待了。所以运行修改后的程序时,我们会发现运行结果是: 1:zhangsan 3:wangwu 2:lisi 可以看到重复元素的问题已经消除。 关于在hibernate的pojo类中,重新equals()和hashcode()的问题: 1),重点是equals,重写hashCode只是技术要求(为了提高效率) 2),为什么要重写equals呢,因为在java的集合框架中,是通过equals来判断两个对象是否相等的 3),在hibernate中,经常使用set集合来保存相关对象,而set集合是不允许重复的。我们再来谈谈前面提到在向hashset集合中添加元素时,怎样判断对象是否相同的准则,前面说了两条,其实只要重写equals()这一条也可以。 但当hashset中元素比较多时,或者是重写的equals()方法比较复杂时,我们只用equals()方法进行比较判断,效率也会非常低, 所以引入了hashcode()这个方法,只是为了提高效率,但是我觉得这是非常有必要的(所以我们在前面以两条准则来进行hashset的元素是否重复 的判断)。 比如可以这样写: public int hashCode(){ return 1;}//等价于hashcode无效 这样做的效果就是在比较哈希码的时候不能进行判断,因为每个对象返回的哈希码都是1,每次都必须要经过比较equals()方法后才能进行判断是否重复,这当然会引起效率的大大降低。 我有一个问题,如果像前面提到的在hashset中判断元素是否重复的必要方法是equals()方法(根据网上找到的观点),但是这里并没有涉及到关于哈希表的问题,可是这个集合却叫hashset,这是为什么?? 我想,在hashmap,hashtable中的存储操作,依然遵守上面的准则。所以这里不再多说。这些是今天看书,网上查询资料,自己总结出来 的,部分代码和语言是引述,但是千真万确是自己总结出来的。有错误之处和不详细不清楚的地方还请大家指出,我也是初学者,所以难免会有错误的地方,希望大 家共同讨论。
摘自: http://shitou521.iteye.com/blog/696006 JNDI的一篇文章 前端时间总是在搞不清JNDI到底是干什么,虽然是一值在用,却不知道他最初出现的原因,用来,说不清是用来干什么,下面我相信介能解开这个迷雾里。 【 转贴一篇】 ------------ JNDI是 Java 命名与目录接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之一,不少专家认为,没有透彻理解JNDI的意义和作用,就没有真正掌握J2EE特别是EJB的知识。 那么,JNDI到底起什么作用? //带着问题看文章是最有效的 要了解JNDI的作用,我们可以从“如果不用JNDI我们怎样做?用了JNDI后我们又将怎样做?”这个问题来探讨。 没有JNDI的做法: 程序员开发时,知道要开发访问MySQL数据库的应用,于是将一个对 MySQL JDBC 驱动程序类的引用进行了编码,并通过使用适当的 JDBC URL 连接到数据库。 就像以下代码这样: - Connection conn=null;
- try {
- Class.forName("com.mysql.jdbc.Driver",
- true, Thread.currentThread().getContextClassLoader());
- conn=DriverManager.
- getConnection("jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue");
- ......
- conn.close();
- } catch(Exception e) {
- e.printStackTrace();
- } finally {
- if(conn!=null) {
- try {
- conn.close();
- } catch(SQLException e) {}
- }
- }
这是传统的做法,也是以前非Java程序员(如Delphi、VB等)常见的做法。这种做法一般在小规模的开发过程中不会产生问题,只要程序员熟悉Java语言、了解JDBC技术和MySQL,可以很快开发出相应的应用程序。 没有JNDI的做法存在的问题: 1、数据库服务器名称MyDBServer 、用户名和口令都可能需要改变,由此引发JDBC URL需要修改; 2、数据库可能改用别的产品,如改用DB2或者Oracle,引发JDBC驱动程序包和类名需要修改; 3、随着实际使用终端的增加,原配置的连接池参数可能需要调整; 4、...... 解决办法: 程 序员应该不需要关心“具体的数据库后台是什么?JDBC驱动程序是什么?JDBC URL格式是什么?访问数据库的用户名和口令是什么?”等等这些问题,程序员编写的程序应该没有对 JDBC 驱动程序的引用,没有服务器名称,没有用户名称或口令 —— 甚至没有数据库池或连接管理。而是把这些问题交给J2EE容器来配置和管理,程序员只需要对这些配置和管理进行引用即可。 由此,就有了JNDI。 //看的出来,是为了一个最最核心的问题:是为了解耦,是为了开发出更加可维护、可扩展//的系统 用了JNDI之后的做法: 首先, 在在J2EE容器中配置JNDI参数,定义一个数据源,也就是JDBC引用参数,给这个数据源设置一个名称;然后,在程序中,通过数据源名称引用数据源从而访问后台数据库。 //红色的字可以看出,JNDI是由j2ee容器提供的功能 具体操作如下(以JBoss为例): 1、配置数据源 在JBoss 的 D:\jboss420GA\docs\examples\jca 文件夹下面,有很多不同数据库引用的数据源定义模板。将其中的 mysql-ds.xml 文件Copy到你使用的服务器下,如 D:\jboss420GA\server\default\deploy。 修改 mysql-ds.xml 文件的内容,使之能通过JDBC正确访问你的MySQL数据库,如下: - <?xml version="1.0" encoding="UTF-8"?>
- <datasources>
- <local-tx-datasource>
- <jndi-name>MySqlDS</jndi-name>
- <connection-url>jdbc:mysql://localhost:3306/lw</connection-url>
- <driver-class>com.mysql.jdbc.Driver</driver-class>
- <user-name>root</user-name>
- <password>rootpassword</password>
- <exception-sorter-class-name>
- org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
- </exception-sorter-class-name>
- <metadata>
- <type-mapping>mySQL</type-mapping>
- </metadata>
- </local-tx-datasource>
- </datasources>
这里,定义了一个名为MySqlDS的数据源,其参数包括JDBC的URL,驱动类名,用户名及密码等。 2、在程序中引用数据源: - Connection conn=null;
- try {
- Context ctx=new InitialContext();
- Object datasourceRef=ctx.lookup("java:MySqlDS"); //引用数据源
- DataSource ds=(Datasource)datasourceRef;
- conn=ds.getConnection();
- ......
- c.close();
- } catch(Exception e) {
- e.printStackTrace();
- } finally {
- if(conn!=null) {
- try {
- conn.close();
- } catch(SQLException e) { }
- }
- }
直接使用JDBC或者通过JNDI引用数据源的编程代码量相差无几,但是现在的程序可以不用关心具体JDBC参数了。 //解藕了,可扩展了 在系统部署后,如果数据库的相关参数变更,只需要重新配置 mysql-ds.xml 修改其中的JDBC参数,只要保证数据源的名称不变,那么程序源代码就无需修改。 由此可见, JNDI避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。 JNDI的扩展: JNDI在满足了数据源配置的要求的基础上,还进一步扩充了作用:所有与系统外部的资源的引用,都可以通过JNDI定义和引用。 //注意什么叫资源 所以,在J2EE规范中,J2EE 中的资源并不局限于 JDBC 数据源。引用的类型有很多,其中包括资源引用(已经讨论过)、环境实体和 EJB 引用。特别是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另外一项关键角色:查找其他应用程序组件。 EJB 的 JNDI 引用非常类似于 JDBC 资源的引用。在服务趋于转换的环境中,这是一种很有效的方法。可以对应用程序架构中所得到的所有组件进行这类配置管理,从 EJB 组件到 JMS 队列和主题,再到简单配置字符串或其他对象,这可以降低随时间的推移服务变更所产生的维护成本,同时还可以简化部署,减少集成工作。外部资源”。 总结: J2EE 规范要求所有 J2EE 容器都要提供 JNDI 规范的实现。//sun 果然喜欢制定规范JNDI 在 J2EE 中的角色就是“交换机” —— J2EE 组件在运行时间接地查找其他组件、资源或服务的通用机制。在多数情况下,提供 JNDI 供应者的容器可以充当有限的数据存储,这样管理员就可以设置应用程序的执行属性,并让其他应用程序引用这些属性(Java 管理扩展(Java Management Extensions,JMX)也可以用作这个目的)。JNDI 在 J2EE 应用程序中的主要角色就是提供间接层,这样组件就可以发现所需要的资源,而不用了解这些间接性。 在 J2EE 中,JNDI 是把 J2EE 应用程序合在一起的粘合剂,JNDI 提供的间接寻址允许跨企业交付可伸缩的、功能强大且很灵活的应用程序。这是 J2EE 的承诺,而且经过一些计划和预先考虑,这个承诺是完全可以实现的。 从上面的文章中可以看出: 1、JNDI 提出的目的是为了解藕,是为了开发更加容易维护,容易扩展,容易部署的应用。 2、JNDI 是一个sun提出的一个规范(类似于jdbc),具体的实现是各个j2ee容器提供商,sun 只是要求,j2ee容器必须有JNDI这样的功能。 3、JNDI 在j2ee系统中的角色是“交换机”,是J2EE组件在运行时间接地查找其他组件、资源或服务的通用机制。 4、JNDI 是通过资源的名字来查找的,资源的名字在整个j2ee应用中(j2ee容器中)是唯一的。
再转一篇文章:
JNDI全称 Java Naming and Directory Interface JNDI 是Java平台的一个标准扩展,提供了一组接口、类和关于命名空间的概念。如同其它很多Java技术一样,JDNI是provider-based的技 术,暴露了一个API和一个服务供应接口(SPI)。这意味着任何基于名字的技术都能通过JNDI而提供服务,只要JNDI支持这项技术。JNDI目前所 支持的技术包括LDAP、CORBA Common Object Service(COS)名字服务、RMI、NDS、DNS、Windows注册表等等。很多J2EE技术,包括EJB都依靠JNDI来组织和定位实体。 JDNI通过绑定的概念将对象和名称联系起来。在一个文件系统中,文件名被绑定给文件。在DNS中,一个IP地址绑定一个URL。在目录服务中,一个对象名被绑定给一个对象实体。 JNDI 中的一组绑定作为上下文来引用。每个上下文暴露的一组操作是一致的。例如,每个上下文提供了一个查找操作,返回指定名字的相应对象。每个上下文都提供了绑 定和撤除绑定名字到某个对象的操作。JNDI使用通用的方式来暴露命名空间,即使用分层上下文以及使用相同命名语法的子上下文。 jndi的用途: 1。你可以用jndi来得到object类的属性 如: - Attribute attr =directory.getAttributes(personName).get("email");
- String email = (String)attr.get();
2。你可以用jndi来搜索对象 如: - foxes = directory.search("o=Wiz,c=US", "sn=Fox", controls);
查找谁的名字叫Fox在wiz部门的员工? 3。你可以用jndi通过naming/directory服务查询像printers和databases的对象 如:查询 Printer - Printer printer = (Printer)namespace.lookup(printerName);
- printer.print(document);
4。你可以用jndi列表出命名空间的特殊级别的内容 如: - NamingEnumeration list = namespace.list("o=Widget, c=US";
- while (list.hasMore()) {
- NameClassPair entry = (NameClassPair)list.next();
- display(entry.getName(), entry.getClassName());
- }
各种数字类型转换成字符串型:
String s = String.valueOf( value); // 其中 value 为任意一种数字类型。
字符串型转换成各种数字类型:
String s = "169";
byte b = Byte.parseByte( s );
short t = Short.parseShort( s );
int i = Integer.parseInt( s );
long l = Long.parseLong( s );
Float f = Float.parseFloat( s );
Double d = Double.parseDouble( s );
数字类型与数字类对象之间的转换:
byte b = 169;
Byte bo = new Byte( b );
b = bo.byteValue();
short t = 169;
Short to = new Short( t );
t = to.shortValue();
int i = 169;
b = bo.byteValue();
short t = 169;
Short to = new Short( t );
t = to.shortValue();
int i = 169;
Integer io = new Integer( i );
i = io.intValue();
long l = 169;
Long lo = new Long( l );
l = lo.longValue();
float f = 169f;
Float fo = new Float( f );
f = fo.floatValue();
double d = 169f;
Double dObj = new Double( d );
d = dObj.doubleValue();
- 触发器使用教程和命名规范
-
-
- 目 录
- 触发器使用教程和命名规范 1
- 1,触发器简介 1
- 2,触发器示例 2
- 3,触发器语法和功能 3
- 4,例一:行级触发器之一 4
- 5,例二:行级触发器之二 4
- 6,例三:INSTEAD OF触发器 6
- 7,例四:语句级触发器之一 8
- 8,例五:语句级触发器之二 9
- 9,例六:用包封装触发器代码 10
- 10,触发器命名规范 11
-
- 1,触发器简介
- 触发器(Trigger)是数据库对象的一种,编码方式类似存储过程,与某张表(Table)相关联,当有DML语句对表进行操作时,可以引起触发器的执行,达到对插入记录一致性,正确性和规范性控制的目的。在当年C/S时代盛行的时候,由于客户端直接连接数据库,能保证数据库一致性的只有数据库本身,此时主键(Primary Key),外键(Foreign Key),约束(Constraint)和触发器成为必要的控制机制。而触发器的实现比较灵活,可编程性强,自然成为了最流行的控制机制。到了B/S时代,发展成4层架构,客户端不再能直接访问数据库,只有中间件才可以访问数据库。要控制数据库的一致性,既可以在中间件里控制,也可以在数据库端控制。很多的青睐Java的开发者,随之将数据库当成一个黑盒,把大多数的数据控制工作放在了Servlet中执行。这样做,不需要了解太多的数据库知识,也减少了数据库编程的复杂性,但同时增加了Servlet编程的工作量。从架构设计来看,中间件的功能是检查业务正确性和执行业务逻辑,如果把数据的一致性检查放到中间件去做,需要在所有涉及到数据写入的地方进行数据一致性检查。由于数据库访问相对于中间件来说是远程调用,要编写统一的数据一致性检查代码并非易事,一般采用在多个地方的增加类似的检查步骤。一旦一致性检查过程发生调整,势必导致多个地方的修改,不仅增加工作量,而且无法保证每个检查步骤的正确性。触发器的应用,应该放在关键的,多方发起的,高频访问的数据表上,过多使用触发器,会增加数据库负担,降低数据库性能。而放弃使用触发器,则会导致系统架构设计上的问题,影响系统的稳定性。
-
-
- 2,触发器示例
- 触发器代码类似存储过程,以PL/SQL脚本编写。下面是一个触发器的示例:
- 新建员工工资表salary
- create table SALARY
- (
- EMPLOYEE_ID NUMBER, --员工ID
- MONTH VARCHAR2(6), --工资月份
- AMOUNT NUMBER --工资金额
- )
-
- 创建与salary关联的触发器salary_trg_rai
- 1 Create or replace trigger salary_trg_rai
- 2 After insert on salary
- 3 For each row
- 4 declare
- 5 Begin
- 6 Dbms_output.put_line(‘员工ID:’ || :new.employee_id);
- 7 Dbms_output.put_line(‘工资月份:’ || :new.month);
- 8 Dbms_output.put_line(‘工资:’ || :new.amount);
- 9 Dbms_output.put_line(‘触发器已被执行’);
- 10 End;
- 打开一个SQL Window窗口(使用PL/SQL Developer工具),或在sqlplus中输入:
- Insert into salary(employee_id, month, amount) values(1, ‘200606’, 10000);
- 执行后可以在sqlplus中,或在SQL Window窗口的Output中见到
- 员工ID:1
- 工资月份:200606
- 工资:10000
- 触发器已执行
-
- 在代码的第一行,定义了数据库对象的类型是trigger,定义触发器的名称是salary_trg_rai
- 第二行说明了这是一个after触发器,在DML操作实施之后执行。紧接着的insert说明了这是一个针对insert操作的触发器,每个对该表进行的insert操作都会执行这个触发器。
- 第三行说明了这是一个针对行级的触发器,当插入的记录有n条时,在每一条插入操作时都会执行该触发器,总共执行n次。
- Declare后面跟的是本地变量定义部分,如果没有本地变量定义,此部分可以为空
- Begin和end括起来的代码,是触发器的执行部分,一般会对插入记录进行一致性检查,在本例中打印了插入的记录和“触发器已执行”。
- 其中:new对象表示了插入的记录,可以通过:new.column_name来引用记录的每个字段值
-
-
- 3,触发器语法和功能
- 触发器的语法如下
- CREATE OR REPLACE TRIGGER trigger_name
- <before | after | instead of> <insert | update | delete> ON table_name
- [FOR EACH ROW]
- WHEN (condition)
- DECLARE
- BEGIN
- --触发器代码
- END;
-
- Trigger_name 是触发器的名称。<before | after | instead of>可以选择before或者after或instead of。 Before表示在DML语句实施前执行触发器,而after表示在在dml语句实施之后执行触发器,instead of触发器用在对视图的更新上。<insert | update | delete>可以选择一个或多个DML语句,如果选择多个,则用or分开,如:insert or update。Table_name是触发器关联的表名。
- [FOR EACH ROW]为可选项,如果注明了FOR EACH ROW,则说明了该触发器是一个行级的触发器,DML语句处理每条记录都会执行触发器;否则是一个语句级的触发器,每个DML语句触发一次。
- WHEN后跟的condition是触发器的响应条件,只对行级触发器有效,当操作的记录满足condition时,触发器才被执行,否则不执行。Condition中可以通过new对象和old对象(注意区别于前面的:new和:old,在代码中引用需要加上冒号)来引用操作的记录。
- 触发器代码可以包括三种类型:未涉及数据库事务代码,涉及关联表(上文语法中的table_name)数据库事务代码,涉及除关联表之外数据库事务代码。其中第一种类型代码只对数据进行简单运算和判断,没有DML语句,这种类型代码可以在所有的触发器中执行。第二种类型代码涉及到对关联表的数据操作,比如查询关联表的总记录数或者往关联表中插入一条记录,该类型代码只能在语句级触发器中使用,如果在行级触发器中使用,将会报ORA-04091错误。第三种类型代码涉及到除关联表之外的数据库事务,这种代码可以在所有触发器中使用。
-
- 从触发器的功能上来看,可以分成3类:
- 重写列(仅限于before触发器)
- 采取行动(任何触发器)
- 拒绝事务(任何触发器)
- “重写列”用于对表字段的校验,当插入值为空或者插入值不符合要求,则触发器用缺省值或另外的值代替,在多数情况下与字段的default属性相同。这种功能只能在行级before触发器中执行。“采取行动”针对当前事务的特点,对相关表进行操作,比如根据当前表插入的记录更新其他表,银行中的总帐和分户帐间的总分关系就可以通过这种触发器功能来维护。“拒绝事务”用在对数据的合法性检验上,当更新的数据不满足表或系统的一致性要求,则通过抛出异常的方式拒绝事务,在其上层的代码可以捕获这个异常并进行相应操作。
-
- 下面将通过举例说明,在例子中将触发器主体的语法一一介绍,读者可以在例子中体会触发器的功能。
-
- 4,例一:行级触发器之一
- CREATE OR REPLACE TRIGGER salary_raiu
- AFTER INSERT OR UPDATE OF amount ON salary
- FOR EACH ROW
- BEGIN
- IF inserting THEN
- dbms_output.put_line(‘插入’);
- ELSIF updating THEN
- dbms_output.put_line(‘更新amount列’);
- END IF;
- END;
- 以上是一个after insert和after update的行级触发器。在第二行中of amount on salary的意思是只有当amount列被更新时,update触发器才会有效。所以,以下语句将不会执行触发器:
- Update salary set month = ‘200601’ where month = ‘200606’;
- 在触发器主体的if语句表达式中,inserting, updating和deleting可以用来区分当前是在做哪一种DML操作,可以作为把多个类似触发器合并在一个触发器中判别触发事件的属性。
-
- 5,例二:行级触发器之二
- 新建员工表employment
- CREATE TABLE EMPLOYMENT
- (
- EMPLOYEE_ID NUMBER, --员工ID
- MAXSALARY NUMBER --工资上限
- )
- 插入两条记录
- Insert into employment values(1, 1000);
- Insert into employment values(2, 2000);
-
- CREATE OR REPLACE TRIGGER salary_raiu
- AFTER INSERT OR UPDATE OF amount ON salary
- FOR EACH ROW
- WHEN ( NEW.amount >= 1000 AND (old.amount IS NULL OR OLD.amount <= 500))
- DECLARE
- v_maxsalary NUMBER;
- BEGIN
- SELECT maxsalary
- INTO v_maxsalary
- FROM employment
- WHERE employee_id = :NEW.employee_id;
- IF :NEW.amount > v_maxsalary THEN
- raise_application_error(-20000, '工资超限');
- END IF;
- END;
-
- 以上的例子引入了一个新的表employment,表中的maxsalary字段代表该员工每月所能分配的最高工资。下面的触发器根据插入或修改记录的 employee_id,在employment表中查到该员工的每月最高工资,如果插入或修改后的amount超过这个值,则报错误。
- 代码中的when子句表明了该触发器只针对修改或插入后的amount值超过1000,而修改前的amount值小于500的记录。New对象和old对象分别表示了操作前和操作后的记录对象。对于insert操作,由于当前操作记录无历史对象,所以old对象中所有属性是null;对于delete操作,由于当前操作记录没有更新对象,所以new对象中所有属性也是null。但在这两种情况下,并不影响old和new对象的引用和在触发器主体中的使用,和普通的空值作同样的处理。
- 在触发器主体中,先通过:new.employee_id,得到该员工的工资上限,然后在if语句中判断更新后的员工工资是否超限,如果超限则错误代码为-20000,错误信息为“工资超限”的自定义错误。其中的raise_application_error包含两个参数,前一个是自定义错误代码,后一个是自定义错误代码信息。其中自定义错误代码必须小于或等于-20000。执行完该语句后,一个异常被抛出,如果在上一层有exception子句,该异常将被捕获。如下面代码:
- DECLARE
- code NUMBER;
- msg VARCHAR2(500);
- BEGIN
- INSERT INTO salary (employee_id, amount) VALUES (2, 5000);
- EXCEPTION
- WHEN OTHERS THEN
- code := SQLCODE;
- msg := substr(SQLERRM, 1, 500);
- dbms_output.put_line(code);
- dbms_output.put_line(msg);
- END;
- 执行后,将在output中或者sqlplus窗口中见着以下信息:
- -20000
- ORA-20000: 工资超出限制
- ORA-06512: 在"SCOTT.SALARY_RAI", line 9
- ORA-04088: 触发器 'SCOTT.SALARY_RAI' 执行过程中出错
-
- 这里的raise_application_error相当于拒绝了插入或者修改事务,当上层代码接受到这个异常后,判断该异常代码等于-20000,可以作出回滚事务或者继续其他事务的处理。
-
- 以上两个例子中用到的inserting, updating, deleting和raise_application_error都是dbms_standard包中的函数,具体的说明可以参照Oracle的帮助文档。
- create or replace package sys.dbms_standard is
- procedure raise_application_error(num binary_integer, msg varchar2,
- function inserting return boolean;
- function deleting return boolean;
- function updating return boolean;
- function updating (colnam varchar2) return boolean;
- end;
-
- 对于before和after行级触发器,:new和:old对象的属性值都是一样的,主要是对于在Oracle约束(Constraint)之前或之后的执行触发器的选择。需要注意的是,可以在before行触发器中更改:new对象中的值,但是在after行触发器就不行。
-
- 下面介绍一种instead of触发器,该触发器主要使用在对视图的更新上,以下是instead of触发器的语法:
- CREATE OR REPLACE TRIGGER trigger_name
- INSTEAD OF <insert | update | delete> ON view_name
- [FOR EACH ROW]
- WHEN (condition)
- DECLARE
- BEGIN
- --触发器代码
- END;
-
- 其他部分语法同前面所述的before和after语法是一样的,唯一不同的是在第二行用上了instead of关键字。对于普通的视图来说,进行 insert等操作是被禁止的,因为Oracle无法知道操作的字段具体是哪个表中的字段。但我们可以通过建立instead of触发器,在触发器主体中告诉Oracle应该更新,删除或者修改哪些表的哪部分字段。如:
-
- 6,例三:instead of触发器
- 新建视图
- CREATE VIEW employee_salary(employee_id, maxsalary, MONTH, amount) AS
- SELECT a.employee_id, a.maxsalary, b.MONTH, b.amount
- FROM employment a, salary b
- WHERE a.employee_id = b.employee_id
-
- 如果执行插入语句
- INSERT INTO employee_salary(employee_id, maxsalary, MONTH, amount)
- VALUES(10, 100000, '200606', 10000);
- 系统会报错:
- ORA-01779:无法修改与非键值保存表对应的列
-
- 我们可以通过建立以下的instead of存储过程,将插入视图的值分别插入到两个表中:
- create or replace trigger employee_salary_rii
- instead of insert on employee_salary
- for each ROW
- DECLARE
- v_cnt NUMBER;
- BEGIN
- --检查是否存在该员工信息
- SELECT COUNT(*)
- INTO v_cnt
- FROM employment
- WHERE employee_id = :NEW.employee_id;
- IF v_cnt = 0 THEN
- INSERT INTO employment
- (employee_id, maxsalary)
- VALUES
- (:NEW.employee_id, :NEW.maxsalary);
- END IF;
- --检查是否存在该员工的工资信息
- SELECT COUNT(*)
- INTO v_cnt
- FROM salary
- WHERE employee_id = :NEW.employee_id
- AND MONTH = :NEW.MONTH;
- IF v_cnt = 0 THEN
- INSERT INTO salary
- (employee_id, MONTH, amount)
- VALUES
- (:NEW.employee_id, :NEW.MONTH, :NEW.amount);
- END IF;
- END employee_salary_rii;
-
- 该触发器被建立后,执行上述insert操作,系统就会提示成功插入一条记录。
- 但需要注意的是,这里的“成功插入一条记录”,只是Oracle并未发现触发器中有异常抛出,而根据insert语句中涉及的记录数作出一个判断。若触发器的主体什么都没有,只是一个空语句,Oracle也会报“成功插入一条记录”。同样道理,即使在触发器主体里往多个表中插入十条记录,Oracle的返回也是“成功插入一条记录”。
-
-
-
-
- 行级触发器可以解决大部分的问题,但是如果需要对本表进行扫描检查,比如要检查总的工资是否超限了,用行级触发器是不行的,因为行级触发器主体中不能有涉及到关联表的事务,这时就需要用到语句级触发器。以下是语句级触发器的语法:
- CREATE OR REPLACE TRIGGER trigger_name
- <before | after | instead of ><insert | update | delete > ON table_name
- DECLARE
- BEGIN
- --触发器主体
- END;
-
- 从语法定义上来看,行级触发器少了for each row,也不能使用when子句来限定入口条件,其他部分都是一样的,包括insert, update, delete和instead of都可以使用。
-
-
- 7,例四:语句级触发器之一
- CREATE OR REPLACE TRIGGER salary_saiu
- AFTER INSERT OR UPDATE OF amount ON salary
- DECLARE
- v_sumsalary NUMBER;
- BEGIN
- SELECT SUM(amount) INTO v_sumsalary FROM salary;
- IF v_sumsalary > 500000 THEN
- raise_application_error(-20001, '总工资超过500000');
- END IF;
- END;
-
- 以上代码定义了一个语句级触发器,该触发器检查在insert和update了amount字段后操作后,工资表中所有工资记录累加起来是否超过500000,如果超过则抛出异常。从这个例子可以看出,语句级触发器可以对关联表表进行扫描,扫描得到的结果可以用来作为判断一致性的标志。需要注意的是,在 before语句触发器主体和after语句触发器主体中对关联表进行扫描,结果是不一样的。在before语句触发器主体中扫描,扫描结果将不包括新插入和更新的记录,也就是说当以上代码换成 before触发器后,以下语句将不报错:
- INSERT INTO salary(employee_id, month, amount) VALUEs(2, '200601', 600000)
- 这是因为在主体中得到的v_sumsalary并不包括新插入的600000工资。
- 另外,在语句级触发器中不能使用:new和:old对象,这一点和行级触发器是显著不同的。如果需要检查插入或更新后的记录,可以采用临时表技术。
- 临时表是一种Oracle数据库对象,其特点是当创建数据的进程结束后,进程所创建的数据也随之清除。进程与进程不可以互相访问同一临时表中对方的数据,而且对临时表进行操作也不产生undo日志,减少了数据库的消耗。具体有关临时表的知识,可以参看有关书籍。
- 为了在语句级触发器中访问新插入后修改后的记录,可以增加行级触发器,将更新的记录插入临时表中,然后在语句级触发器中扫描临时表,获得修改后的记录。临时表的表结构一般与关联表的结构一致。
-
-
- 8,例五:语句级触发器之二
- 目的:限制每个员工的总工资不能超过50000,否则停止对该表操作。
- 创建临时表
- create global temporary table SALARY_TMP
- (
- EMPLOYEE_ID NUMBER,
- MONTH VARCHAR2(6),
- AMOUNT NUMBER
- )
- on commit delete rows;
-
- 为了把操作记录插入到临时表中,创建行级触发器:
- CREATE OR REPLACE TRIGGER salary_raiu
- AFTER INSERT OR UPDATE OF amount ON salary
- FOR EACH ROW
- BEGIN
- INSERT INTO salary_tmp(employee_id, month, amount)
- VALUES(:NEW.employee_id, :NEW.MONTH, :NEW.amount);
- END;
- 该触发器的作用是把更新后的记录信息插入到临时表中,如果更新了多条记录,则每条记录都会保存在临时表中。
-
- 创建语句级触发器:
- CREATE OR REPLACE TRIGGER salary_sai
- AFTER INSERT OR UPDATE OF amount ON salary
- DECLARE
- v_sumsalary NUMBER;
- BEGIN
- FOR cur IN (SELECT * FROM salary_tmp) LOOP
- SELECT SUM(amount)
- INTO v_sumsalary
- FROM salary
- WHERE employee_id = cur.employee_id;
- IF v_sumsalary > 50000 THEN
- raise_application_error(-20002, '员工累计工资超过50000');
- END IF;
- DELETE FROM salary_tmp;
- END LOOP;
- END;
-
- 该触发器首先用游标从salary_tmp临时表中逐条读取更新或插入的记录,取employee_id,在关联表salary中查找所有相同员工的工资记录,并求和。若某员工工资总和超过50000,则抛出异常。如果检查通过,则清空临时表,避免下次检查相同的记录。
- 执行以下语句:
- INSERT INTO salary(employee_id, month, amount) VALUEs(7, '200601', 20000);
- INSERT INTO salary(employee_id, month, amount) VALUEs(7, '200602', 20000);
- INSERT INTO salary(employee_id, month, amount) VALUEs(7, '200603', 20000);
- 在执行第三句时系统报错:
- ORA-20002:员工累计工资超过50000
- 查询salary表,发现前两条记录正常插入了,第三条记录没有插入。
-
-
- 如果系统结构比较复杂,而且触发器的代码比较多,在触发器主体中写过多的代码,对于维护来说是一个困难。这时可以将所有触发器的代码写到同一个包中,不同的触发器代码以不同的存储过程封装,然后触发器主体中调用这部分代码。
-
- 9,例六:用包封装触发器代码
- 目的:改写例五,封装触发器主体代码
- 创建代码包:
- CREATE OR REPLACE PACKAGE BODY salary_trigger_pck IS
-
- PROCEDURE load_salary_tmp(i_employee_id IN NUMBER,
- i_month IN VARCHAR2,
- i_amount IN NUMBER) IS
- BEGIN
- INSERT INTO salary_tmp VALUES (i_employee_id, i_month, i_amount);
- END load_salary_tmp;
-
- PROCEDURE check_salary IS
- v_sumsalary NUMBER;
- BEGIN
- FOR cur IN (SELECT * FROM salary_tmp) LOOP
- SELECT SUM(amount)
- INTO v_sumsalary
- FROM salary
- WHERE employee_id = cur.employee_id;
- IF v_sumsalary > 50000 THEN
- raise_application_error(-20002, '员工累计工资超过50000');
- END IF;
- DELETE FROM salary_tmp;
- END LOOP;
- END check_salary;
- END salary_trigger_pck;
- 包salary_trigger_pck中有两个存储过程,load_salary_tmp用于在行级触发器中调用,往salary_tmp临时表中装载更新或插入记录。而check_salary用于在语句级触发器中检查员工累计工资是否超限。
-
- 修改行级触发器和语句级触发器:
- CREATE OR REPLACE TRIGGER salary_raiu
- AFTER INSERT OR UPDATE OF amount ON salary
- FOR EACH ROW
- BEGIN
- salary_trigger_pck.load_salary_tmp(:NEW.employee_id, :NEW.MONTH, :NEW.amount);
- END;
-
- CREATE OR REPLACE TRIGGER salary_sai
- AFTER INSERT OR UPDATE OF amount ON salary
- BEGIN
- salary_trigger_pck.check_salary;
- END;
-
- 这样主要代码就集中到了salary_trigger_pck中,触发器主体中只实现了一个调用功能。
-
- 10,触发器命名规范
- 为了方便对触发器命名和根据触发器名称了解触发器含义,需要定义触发器的命名规范:
- Trigger_name = table_name_trg_<R|S><A|B|I><I|U|D>
-
- 触发器名限于30个字符。必须缩写表名,以便附加触发器属性信息。
- <R|S>基于行级(row)还是语句级(statement)的触发器
- <A|B|I>after, before或者是instead of触发器
- <I|U|D>触发事件是insert,update还是delete。如果有多个触发事件则连着写
-
- 例如:
- Salary_rai salary表的行级after触发器,触发事件是insert
- Employee_sbiud employee表的语句级before触发器,触发事件是insert,update和delete
经常在用apache和tomcat等这些服务器,可是总感觉还是不清楚他们之间有什么关系,在用tomcat的时候总出现apache,总感到迷惑,到底谁是主谁是次,因此特意在网上查询了一些这方面的资料,总结了一下:
一
apache支持静态页,tomcat支持动态的,比如servlet等,
一般使用apache+tomcat的话,apache只是作为一个转发,对jsp的处理是由tomcat来处理的。
apache可以支持php\cgi\perl,但是要使用java的话,你需要tomcat在apache后台支撑,将java请求由apache转发给tomcat处理。
apache是web服务器,Tomcat是应用(java)服务器,它只是一个servlet(jsp也翻译成servlet)容器,可以认为是apache的扩展,但是可以独立于apache运行。
这两个有以下几点可以比较的:
1、两者都是apache组织开发的
2、两者都有HTTP服务的功能
3、两者都是免费的
不同点:
Apache是专门用了提供HTTP服务的,以及相关配置的(例如虚拟主机、URL转发等等)
Tomcat是Apache组织在符合J2EE的JSP、Servlet标准下开发的一个JSP服务器
二:
APACHE是一个web服务器环境程序 启用他可以作为web服务器使用 不过只支持静态网页 如(asp,php,cgi,jsp)等动态网页的就不行
如果要在APACHE环境下运行jsp 的话就需要一个解释器来执行jsp网页 而这个jsp解释器就是TOMCAT, 为什么还要JDK呢?因为jsp需要连接数据库的话 就要jdk来提供连接数据库的驱程,所以要运行jsp的web服务器平台就需要APACHE+TOMCAT+JDK
整合的好处是:
如果客户端请求的是静态页面,则只需要Apache服务器响应请求
如果客户端请求动态页面,则是Tomcat服务器响应请求
因为jsp是服务器端解释代码的,这样整合就可以减少Tomcat的服务开销
三:
apache:侧重于http server
tomcat:侧重于servlet引擎,如果以standalone方式运行,功能上与apache等效 , 支持JSP,但对静态网页不太理想;
apache是web服务器,tomcat是应用(java)服务器,它只是一个servlet(jsp也翻译成servlet)容器,可以认为是apache的扩展,但是可以独立于apache运行。
换句话说,apache是一辆卡车,上面可以装一些东西如html等。但是不能装水,要装水必须要有容器(桶),而这个桶也可以不放在卡车上。
摘要:
proxool一个数据库连接池框架,提供了对你选择的其它类型的驱动程序的连接池封装。可以非常简单的移植到现存的代码中。完全可配置。快速,成熟,健
壮。可以透明地为你现存的JDBC驱动程序增加连接池功能。到目前为止最新版本是proxool 0.9.1,可从官网下载最新版本http://proxool.sourceforge.net
... 阅读全文
1.
在hibernate中写hql时候.
"from item i where i.supplier="+supplier 这样写法是不行的.
改为
"from item i where i.supplier.id="+supplier.getId(); 才可以.
2.boolean的处理
java中
1 /**是否可库存**/
2 private boolean invItemFlag = false;
hbm中
1 <property name="invItemFlag" type="boolean">
2 <column name="INV_ITEM_FLAG" />
3 </property>
然后插入数据库时,false的值对应db的0.true的值对应db的1.
在hibernate的hql查询时候
不可以写
from item i where i.invItemFlag='ture'
而应该写
from item i where i.invItemFlag=1
载自http://xiaoxinshome.javaeye.com/blog/139609
--------
语法规则:
Create [or replace] trigger [模式.]触发器名
Before| after insert|delete|(update of
列名)
On 表名
[for each row]
When 条件
PL/SQL块
说明:
For each row的意义是:在一次操作表的语句中,每操作成功一行就会触发一
次;不写的话,表示是表级触发器,则无论操作多少行,都只触发一次;
When条件的出现说明了,在DML操作的时候也许一定会触发触发器,但是触发器不一定会做实际的工作,比如when
后的条件不为真的时候,触发器只是简单地跳过了PL/SQL块;
例子:
sql 代码
- create or replace trigger wf_tri_user_list before insert or update or delete on user_list
- for each row
- declare
- uid varchar2(10); useq varchar2(10); asql varchar2(200); namea varchar2(200); nameb varchar2(200);
- begin
- namea:=NULL;
- nameb:=NULL;
- if inserting then
- insert into wflow.bpm_org_user(userid,username,diaplayname,seq) values(:NEW.user_id,:NEW.user_name,:NEW.user_realname,:NEW.user_id);
- dbms_output.put_line('insert trigger is chufale .....');
-
- end if;
- if updating then
- if (:NEW.user_name<>:OLD.user_name) and (:NEW.user_realname<>:OLD.user_realname) then
- namea:=:NEW.user_name;
- nameb:=:NEW.user_realname;
- asql:='update wflow.bpm_org_user set diaplayname=:1 where username=:2';
- execute immediate asql using namea,nameb;
- else
- if :NEW.user_name<>:OLD.user_name then
- namea:=:NEW.user_name;
- asql:='update wflow.bpm_org_user set user_name=:1 where username=:2';
- execute immediate asql using namea;
- else
- if :NEW.user_realname<>:OLD.user_realname then
- nameb:=:NEW.user_realname;
- asql:='update wflow.bpm_org_user set diaplayname=:1 where username=:2';
- execute immediate asql using nameb,:OLD.user_id;
- end if;
- end if;
- end if;
- end if;
- if deleting then
- update wflow.bpm_org_jobusers set userid = 0 where :OLD.user_id =userid and parentid=-1;
- delete from wflow.bpm_org_jobusers where userid = :OLD.user_id;
- delete wflow.bpm_org_user where userid=:OLD.user_id;
- end if;
- commit;
- end;
-
关键字:
:NEW 和:OLD使用方法和意义,new
只出现在insert和update时,old只出现在update和delete时。在insert时new表示新插入的行数据,update时new
表示要替换的新数据、old表示要被更改的原来的数据行,delete时old表示要被删除的数据。
注意:
在触发器中不能使用commit。
DECODE()函数,它将输入数值与函数中的参数列表相比较,根据输入值返回一个对应值。函数的参数列表是由若干数值及其对应结果值组成的若干序偶形
式。当然,如果未能与任何一个实参序偶匹配成功,则函数也有默认的返回值。
区别于SQL的其它函数,DECODE函数还能识别和操作空值。
语法:DECODE(control_value,value1,result1[,value2,result2…]
[,default_result]);
control _value试图处理的数值。DECODE函数将该数值与后面的一系列的偶序相比较,以决定返回值。
value1是一组成序偶的数值。如果输入数值与之匹配成功,则相应的结果将被返回。对应一个空的返回值,可以使用关键字NULL于之对应
result1 是一组成序偶的结果值。
default_result 未能与任何一个值匹配时,函数返回的默认值。
例如:
selectdecode( x , 1 , ‘x is 1 ’, 2 , ‘x is 2 ’, ‘others’) from
dual
当x等于1时,则返回‘x is 1’。
当x等于2时,则返回‘x is 2’。
否则,返回others’。
需要,比较2个值的时候,可以配合SIGN()函数一起使用。
SELECT DECODE( SIGN(5 -6), 1 'Is Positive', -1, 'Is Nagative', 'Is
Zero')
同样,也可以用CASE实现:
SELECT CASE SIGN(5 - 6)
WHEN 1 THEN 'Is Positive'
WHEN -1 THEN 'Is Nagative'
ELSE 'Is Zero' END
FROM DUAL
此外,还可以在Order by中使用Decode。
例如:表table_subject,有subject_name列。要求按照:语、数、外的顺序进行排序。这时,就可以非常轻松的使用
Decode完成要求了。
select * from table_subject order by decode(subject_name, '语文', 1,
'数学', 2, , '外语',3)
Unique约束
Unique约束可应用于一列或多列字段上。如果字段值存在,必须为唯一的,可以取null值
1、一张表只能有一个PK约束但可以有多个Unique约束
2、作为PK的字段不能为null,但作为Unique的字段可以为null,但不为null的行必须是Unique的
3、当创建一个PK时,创建一个Index,创建一个Unique时也创建一个Index
4、PK和Unique约束字段可以作为FK的父亲。FK约束字段引用PK约束,也引用Unique约束
创建语句:
CREATE TABLE temp (pk NUMBER PRIMARY KEY, a NUMBER, b NUMBER);
ALTER TABLE temp ADD CONSTRAINT uk_temp_a_b UNIQUE (a, b);
Example:
CREATE TABLE students
(student_id VARCHAR2(10) NOT NULL,
student_name VARCHAR2(30) NOT NULL,
college_major VARCHAR2(15) NOT NULL,
status VARCHAR2(15) NOT NULL,
state VARCHAR2(2),
license_no VARCHAR2(30)) TABLESPACE student_data;
ALTER TABLE students
ADD CONSTRAINT pk_students PRIMARY KEY (student_id)
USING INDEX TABLESPACE student_index;
ALTER TABLE students
ADD CONSTRAINT uk_students_license
UNIQUE (state, license_no)
USING INDEX TABLESPACE student_index;
ALTER TABLE students
ADD CONSTRAINT ck_students_st_lic
CHECK ((state IS NULL AND license_no IS NULL) OR
(state IS NOT NULL AND license_no is NOT NULL));
作为开源的连接池Proxool
有以下优点。
透明性 可以明的添加接连池而不影响你原来的项目的JDBC代码;
开放性 你可以方便的与其它的开源产品进行整合。如hibernate 中自带的这个Proxool
标准性 它是在J2SE下开出来的。你可以放心的开发
易用性 非常容易 的进行配置。
proxool是一个非常强大的连接池工具包,我觉得相比dbcp、c3p0这两个连接池包都要好用,我用loadrunner测试过,这三个连
接池的从性能上排名如下:proxool>c3p0>dbcp,特别是dbcp在大并发的情况下总是出现各种异常。
下面是实现proxool的几种方式:
JDBC连接方法:
首先建一个proxool的配置文件proxool.xml
proxool.xml 代码
xml 代码
<!--sp-->xml version="1.0" encoding="UTF-8"?>
<!-- the proxool configuration can be embedded within your own
application's. Anything outside the "proxool" tag is ignored.
-->
<something-else-entirely>
<proxool>
<!--连接池的别名-->
<alias>DBPool</alias>
<!--proxool只能管理由自己产生的连接-->
<driver-url>jdbc:oracle:thin:@192.168.0.40:1521:drcom</driver-url>
<!--JDBC驱动程序-->
<driver-class>oracle.jdbc.driver.OracleDriver</driver-class>
<driver-properties>
<property name="user" value="drcom"/>
<property name="password" value="drcom"/>
</driver-properties>
<!-- proxool自动侦察各个连接状态的时间间隔(毫秒),侦察到空闲的连接就马上回收,超时的销毁-->
<house-keeping-sleep-time>90000</house-keeping-sleep-time>
<!-- 指因未有空闲连接可以分配而在队列中等候的最大请求数,超过这个请求数的用户连接就不会被接受-->
<maximum-new-connections>150</maximum-new-connections>
<!-- 最少保持的空闲连接数-->
<prototype-count>3</prototype-count>
<!-- 允许最大连接数,超过了这个连接,再有请求时,就排在队列中等候,最大的等待请求数由maximum-new-connections决定-->
<maximum-connection-count>100</maximum-connection-count>
<!-- 最小连接数-->
<minimum-connection-count>3</minimum-connection-count>
</proxool>
</something-else-entirely>
再在web.xml中进行配置,其中的ServletConfigurator是装载WEB-INF目录下的proxool.xml,并设置为Tomcat启动时就加载。Admin这个Servlet是proxool提供的察看连接池的信息的工具,
web.xml 代码
xml 代码
<servlet>
<servlet-name>proxoolServletConfigurator</servlet-name>
<servlet-class>org.logicalcobwebs.proxool.configuration.ServletConfigurator</servlet-class>
<init-param>
<param-name>xmlFile</param-name>
<param-value>WEB-INF/config/proxool.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<!-- proxool提供的管理监控工具,可查看当前数据库连接情况。如果运行不成功,请删除本行 -->
<servlet>
<servlet-name>Admin</servlet-name>
<servlet-class>org.logicalcobwebs.proxool.admin.servlet.AdminServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Admin</servlet-name>
<url-pattern>/admin</url-pattern>
</servlet-mapping>
以上配置完成后,第三步就可以创建一个连接池的类了
java 代码
package selfservice;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.logicalcobwebs.proxool.ProxoolException;
import org.logicalcobwebs.proxool.ProxoolFacade;
import org.logicalcobwebs.proxool.admin.SnapshotIF;
public class PoolManager {
private static int activeCount = 0;
public PoolManager(){
}
/**
* 获取连接
* getConnection
* @param name
* @return
*/
public Connection getConnection() {
try{
Class.forName("org.logicalcobwebs.proxool.ProxoolDriver");//proxool驱动类
Connection conn = DriverManager.getConnection("proxool.DBPool");
//此处的DBPool是在proxool.xml中配置的连接池别名
showSnapshotInfo();
return conn;
}catch(Exception ex){
ex.printStackTrace();
}
return null;
}
/**
* 此方法可以得到连接池的信息
* showSnapshotInfo
*/
private void showSnapshotInfo(){
try{
SnapshotIF snapshot = ProxoolFacade.getSnapshot("DBPool", true);
int curActiveCount=snapshot.getActiveConnectionCount();//获得活动连接数
int availableCount=snapshot.getAvailableConnectionCount();//获得可得到的连接数
int maxCount=snapshot.getMaximumConnectionCount() ;//获得总连接数
if(curActiveCount!=activeCount)//当活动连接数变化时输出的信息
{
System.out.println("活动连接数:"+curActiveCount+"(active)
可得到的连接数:"+availableCount+"(available)
总连接数:"+maxCount+"(max)");
activeCount=curActiveCount;
}
}catch(ProxoolException e){
e.printStackTrace();
}
}
/**
* 获取连接
* getConnection
* @param name
* @return
*/
public Connection getConnection(String name){
return getConnection();
}
/**
* 释放连接
* freeConnection
* @param conn
*/
public void freeConnection(Connection conn){
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 释放连接
* freeConnection
* @param name
* @param con
*/
public void freeConnection (String name,Connection con){
freeConnection(con);
}
public void getQuery() {
try {
Connection conn = getConnection();
if(conn != null){
Statement statement = conn.createStatement();
ResultSet rs = statement.executeQuery("select * from tblgxinterface");
int c = rs.getMetaData().getColumnCount();
while(rs.next()){
System.out.println();
for(int i=1;i<=c;i++){
System.out.print(rs.getObject(i));
}
}
rs.close();
}
freeConnection(conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
就这样我们完成了一个连接池的功能。proxool的连接池我用loadrunner进行大并发的测试,性能还是很好的。
Hibernate中proxool连接池的方式:
首先步骤跟JDBC的连接池一样,也是新建一个proxool.xml配置文件,再在web.xml中配置,具体参考上面。
第二步在hibernate的配置文件hibernate.cfg.xml中配置proxool连接设置:
hibernate.cfg.xml代码
xml 代码
<?xmlversion='1.0'encoding='UTF-8'?>
<!DOCTYPEhibernate-configurationPUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.provider_class">org.hibernate.connection.ProxoolConnectionProvider</property>
<property name="hibernate.proxool.pool_alias">DBPool</property>
<property name="hibernate.proxool.xml">Proxool.xml</property>
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<mappin gresource="hibernate.cfg.xml"/>
这里放Hibernate的映射文件
</session-factory>
Spring中proxool连接池的方式:
首先布骤与JDBC的连接池一样,先建一个proxool.xml配置文件,再在web.xml中配置,具体参考上面的。
第二步在spring配置文件applicationContext.xml中配置proxool连接设置
applicationContext.xml代码
xml 代码
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" singleton="true">
<property name="driverClassName" value="org.logicalcobwebs.proxool.ProxoolDriver"/>
<property name="url" value="proxool.StatDBPool"/>
</bean>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource">
<ref local="dataSource" />
</property>
</bean>
这样spring就能得到一个dataSource的数据源。
proxool还有很多功能,我这只是简单的应用。具体请察看proxool用户指南。
概述
J2SE(TM) 5.0引入了很多激进的语言元素变化,这些变化或多或少减轻了我们开发人员的一些编码负担,其中的大部分也必然会被应用到即将发布的 J2EE(TM) 5.0中。主要的新特性包括:
· 泛型
· 增强的for循环
· 自动装箱和自动拆箱
· 类型安全的枚举
· 可变长度参数
· 静态引入
· 元数据(注解)
· C风格的格式化输出
这当中,泛型、枚举和注解可能会占用较大的篇幅,而其余的因为用法直截了当,抑或相对简单,我就稍作介绍,剩下的留给读者去思考、去探索了。
1.4. 泛型
泛型这个题目相当大,大到完全可以就这个话题写一本书。有关Java是否需要泛型和如何实现泛型的讨论也早就在Java社群广为流传。终于,我们在J2SE(TM) 5.0中看到了它。也许目前Java对泛型的支持还算不上足够理想,但这一特性的添加也经足以让我们欣喜一阵了。
在接下来的介绍中,我们会了解到:Java的泛型虽然跟C++的泛型看上去十分相似,但其实有着相当大的区别,有些细节的东西也相当复杂(至少很多地方会跟我们的直觉背道而驰)。可以这样说,泛型的引入在很大程度上增加了Java语言的复杂度,对初学者尤其是个挑战。下面我们将一点一点往里挖。
首先我们来看一个简单的使用泛型类的例子:
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(new Integer(1));
// ...
Integer myInteger = aList.get(0);
我们可以看到,在这个简单的例子中,我们在定义aList的时候指明了它是一个直接受Integer类型的ArrayList,当我们调用aList.get(0)时,我们已经不再需要先显式的将结果转换成Integer,然后再赋值给myInteger了。而这一步在早先的Java版本中是必须的。也许你在想,在使用Collection时节约一些类型转换就是Java泛型的全部吗?远不止。单就这个例子而言,泛型至少还有一个更大的好处,那就是使用了泛型的容器类变得更加健壮:早先,Collection接口的get()和Iterator接口的next()方法都只能返回Object类型的结果,我们可以把这个结果强制转换成任何Object的子类,而不会有任何编译期的错误,但这显然很可能带来严重的运行期错误,因为在代码中确定从某个Collection中取出的是什么类型的对象完全是调用者自己说了算,而调用者也许并不清楚放进Collection的对象具体是什么类的;就算知道放进去的对象“应该”是什么类,也不能保证放到Collection的对象就一定是那个类的实例。现在有了泛型,只要我们定义的时候指明该Collection接受哪种类型的对象,编译器可以帮我们避免类似的问题溜到产品中。我们在实际工作中其实已经看到了太多的ClassCastException,不是吗?
泛型的使用从这个例子看也是相当易懂。我们在定义ArrayList时,通过类名后面的<>括号中的值指定这个ArrayList接受的对象类型。在编译的时候,这个ArrayList会被处理成只接受该类或其子类的对象,于是任何试图将其他类型的对象添加进来的语句都会被编译器拒绝。
那么泛型是怎样定义的呢?看看下面这一段示例代码:(其中用E代替在实际中将会使用的类名,当然你也可以使用别的名称,习惯上在这里使用大写的E,表示Collection的元素。)
public class TestGenerics<E> {
Collection<E> col;
public void doSth(E elem) {
col.add(elem);
// ...
}
}
在泛型的使用中,有一个很容易有的误解,那就是既然Integer是从Object派生出来的,那么ArrayList<Integer>当然就是ArrayList<Object>的子类。真的是这样吗?我们仔细想一想就会发现这样做可能会带来的问题:如果我们可以把ArrayList<Integer>向上转型为ArrayList<Object>,那么在往这个转了型以后的ArrayList中添加对象的时候,我们岂不是可以添加任何类型的对象(因为Object是所有对象的公共父类)?这显然让我们的ArrayList<Integer>失去了原本的目的。于是Java编译器禁止我们这样做。那既然是这样,ArrayList<Integer>以及ArrayList<String>、ArrayList<Double>等等有没有公共的父类呢?有,那就是ArrayList<?>。?在这里叫做通配符。我们为了缩小通配符所指代的范围,通常也需要这样写:ArrayList<? extends SomeClass>,这样写的含义是定义这样一个类ArrayList,比方说SomeClass有SomeExtendedClass1和SomeExtendedClass2这两个子类,那么ArrayList<? extends SomeClass>就是如下几个类的父类:ArrayList<SomeClass>、ArrayList<SomeExtendedClass1>和ArrayList<SomeExtendedClass2>。
接下来我们更进一步:既然ArrayList<? extends SomeClass>是一个通配的公用父类,那么我们可不可以往声明为ArrayList<? extends SomeClass>的ArrayList实例中添加一个SomeExtendedClass1的对象呢?答案是不能。甚至你不能添加任何对象。为什么?因为ArrayList<? extends SomeClass>实际上代表了所有ArrayList<SomeClass>、ArrayList<SomeExtendedClass1>和ArrayList<SomeExtendedClass2>三种ArrayList,甚至包括未知的接受SomeClass其他子类对象的ArrayList。我们拿到一个定义为ArrayList<? extends SomeClass>的ArrayList的时候,我们并不能确定这个ArrayList具体是使用哪个类作为参数定义的,因此编译器也无法让这段代码编译通过。举例来讲,如果我们想往这个ArrayList中放一个SomeExtendedClass2的对象,我们如何保证它实际上不是其他的如ArrayList<SomeExtendedClass1>,而就是这个ArrayList<SomeExtendedClass2>呢?(还记得吗?ArrayList<Integer>并非ArrayList<Object>的子类。)怎么办?我们需要使用泛型方法。泛型方法的定义类似下面的例子:
public static <T extends SomeClass> void add (Collection<T> c, T elem) {
c.add(elem);
}
其中T代表了我们这个方法期待的那个最终的具体的类,相关的声明必须放在方法签名中紧靠返回类型的位置之前。在本例中,它可以是SomeClass或者SomeClass的任何子类,其说明放在void关键字之前(只能放在这里)。这样我们就可以让编译器确信当我们试图添加一个元素到泛型的ArrayList实例中时,可以保证类型安全。
Java泛型的最大特点在于它是在语言级别实现的,区别于C# 2.0中的CLR级别。这样的做法使得JRE可以不必做大的调整,缺点是无法支持一些运行时的类型甄别。一旦编译,它就被写死了,能提供的动态能力相当弱。
个人认为泛型是这次J2SE(TM) 5.0中引入的最重要的语言元素,给Java语言带来的影响也是最大。举个例子来讲,我们可以看到,几乎所有的Collections API都被更新成支持泛型的版本。这样做带来的好处是显而易见的,那就是减少代码重复(不需要提供多个版本的某一个类或者接口以支持不同类的对象)以及增强代码的健壮性(编译期的类型安全检查)。不过如何才能真正利用好这个特性,尤其是如何实现自己的泛型接口或类供他人使用,就并非那么显而易见了。让我们一起在使用中慢慢积累。
1.5. 增强的for循环
你是否已经厌倦了每次写for循环时都要写上那些机械的代码,尤其当你需要遍历数组或者Collection,如:(假设在Collection中储存的对象是String类型的)
public void showAll (Collection c) {
for (Iterator iter = c.iterator(); iter.hasNext(); ) {
System.out.println((String) iter.next());
}
}
public void showAll (String[] sa) {
for (int i = 0; i < sa.length; i++) {
System.out.println(sa[i]);
}
}
这样的代码不仅显得臃肿,而且容易出错,我想我们大家在刚开始接触编程时,尤其是C/C++和Java,可能多少都犯过以下类似错误的一种或几种:把for语句的三个表达式顺序弄错;第二个表达式逻辑判断不正确(漏掉一些、多出一些、甚至死循环);忘记移动游标;在循环体内不小心改变了游标的位置等等。为什么不能让编译器帮我们处理这些细节呢?在5.0中,我们可以这样写:
public void showAll (Collection c) {
for (Object obj : c) {
System.out.println((String) obj);
}
}
public void showAll (String[] sa) {
for (String str : sa) {
System.out.println(str);
}
}
这样的代码显得更加清晰和简洁,不是吗?具体的语法很简单:使用":"分隔开,前面的部分写明从数组或Collection中将要取出的类型,以及使用的临时变量的名字,后面的部分写上数组或者Collection的引用。加上泛型,我们甚至可以把第一个方法变得更加漂亮:
public void showAll (Collection<String> cs) {
for (String str : cs) {
System.out.println(str);
}
}
有没有发现:当你需要将Collection替换成String[],你所需要做的仅仅是简单的把参数类型"Collection"替换成"String[]",反过来也是一样,你不完全需要改其他的东西。这在J2SE(TM) 5.0之前是无法想象的。
对于这个看上去相当方便的新语言元素,当你需要在循环体中访问游标的时候,会显得很别扭:比方说,当我们处理一个链表,需要更新其中某一个元素,或者删除某个元素等等。这个时候,你无法在循环体内获得你需要的游标信息,于是需要回退到原先的做法。不过,有了泛型和增强的for循环,我们在大多数情况下已经不用去操心那些烦人的for循环的表达式和嵌套了。毕竟,我们大部分时间都不会需要去了解游标的具体位置,我们只需要遍历数组或Collection,对吧?
1.6. 自动装箱/自动拆箱
所谓装箱,就是把值类型用它们相对应的引用类型包起来,使它们可以具有对象的特质,如我们可以把int型包装成Integer类的对象,或者把double包装成Double,等等。所谓拆箱,就是跟装箱的方向相反,将Integer及Double这样的引用类型的对象重新简化为值类型的数据。
在J2SE(TM) 5.0发布之前,我们只能手工的处理装箱和拆箱。也许你会问,为什么需要装箱和拆箱?比方说当我们试图将一个值类型的数据添加到一个Collection中时,就需要先把它装箱,因为Collection的add()方法只接受对象;而当我们需要在稍后将这条数据取出来,而又希望使用它对应的值类型进行操作时,我们又需要将它拆箱成值类型的版本。现在,编译器可以帮我们自动地完成这些必要的步骤。下面的代码我提供两个版本的装箱和拆箱,一个版本使用手工的方式,另一个版本则把这些显而易见的代码交给编译器去完成:
public static void manualBoxingUnboxing(int i) {
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(0, new Integer(i));
int a = aList.get(0).intValue();
System.out.println("The value of i is " + a);
}
public static void autoBoxingUnboxing(int i) {
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(0, i);
int a = aList.get(0);
System.out.println("The value of i is " + a);
}
看到了吧,在J2SE(TM) 5.0中,我们不再需要显式的去将一个值类型的数据转换成相应的对象,从而把它作为对象传给其他方法,也不必手工的将那个代表一个数值的对象拆箱为相应的值类型数据,只要你提供的信息足够让编译器确信这些装箱/拆箱后的类型在使用时是合法的:比方讲,如果在上面的代码中,如果我们使用的不是ArrayList而是ArrayList或者其他不兼容的版本如ArrayList,会有编译错误。
当然,你需要足够重视的是:一方面,对于值类型和引用类型,在资源的占用上有相当大的区别;另一方面,装箱和拆箱会带来额外的开销。在使用这一方便特性的同时,请不要忘记了背后隐藏的这些也许会影响性能的因素。
1.7. 类型安全的枚举
在介绍J2SE(TM) 5.0中引入的类型安全枚举的用法之前,我想先简单介绍一下这一话题的背景。
我们知道,在C中,我们可以定义枚举类型来使用别名代替一个集合中的不同元素,通常是用于描述那些可以归为一类,而又具备有限数量的类别或者概念,如月份、颜色、扑克牌、太阳系的行星、五大洲、四大洋、季节、学科、四则运算符,等等。它们通常看上去是这个样子:
typedef enum {SPRING, SUMMER, AUTUMN, WINTER} season;
实质上,这些别名被处理成int常量,比如0代表SPRING,1代表SUMMER,以此类推。因为这些别名最终就是int,于是你可以对它们进行四则运算,这就造成了语意上的不明确。
Java一开始并没有考虑引入枚举的概念,也许是出于保持Java语言简洁的考虑,但是使用Java的广大开发者对于枚举的需求并没有因为Java本身没有提供而消失,于是出现了一些常见的适用于Java的枚举设计模式,如int enum和typesafe enum,还有不少开源的枚举API和不开源的内部实现。
我大致说一下int enum模式和typesafe enum模式。所谓int enum模式就是模仿C中对enum的实现,如:
public class Season {
public static final int SPRING = 0;
public static final int SUMMER = 1;
public static final int AUTUMN = 2;
public static final int WINTER = 3;
}
这种模式跟C中的枚举没有太多本质上的区别,C枚举的局限它基本上也有。而typesafe enum模式则要显得健壮得多:
public class Season {
private final String name;
private Season(String name) {
this.name = name;
}
public String toString() {
return name;
}
public static final Season SPRING = new Season("spring");
public static final Season SUMMER = new Season("summer");
public static final Season AUTUMN = new Season("autumn");
public static final Season WINTER = new Season("winter");
}
后一种实现首先通过私有的构造方法阻止了对该类的继承和显式实例化,因而我们只可能取得定义好的四种Season类别,并且提供了方便的toString()方法获取有意义的说明,而且由于这是一个完全意义上的类,所以我们可以很方便的加入自己的方法和逻辑来自定义我们的枚举类。
最终,Java决定拥抱枚举,在J2SE(TM) 5.0中,我们看到了这一变化,它所采用的设计思路基本上就是上面提到的typesafe enum模式。它的语法很简单,用一个实际的例子来说,要定义一个枚举,我们可以这样写:
public enum Language {CHINESE, ENGLISH, FRENCH, HUNGARIAN}
接下来我们就可以通过Language.ENGLISH来使用了。呃…这个例子是不是有点太小儿科了,我们来看一个复杂点的例子。使用Java的类型安全枚举,我们可以为所有枚举元素定义公用的接口,然后具体到每个元素本身,可以针对这些接口实现一些特定的行为。这对于那些可以归为一类,又希望能通过统一的接口访问的不同操作,将会相当方便。通常,为了实现类似的功能,我们需要自己来维护一套继承关系或者类似的枚举模式。这里借用Java官方网站上的一个例子:
public enum Operation {
PLUS { double eval(double x, double y) { return x + y; } },
MINUS { double eval(double x, double y) { return x - y; } },
TIMES { double eval(double x, double y) { return x * y; } },
DIVIDE { double eval(double x, double y) { return x / y; } };
// Do arithmetic op represented by this constant
abstract double eval(double x, double y);
}
在这个枚举中,我们定义了四个元素,分别对应加减乘除四则运算,对于每一种运算,我们都可以调用eval()方法,而具体的方法实现各异。我们可以通过下面的代码来试验上面这个枚举类:
public static void main(String args[]) {
double x = Double.parseDouble(args[0]);
double y = Double.parseDouble(args[1]);
for (Operation op : Operation.values()) {
System.out.println(x + " " + op + " " + y + " = " + op.eval(x, y));
}
}
怎么样,使用枚举,我们是不是能够很方便的实现一些有趣的功能?其实说穿了,Java的类型安全枚举就是包含了有限数量的已生成好的自身实例的一种类,这些现成的实例可以通过类的静态字段来获取。
1.8. 可变长度参数
顾名思义,可变长度参数就是指在方法的参数体中,只要定义恰当,我们可以使用任意数量的参数,类似于使用数组。在J2SE(TM) 5.0中,一个新的语法被引入,就是在参数类型名称后面加上"...",表示该方法可以接受多个该类型的参数。需要说明的是可变长度参数必须放在参数列表的最后,且一个方法只能包含一个这样的参数。在方法体内部,这样的参数被当作数组处理,看上去代码应该类似这个样子:
public String testVararg(String... args) {
StringBuilder sb = new StringBuilder();
for (String str : args) {
sb.append(str);
}
return sb.toString();
}
这样的方法签名跟你写成testVararg(String[] args)的区别在于:在调用时,你不再需要传入一个包装好的String数组,你只需要简单的写一连串String参数,以逗号隔开即可,就如同这个方法正好有一个重载的版本是接受那么多个String参数一样。
1.9. 静态引入
所谓静态引入就是指除了引入类之外,我们现在又多了一种选择:引入某个类的静态字段。如:
import static java.lang.Math.PI;
或者
import static java.lang.Math.*;
这样我们在接下来的代码中,当我们需要使用某个被引入的静态字段时,就不用再写上前面的类名了。当然,出现名字冲突时,跟原来的类引入一样,还是需要前缀以示区分。我个人认为这个新语言元素意义不大。当引入太多静态字段后,代码会变得难以阅读和维护。由于静态字段的名字通常不如类名那么具有描述性,我认为原先在静态字段前写上类名才是更好的选择。不过,毕竟每个人的喜好和需求不同,如果你觉得它对你有用,既然提供了,那么就用咯。
1.10. 元数据(注解)
注解是J2SE(TM) 5.0引入的重要语言元素,它所对应的JSR是JSR 175,我们先来看看JSR 175的文档对注解的说明:
注解不会直接影响程序的语义,而开发和部署工具则可以读取这些注解信息,并作相应处理,如生成额外的Java源代码、XML文档、或者其他将与包含注解的程序一起使用的物件。
在之前的J2SE版本中,我们已经使用到了一部分早期的注解元素,如@deprecated等。这些元素通常被用于产生HTML的Javadoc。在J2SE(TM) 5.0中,注解被正式引入,且推到了Java历史上前所未有的高度。
现在,注解不仅仅被用来产生Javadoc,更重要的,注解使得代码的编译期检查更加有效和方便,同时也增强了代码的描述能力。有一些注解是随着J2SE(TM) 5.0一起发布的,我们可以直接使用。除此之外,我们也可以很方便的实现自定义的注解。在此基础上,很多以前我们只能靠反射机制来完成的功能也变得更加容易实现。
我们来看现成的有哪些有用的注解:
首先是@Override,这个注解被使用在方法上,表明这个方法是从其父类继承下来的,这样的写法可以很方便的避免我们在重写继承下来的方法时,不至于不小心写错了方法签名,且悄悄的溜过了编译器,造成隐蔽性相当高的bug。
其次是@Deprecated,表明该项(类、字段、方法)不再被推荐使用。
还有一个@SuppressWarnings,表明该项(类、字段、方法)所涵盖的范围不需要显示所有的警告信息。这个注解需要提供参数,如unchecked等等。
下面我通过一个例子向大家说明这些现成的注解的用法:
public class Main {
@Deprecated
public String str;
public static void main(String[] args) {
new SubMain().doSomething();
}
public void doSomething() {
System.out.println("Done.");
}
}
class SubMain extends Main {
@Override
@SuppressWarnings("unchecked", "warning")
public void doSomething() {
java.util.ArrayList aList = new java.util.ArrayList();
aList.add(new Integer(0));
System.out.println("Done by SubMain.");
}
}
当然,我们也完全可以写自己的注解。注解定义的语法是@interface关键字。J2SE(TM) 5.0支持三种形式的注解:不带参数的标记注解、带一个参数的注解和带多个参数的完整注解。下面分别举例说明:
标记注解,类似@Deprecated,如:
@interface SomeEmptyAnnotation {}
单个参数的注解,如:
@interface MySingleElementAnnotation {
String value();
}
以及多个参数的注解,如:
@interface MyAnnotationForMethods {
int index();
String info();
String developer() default "Sean GAO";
}
我们可以看到,注解的定义跟interface的定义相当类似,我们还可以指定默认值。对于这些注解,我们也可以为其添加注解,所谓“注解的注解”。比方讲,我们通常会使用@Target指定注解的作用对象,以及用@Retention指定注解信息写入的级别,如源代码、类文件等等。举个例子:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface SignedMethod {
}
在使用时,我们需要在注解名称前面写上@,然后()中指定参数值,如:
@MyAnnotationForMethods (
index = 1,
info = "This is a method to test MyAnnotation.",
developer = "Somebody else"
)
public void testMethod1() {
// ...
}
注解的最大作用在于它在源代码的基础上增加了有用的信息,使得源代码的描述性更强。这些信息可以被代码之外的工具识别,从而可以很方便的增加外部功能,以及减少不必要的相关代码/文件的维护。这里我想简单提一个超出J2SE(TM) 5.0范畴的话题:在未来的EJB 3.0规范中会有相当多的对注解的应用,让我们预览一下将来的无状态会话bean用注解来定义会是什么样子:
@Stateless public class BookShelfManagerBean {
public void addBook(Book aBook) {
// business logic goes here...
}
public Collection getAllBooks() {
// business logic goes here...
}
// ...
}
我们甚至不用写任何接口和部署描述符,这些工作将完全由外部工具通过读取注解加上反射来完成,这不是很好吗?
1.11. C风格格式化输出
Java总算也有类似C的printf()风格的方法了,方法名同样叫作printf(),这一特性依赖于前边提到的可变长度参数。举个例子来说,我们现在可以写:
System.out.printf("%s has a value of %d.%n", someString, a);
怎么样,看上去还不错吧?需要注意的是Java为了支持多平台,新增了%n标示符,作为对\n的补充。有关Java格式化输出的具体语法,请参考java.util.Formatter的API文档。
1.12. 结语
在这一篇介绍性的文章中,我们一起领略了J2SE 5.0带来的新的语言元素,不知道大家是否也跟笔者一样,感受到了这些新特性在提高我们的开发效率上所作的巨大努力。其实不只是语言元素,J2SE(TM) 5.0的发布在其他很多方面都作了不小的改进,包括虚拟机、新的API类库等等,性能和功能上都有大幅提升。
对于主要靠J2EE吃饭的朋友来讲,也许真正意义上要在工作中充分利用这些新的元素,恐怕要等主流的J2EE服务器都支持J2EE(TM) 5.0的那一天了,对此我充满期待。
java截取字符串,截串,substring和split,分割字母和数字,正则缝隙
关键字: java截取字符串 截串 substring
需求,把"01:大汽车",分成01和大汽车
有两种做法:一是substring
- package test;
-
- public class substringTest
- {
- public static void main(String args[])
- {
- String N = "01:大汽车";
- String L="";
- String R="";
- int k= N.length();
- for (int i = 0; i < N.length(); i++)
- {
- if (N.substring(i, i + 1).equals("|"))
- {
- L=N.substring(0,i).trim();
- R=N.substring(i+1,k).trim();
- }
- else
- {
-
- }
- System.out.println(L);
- System.out.println(R);
- }
- }
- }
package test;
public class substringTest
{
public static void main(String args[])
{
String N = "01:大汽车";
String L="";
String R="";
int k= N.length();
for (int i = 0; i < N.length(); i++)
{
if (N.substring(i, i + 1).equals("|"))
{
L=N.substring(0,i).trim();
R=N.substring(i+1,k).trim();
}
else
{
}
System.out.println(L);
System.out.println(R);
}
}
}
另外一种方法是CSDN上一位叫老六的人给我写的
package Test
- public class splitTest
- {
- public static void main(String[] args)
- {
- String s = new String("01:大汽车");
- String a[] = s.split(":");
-
- System.out.println(a[0]);
- System.out.println(a[1]);
- }
- }
public class splitTest
{
public static void main(String[] args)
{
String s = new String("01:大汽车");
String a[] = s.split(":");
System.out.println(a[0]);
System.out.println(a[1]);
}
}
split分割字母和数字,简单正则缝隙
- public class Test01 {
- public static void main(String[] args) {
- String str = "one123";
- String regex = "(?<=one)(?=123)";
- String[] strs = str.split(regex);
- for(int i = 0; i < strs.length; i++) {
- System.out.printf("strs[%d] = %s%n", i, strs[i]);
- }
- }
- }
public class Test01 {
public static void main(String[] args) {
String str = "one123";
String regex = "(?<=one)(?=123)";
String[] strs = str.split(regex);
for(int i = 0; i < strs.length; i++) {
System.out.printf("strs[%d] = %s%n", i, strs[i]);
}
}
}
substring讲解:
s=s.substring(int begin);截取掉s从首字母起长度为begin的字符串,将剩余字符串赋值给s;
s=s.substring(int begin,int end);截取s中从begin开始至end结束时的字符串,并将其赋值给s;
split讲解:
java.lang.string.split
split 方法
将一个字符串分割为子字符串,然后将结果作为字符串数组返回。
stringObj.split([separator,[limit]])
参数
stringObj
必选项。要被分解的 String 对象或文字。该对象不会被 split 方法修改。
separator
可选项。字符串或 正则表达式 对象,它标识了分隔字符串时使用的是一个还是多个字符。如果忽
略该选项,返回包含整个字符串的单一元素数组。
limit
可选项。该值用来限制返回数组中的元素个数。
说明
split 方法的结果是一个字符串数组,在 stingObj 中每个出现 separator 的位置都要进行分解
。separator 不作为任何数组元素的部分返回。
split 的实现直接调用的 matcher 类的 split 的方法。“ . ”在正则表达式中有特殊的含义,因此我们使用的时候必须进行转义。
- public static void main(string[] args) {
- string value = "192.168.128.33";
- string[] names = value.split("\\.");
- for (int i = 0; i < names.length; i++) {
- system.out.println(names[i]);
- }}
public static void main(string[] args) {
string value = "192.168.128.33";
string[] names = value.split("\\.");
for (int i = 0; i < names.length; i++) {
system.out.println(names[i]);
}}
如果用竖线“|”分隔的话,将出现不可得到的结果,必须改为“\\|”
摘自:http://www.blogjava.net/ghyghost/archive/2008/06/16/208309.html
标题 在Java中实现浮点数的精确计算 AYellow(原作) 修改
关键字 Java 浮点数 精确计算
问题的提出:
编译运行下面这个程序会看到什么?
public class Test{
public static void main(String args[]){
System.out.println(0.05+0.01);
System.out.println(1.0-0.42);
System.out.println(4.015*100);
System.out.println(123.3/100);
}
};
你没有看错!结果确实是
0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999
Java中的简单浮点数类型float和double不能够进行运算。不光是Java,在其它很多编程语言中也有这样的问题。在大多数情况下,计算的结果是准确的,但是多试几次(可以做一个循环)就可以试出类似上面的错误。现在终于理解为什么要有BCD码了。
这个问题相当严重,如果你有9.999999999999元,你的计算机是不会认为你可以购买10元的商品的。
在有的编程语言中提供了专门的货币类型来处理这种情况,但是Java没有。现在让我们看看如何解决这个问题。
四舍五入
我们的第一个反应是做四舍五入。Math类中的round方法不能设置保留几位小数,我们只能象这样(保留两位):
public double round(double value){
return Math.round(value*100)/100.0;
}
非常不幸,上面的代码并不能正常工作,给这个方法传入4.015它将返回4.01而不是4.02,如我们在上面看到的
4.015*100=401.49999999999994
因此如果我们要做到精确的四舍五入,不能利用简单类型做任何运算
java.text.DecimalFormat也不能解决这个问题:
System.out.println(new java.text.DecimalFormat("0.00").format(4.025));
输出是4.02
BigDecimal
在《Effective
Java》这本书中也提到这个原则,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用
java.math.BigDecimal。BigDecimal一共有4个够造方法,我们不关心用BigInteger来够造的那两个,那么还有两个,
它们是:
BigDecimal(double val)
Translates a double into a BigDecimal.
BigDecimal(String val)
Translates the String repre sentation of a BigDecimal into a BigDecimal.
上面的API简要描述相当的明确,而且通常情况下,上面的那一个使用起来要方便一些。我们可能想都不想就用上了,会有什么问题呢?等到出了问题的时候,才发现上面哪个够造方法的详细说明中有这么一段:
Note: the results of this constructor can be somewhat
unpredictable. One might assume that new BigDecimal(.1)
is exactly equal to .1, but it is actually equal to
.1000000000000000055511151231257827021181583404541015625. This is
so because .1 cannot be represented exactly as a
double (or, for that matter, as a binary fraction of
any finite length). Thus, the long value that is
being passed in to the constructor is not exactly
equal to .1, appearances nonwithstanding.
The (String) constructor, on the other hand, is
perfectly predictable: new BigDecimal(".1") is exactly
equal to .1, as one would expect. Therefore, it is
generally recommended that the (String) constructor be
used in preference to this one.
原来我们如果需要精确计算,非要用String来够造BigDecimal不可!在《Effective Java》一书中的例子是用String来够造BigDecimal的,但是书上却没有强调这一点,这也许是一个小小的失误吧。
解决方案
现在我们已经可以解决这个问题了,原则是使用BigDecimal并且一定要用String来够造。
但是想像一下吧,如果我们要做一个加法运算,需要先将两个浮点数转为String,然后够造成BigDecimal,在其中一个上调用add方法,传入另
一个作为参数,然后把运算的结果(BigDecimal)再转换为浮点数。你能够忍受这么烦琐的过程吗?下面我们提供一个工具类Arith来简化操作。它
提供以下静态方法,包括加减乘除和四舍五入:
public static double add(double v1,double v2)
public static double sub(double v1,double v2)
public static double mul(double v1,double v2)
public static double div(double v1,double v2)
public static double div(double v1,double v2,int scale)
public static double round(double v,int scale)
附录
源文件Arith.java:
import java.math.BigDecimal;
/**
* 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精
* 确的浮点数运算,包括加减乘除和四舍五入。
*/
public class Arith{
//默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
//这个类不能实例化
private Arith(){
}
/**
* 提供精确的加法运算。
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的减法运算。
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的乘法运算。
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1,double v2){
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1,double v2){
return div(v1,v2,DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1,double v2,int scale){
if(scale<0){
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理。
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v,int scale){
if(scale<0){
throw new IllegalArgumentException(
"The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
BigDecimal one = new BigDecimal("1");
return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
};
0
推荐
要学习ANT的使用,要先了解什么是ANT。
什么是ant呢? ant是构建工具;那构建是又什么意思呢?形象来说,你要把代码从某个地方拿来,编译,再拷贝到某个地方去等等操作,当然不仅与此,但是主要用来干这个。
知道这些,我们还要了解为什么要用ANT,呵呵,哦对,就像那位MM说的,当然是有好处的:
A, 跨平台 --因为ant是使用java实现的,所以它跨平台
B,使用简单--与ant的兄弟make比起来
C, 语法清晰--同样是和make相比
D,功能强大--ant能做的事情很多,可能你用了很久,你仍然不知道它能有多少功能。当你自己开发一些ant插件的时候,你会发现它更多的功能。
一,构建ant环境
要使用ant首先要构建一个ant环境,步骤很简单:
1),安装jdk,设置JAVA_HOME ,PATH ,CLASS_PATH(这些应该是看这篇文章的人应该知道的)
2),下载ant 地址http://17xx.zhmy.com找一个你喜欢的版本,或者干脆最新的版本
3),解压ant 你得到的是一个压缩包,解压缩它,并把它放在一个尽量简单的目录,例如D:\ant-1.6虽然你不一 定要这么做,但这么做是有好处的。
4),设置ANT_HOME PATH中添加ANT_HOME目录下的bin目录
5),测试一下你的设置,开始-->运行-->cmd进入命令行-->键入 ant 回车,如果看到
Buildfile: build.xml does not exist!
Build failed
那么恭喜你你已经完成ant的设置
二,体验ant
就像每个语言都有HelloWorld一样,一个最简单的应用能让人感受一下Ant
1,首先你要知道你要干什么,我现在想做的事情是:
编写一些程序
编译它们
把它打包成jar包
把他们放在应该放置的地方
运行它们
这里为了简单起见只写一个程序,就是HelloWorld.java程序代码如下:
package test.ant;
public class HelloWorld{
public static void main(String[] args){
System.out.println("Hello world1");
}
};
2,为了达到上边的目的,你可以手动的用javac 、copy 、jar、java来完成,但是考虑一下如果你有成百上千个类,在多次调试,部署的时候,一次次的javac 、copy、jar、
java那将是一份辛苦的工作。现在看看ant怎么优雅的完成它们。
要运行ant需要有一个build.xml虽然不一定要叫这个名字,但是建议你这么做
下边就是一个完整的build.xml,然后我们来详细的解释每一句
<?xml version="1.0" encoding="UTF-8" ?>
<project name="HelloWorld" default="run" basedir=".">
<property name="src" value="http://www.zhmy.com/src"/>
<property name="dest" value="classes"/>
<property name="hello_jar" value="hello1.jar"/>
<target name="init">
<mkdir dir="${dest}"/>
</target>
<target name="compile" depends="init">
<javac srcdir="${src}" destdir="${dest}"/>
</target>
<target name="build" depends="compile">
<jar jarfile="${hello_jar}" basedir="${dest}"/>
</target>
<target name="run" depends="build">
<java classname="test.ant.HelloWorld" classpath="${hello_jar}"/>
</target>
<target name="clean">
<delete dir="${dest}" />
<delete file="${hello_jar}" />
</target>
<target name="rerun" depends="clean,run">
<ant target="clean" />
<ant target="run" />
</target>
</project>
解释:
<?xml version="1.0" encoding="UTF-8" ?>
build.xml中的第一句话,没有实际的意义
<project name="HelloWorld" default="run" basedir=".">
</project>
ant的所有内容必须包含在这个里边,name是你给它取的名字,basedir故名思意就是工作的根目录 .代表当前目录。default代表默认要做的事情。
<property name="src" value="src"/>
类似程序中的变量,为什么这么做想一下变量的作用
<target name="compile" depends="init">
<javac srcdir="${src}" destdir="${dest}"/>
</target>
把你想做的每一件事情写成一个target ,它有一个名字,depends是它所依赖的target,在执行这个target 例如这里的compile之前ant会先检查init是否曾经被执行过,如果执行
过则直接直接执行compile,如果没有则会先执行它依赖的target例如这里的init,然后在执行这个target
如我们的计划
编译:
<target name="compile" depends="init">
<javac srcdir="${src}" destdir="${dest}"/>
</target>
做jar包:
<target name="build" depends="compile">
<jar jarfile="${hello_jar}" basedir="${dest}"/>
</target>
运行:
<target name="run" depends="build">
<java classname="test.ant.HelloWorld" classpath="${hello_jar}"/>
</target>
为了不用拷贝,我们可以在最开始定义好目标文件夹,这样ant直接把结果就放在目标文件夹中了
新建文件夹:
<target name="init">
<mkdir dir="${dest}"/>
</target>
为了更多一点的功能体现,又加入了两个target
删除生成的文件
<target name="clean">
<delete dir="${dest}" />
<delete file="${hello_jar}" />
</target>
再次运行,这里显示了如何在一个target里边调用其他的target
<target name="rerun" depends="clean,run">
<ant target="clean" />
<ant target="run" />
</target>
好了,解释完成了,下边检验一下你的ant吧
新建一个src的文件夹,然后把HelloWorld.java按照包目录放进去
做好build.xml文件
在命令行下键入ant ,你会发现一个个任务都完成了。每次更改完代码只需要再次键入ant
有的时候我们可能并不想运行程序,只想执行这些步骤中的某一两个步骤,例如我只想重新部署而不想运行,键入
ant build
ant中的每一个任务都可以这样调用ant + target name
好了,这样一个简单的ant任务完成了。
到此你应该看出来了,ant的使用,关键就是build.xml的编写,一个优秀的build.xml可以用来做模板,甚至直接用就OK,打开命令提示窗口,键入ANT即可~~
一、利用SQL自带函数
SQL
Sever足够强大,可以在需要的时候把大部分数值从一种类型转换为另一种类型。例如,要比较SMALLINT型和INT型数据的大小,你不需要进行显式
的类型转换。SQL
Sever会为你完成这项工作。但是,当你想在字符型数据和其它类型的数据之间进行转换时,你的确需要自己进行转换操作。例如,假设你想从一个MONEY
型字段中取出所有的值,并在结果后面加上字符串“US Dollars”。你需要使用函数CONVERT(),如下例所示:
SELECT CONVERT(CHAR(8),price)+’US Dollars’ FROM orders
函数CONVERT()带有两个变量。第一个变量指定了数据类型和长度。第二个变量指定了要进行转换的字段。在这个例子中,字段price被转换成长度为8个字符的CHAR型字段。字段price要被转换成字符型,才可以在它后面连接上字符串’US Dollars’。
当向BIT型,DATETIME型,INT型,或者NUMERIC型字段添加字符串时,你需要进行同样的转换操作。例如,下面的语句在一个SELECT语句的查询结果中加入字符串’The vote is’,该SELECT语句返回一个BIT型字段的值:
SELECT ‘The vote is’+CONVERT(CHAR(1),vote) FROM opinion
下面是这个语句的结果示例:
The vote is 1
The vote is 1
The vote is 0
(3 row(s) affected)
如果你不进行显式的转换,你会收到如下的错误信息:
Implicit conversion from datatype ‘varchar’ to ‘bit’ is not allowec.
Use the CONVERT function to run this query.
操作字符串数据
SQL Sever有许多函数和表达式,使你能对字符串进行有趣的操作,包括各种各样的模式匹配和字符转换。在这一节中,你将学习如何使用最重要的字符函数和表达式。
匹配通配符
假设你想建立一个与Yahoo功能相似的Internet目录。你可以建立一个表用来保存一系列的站点名称,统一资源定位器(URL),描述,和类别,并允许访问者通过在HTML form中输入关键字来检索这些内容。
假如有一个访问者想从这个目录中得到其描述中包含关键字trading card的站点的列表。要取出正确的站点列表,你也许试图使用这样的查询:
SELECT site_name FROM site_directory WHERE site_desc=’trading card’
这个查询可以工作。但是,它只能返回那些其描述中只有trading card这个字符串的站点。例如,一个描述为We have the greatest collection of trading cards in the world!的站点不会被返回。
要把一个字符串与另一个字符串的一部分相匹配,你需要使用通配符。你使用通配符和关键字LIKE来实现模式匹配。下面的语句使用通配符和关键字LIKE重写了上面的查询,以返回所有正确站点的名字:
SELECT SITE_name FROM site_directory
WHERE site_desc LIKE ‘%trading cark%’
在这个例子中,所有其描述中包含表达式trading card的站点都被返回。描述为We have the greatest
collection of trading cards in the world!的站点也被返回。当然,如果一个站点的描述中包含I am
trading cardboard boxes online ,该站点的名字也被返回。
注意本例中百分号的使用。百分号是通配符的例子之一。它代表0个或多个字符。通过把trading card括在百分号中,所有其中嵌有字符串trading card的字符串都被匹配。
现在,假设你的站点目录变得太大而不能在一页中完全显示。你决定把目录分成两部分。在第一页,你想显示所有首字母在A到M之间的站点。在第二页,你想显示所有首字母在N到Z之间的站点。要得到第一页的站点列表,你可以使用如下的SQL语句:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[A-M]%’
在这个例子中使用了表达式[A-M],只取出那些首字母在A到M之间的站点。中括号([])用来匹配处在指定范围内的单个字符。要得到第二页中显示的站点,应使用这个语句:
SELECT site_name FROM site_directory
WHERE site_name LIKE ‘[N-Z]%’
在这个例子中,括号中的表达式代表任何处在N到Z之间的单个字符。
假设你的站点目录变得更大了,你现在需要把目录分成更多页。如果你想显示那些以A,B或C开头的站点,你可以用下面的查询来实现:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[ABC]%’
在这个例子中,括号中的表达式不再指定一个范围,而是给出了一些字符。任何一个其名字以这些字符中的任一个开头的站点都将被返回。
通过在括号内的表达式中同时包含一个范围和一些指定的字符,你可以把这两种方法结合起来。例如,用下面的这个查询,你可以取出那些首字母在C到F之间,或者以字母Y开头的站点:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[C-FY]%’
在这个例子中,名字为Collegescape和Yahoo的站点会被选取,而名字为Magicw3的站点则不会被选取。
你也可以使用脱字符(^)来排除特定的字符。例如,要得到那些名字不以Y开头的站点,你可以使用如下的查询:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘[^Y]%’
对给定的字符或字符范围均可以使用脱字符。
最后,通过使用下划线字符(_),你可以匹配任何单个字符。例如,下面这个查询返回每一个其名字的第二个字符为任何字母的站点:
SELECT site_name FROM site_directory WHERE site_name LIKE ‘M_crosoft’
这个例子既返回名为Microsoft的站点,也返回名为Macrosoft的站点。但是,名字为Moocrosoft的站点则不被返回。与通配符’%’不同,下划线只代表单个字符。
注意:
如果你想匹配百分号或下划线字符本身,你需要把它们括在方括号中。如果你想匹配连字符(-),应把它指定为方括号中的第一个字符。如果你想匹配方括号,应把它们也括在方括号中。例如,下面的语句返回所有其描述中包含百分号的站点:
SELECT site_name FROM site_directory WHERE site_desc LIKE ‘%[%]%’
匹配发音\r
Microsoft SQL
有两个允许你按照发音来匹配字符串的函数。函数SOUNDEX()给一个字符串分配一个音标码,函数DIFFERENCE()按照发音比较两个字符串。当
你不知道一个名字的确切拼写,但多少知道一点它的发音时,使用这两个函数将有助于你取出该记录。
例如,如果你建立一个Internet目录,你也许想增加一个选项,允许访问者按照站点名的发音来搜索站点,而不是按名字的拼写。考虑如下的语句:
SELECT site_name FROM site_directory
WHERE DIFFERENCE(site_name , ‘Microsoft’>3
这个语句使用函数DEFFERENCE()来取得其名字的发音与Microsoft非常相似的站点。函数DIFFERENCE()返回一个0到4之间的数字。如果该函数返回4,表示发音非常相近;如果该函数返回0,说明这两个字符串的发音相差很大。
例如,上面的语句将返回站点名Microsoft和Macrosoft。这两个名字的发音与Microsoft都很相似。如果你把上一语句中的大于3改为
大于2,那么名为Zicrosoft和Megasoft的站点也将被返回。最后,如果你只需要差别等级大于1即可,则名为Picosoft和
Minisoft的站点也将被匹配。
要深入了解函数DIFFERENCE()是如何工作的,你可以用函数SOUNDEX()来返回函数DIFFERENCE()所使用的音标码。这里有一个例子:
SELECT site_name ‘site name’,SOUNDEX(site_name) ‘sounds like’
这个语句选取字段site_name的所有数据及其音标码。下面是这个查询的结果:
site name sounds like
……………………………………………………………….
Yahoo Y000
Mahoo M000
Microsoft M262
Macrosoft M262
Minisoft M521
Microshoft M262
Zicrosoft Z262
Zaposoft Z121
Millisoft M421
Nanosoft N521
Megasoft M221
Picosoft P221
(12 row(s) affected)
如果你仔细看一下音标码,你会注意到音标码的第一个字母与字段值的第一个字母相同。例如,Yahoo和Mahoo的音标码只有第一个字母不同。你还可以发现Microsoft和Macrosoft的音标码完全相同。
函数DIFFERENDE()比较两个字符串的第一个字母和所有的辅音字母。该函数忽略任何元音字母(包括y),除非一个元音字母是一个字符串的第一个字母。
不幸的是,使用SOUNDEX()和DIFFERENCE()有一个欠缺。WHERE子句中包含这两个函数的查询执行起来效果不好。因此,你应该小心使用这两个函数。
删除空格
有两个函数,TTRIM()和LTRIM(),可以用来从字符串中剪掉空格。函数LTRIM()去除应该字符串前面的所有空格;函数RTRIM()去除一个字符串尾部的所有空格。这里有一个任何使用函数RTRIM()的例子:
SELECT RTRIM(site_name) FROM site_directory
在这个例子中,如果任何一个站点的名字尾部有多余的空格,多余的空格将从查询结果中删去。
你可以嵌套使用这两个函数,把一个字符串前后的空格同时删去:
SELECT LTRIM(RTRIM(site_name)) FROM site_directory
你会发现,在从CHAR型字段中剪掉多余的空格时,这两个函数非常有用。记住,如果你把一个字符串保存在CHAR型字段中,该字符串会被追加多余的空格,以匹配该字段的长度。用这两个函数,你可以去掉无用的空格,从而解决这个问题。
删除空格
update zjkjt.code_subject set code = rtrim(code)
二、利用SQL Update 语句删除空格
Update Table Set Colname=Replace(Colname," ","")
三、利用SQL Update 语句删除回车
Update Table Set Colname=Replace(Colname,char(13),"")
三、利用SQL Update 语句删除硬回车
Update Font Set DemoUrl=Replace(DemoUrl,'
','')
注意:以上语句不要合关成一行,否则无法达到效果。
可以从这2个表中查询,
user_constraints, user_cons_columns
查询的时候观察下表的内容即明白.
Ctrl+1 快速修复(最经典的快捷键,就不用多说了)
Ctrl+D: 删除当前行
Ctrl+Alt+↓ 复制当前行到下一行(复制增加)
Ctrl+Alt+↑ 复制当前行到上一行(复制增加)
Alt+↓ 当前行和下面一行交互位置(特别实用,可以省去先剪切,再粘贴了)
Alt+↑ 当前行和上面一行交互位置(同上)
Alt+← 前一个编辑的页面
Alt+→ 下一个编辑的页面(当然是针对上面那条来说了)
Alt+Enter 显示当前选择资源(工程,or 文件 or文件)的属性
Shift+Enter 在当前行的下一行插入空行(这时鼠标可以在当前行的任一位置,不一定是最后)
Shift+Ctrl+Enter 在当前行插入空行(原理同上条)
Ctrl+Q 定位到最后编辑的地方
Ctrl+L 定位在某行 (对于程序超过100的人就有福音了)
Ctrl+M 最大化当前的Edit或View (再按则反之)
Ctrl+/ 注释当前行,再按则取消注释
Ctrl+O 快速显示 OutLine
Ctrl+T 快速显示当前类的继承结构
Ctrl+W 关闭当前Editer
Ctrl+K 参照选中的Word快速定位到下一个
Ctrl+E 快速显示当前Editer的下拉列表(如果当前页面没有显示的用黑体表示)
Ctrl+/(小键盘) 折叠当前类中的所有代码
Ctrl+×(小键盘) 展开当前类中的所有代码
Ctrl+Space 代码助手完成一些代码的插入(但一般和输入法有冲突,可以修改输入法的热键,也可以暂用Alt+/来代替)
Ctrl+Shift+E 显示管理当前打开的所有的View的管理器(可以选择关闭,激活等操作)
Ctrl+J 正向增量查找(按下Ctrl+J后,你所输入的每个字母编辑器都提供快速匹配定位到某个单词,如果没有,则在stutes line中显示没有
找到了,查一个单词时,特别实用,这个功能Idea两年前就有了)
Ctrl+Shift+J 反向增量查找(和上条相同,只不过是从后往前查)
Ctrl+Shift+F4 关闭所有打开的Editer
Ctrl+Shift+X 把当前选中的文本全部变味小写
Ctrl+Shift+Y 把当前选中的文本全部变为小写
Ctrl+Shift+F 格式化当前代码
Ctrl+Shift+P 定位到对于的匹配符(譬如{}) (从前面定位后面时,光标要在匹配符里面,后面到前面,则反之)
下面的快捷键是重构里面常用的,本人就自己喜欢且常用的整理一下(注:一般重构的快捷键都是Alt+Shift开头的了)
Alt+Shift+R 重命名 (是我自己最爱用的一个了,尤其是变量和类的Rename,比手工方法能节省很多劳动力)
Alt+Shift+M 抽取方法 (这是重构里面最常用的方法之一了,尤其是对一大堆泥团代码有用)
Alt+Shift+C 修改函数结构(比较实用,有N个函数调用了这个方法,修改一次搞定)
Alt+Shift+L 抽取本地变量( 可以直接把一些魔法数字和字符串抽取成一个变量,尤其是多处调用的时候)
Alt+Shift+F 把Class中的local变量变为field变量 (比较实用的功能)
Alt+Shift+I 合并变量(可能这样说有点不妥Inline)
Alt+Shift+V 移动函数和变量(不怎么常用)
Alt+Shift+Z 重构的后悔药(Undo)
编辑
作用域 功能 快捷键
全局 查找并替换 Ctrl+F
文本编辑器 查找上一个 Ctrl+Shift+K
文本编辑器 查找下一个 Ctrl+K
全局 撤销 Ctrl+Z
全局 复制 Ctrl+C
全局 恢复上一个选择 Alt+Shift+↓
全局 剪切 Ctrl+X
全局 快速修正 Ctrl1+1
全局 内容辅助 Alt+/
全局 全部选中 Ctrl+A
全局 删除 Delete
全局 上下文信息 Alt+?
Alt+Shift+?
Ctrl+Shift+Space
Java编辑器 显示工具提示描述 F2
Java编辑器 选择封装元素 Alt+Shift+↑
Java编辑器 选择上一个元素 Alt+Shift+←
Java编辑器 选择下一个元素 Alt+Shift+→
文本编辑器 增量查找 Ctrl+J
文本编辑器 增量逆向查找 Ctrl+Shift+J
全局 粘贴 Ctrl+V
全局 重做 Ctrl+Y
查看
作用域 功能 快捷键
全局 放大 Ctrl+=
全局 缩小 Ctrl+-
窗口
作用域 功能 快捷键
全局 激活编辑器 F12
全局 切换编辑器 Ctrl+Shift+W
全局 上一个编辑器 Ctrl+Shift+F6
全局 上一个视图 Ctrl+Shift+F7
全局 上一个透视图 Ctrl+Shift+F8
全局 下一个编辑器 Ctrl+F6
全局 下一个视图 Ctrl+F7
全局 下一个透视图 Ctrl+F8
文本编辑器 显示标尺上下文菜单 Ctrl+W
全局 显示视图菜单 Ctrl+F10
全局 显示系统菜单 Alt+-
导航
作用域 功能 快捷键
Java编辑器 打开结构 Ctrl+F3
全局 打开类型 Ctrl+Shift+T
全局 打开类型层次结构 F4
全局 打开声明 F3
全局 打开外部javadoc Shift+F2
全局 打开资源 Ctrl+Shift+R
全局 后退历史记录 Alt+←
全局 前进历史记录 Alt+→
全局 上一个 Ctrl+,
全局 下一个 Ctrl+.
Java编辑器 显示大纲 Ctrl+O
全局 在层次结构中打开类型 Ctrl+Shift+H
全局 转至匹配的括号 Ctrl+Shift+P
全局 转至上一个编辑位置 Ctrl+Q
Java编辑器 转至上一个成员 Ctrl+Shift+↑
Java编辑器 转至下一个成员 Ctrl+Shift+↓
文本编辑器 转至行 Ctrl+L
搜索
作用域 功能 快捷键
全局 出现在文件中 Ctrl+Shift+U
全局 打开搜索对话框 Ctrl+H
全局 工作区中的声明 Ctrl+G
全局 工作区中的引用 Ctrl+Shift+G
文本编辑
作用域 功能 快捷键
文本编辑器 改写切换 Insert
文本编辑器 上滚行 Ctrl+↑
文本编辑器 下滚行 Ctrl+↓
文件
作用域 功能 快捷键
全局 保存 Ctrl+X
Ctrl+S
全局 打印 Ctrl+P
全局 关闭 Ctrl+F4
全局 全部保存 Ctrl+Shift+S
全局 全部关闭 Ctrl+Shift+F4
全局 属性 Alt+Enter
全局 新建 Ctrl+N
项目
作用域 功能 快捷键
全局 全部构建 Ctrl+B
源代码
作用域 功能 快捷键
Java编辑器 格式化 Ctrl+Shift+F
Java编辑器 取消注释 Ctrl+\
Java编辑器 注释 Ctrl+/
Java编辑器 添加导入 Ctrl+Shift+M
Java编辑器 组织导入 Ctrl+Shift+O
Java编辑器 使用try/catch块来包围 未设置,太常用了,所以在这里列出,建议自己设置。
也可以使用Ctrl+1自动修正。
运行
作用域 功能 快捷键
全局 单步返回 F7
全局 单步跳过 F6
全局 单步跳入 F5
全局 单步跳入选择 Ctrl+F5
全局 调试上次启动 F11
全局 继续 F8
全局 使用过滤器单步执行 Shift+F5
全局 添加/去除断点 Ctrl+Shift+B
全局 显示 Ctrl+D
全局 运行上次启动 Ctrl+F11
全局 运行至行 Ctrl+R
全局 执行 Ctrl+U
重构
作用域 功能 快捷键
全局 撤销重构 Alt+Shift+Z
全局 抽取方法 Alt+Shift+M
全局 抽取局部变量 Alt+Shift+L
全局 内联 Alt+Shift+I
全局 移动 Alt+Shift+V
全局 重命名 Alt+Shift+R
全局 重做 Alt+Shift+Y
对于大多数程序员来说,微软是一家值得崇敬的公司,能够加入微软,也是很多程序
员的愿望。在付出足够的努力后,一旦进入了微软,也就意味着可以和最先进的技术终日为伍,一直沿着技术这条路线走下去了。对吗?错。今年九月份刚刚加入微
软开发合作部的王洪超就为自己规划了一条技术管理的路线,除了在技术方面继续学习之外,他还希望在未来的时间里有意识的提升项目管理的能力。王洪超说:"
微软为员工的职业发展规划提供了足够的学习机会。"
对更多的程序员来说,进入微软仍旧是一个梦想。然而,与以往任何一个时候相比,做出职业规划的必要性更加迫切。面对层出不穷的新技术,激增的就业压力,不
断分化的开发角色,再加上IT发展的不明确,做出职业规划既是一种挑战,也是必须之举。
以前,学校的学生只要考取一个认证就很容易开始自己的职业生涯,已经工作几年的程序员更是成为公司抢夺的对象,而就在互联网热时,高级程序员更多想到的是
自己出去创业。现在一切都改变了,混乱的认证市场让毕业的学生失去了一块招牌,企业更注重其技能和做项目的经验,而少有工作经验的学生和企业需求之间形成
了无法弥补的裂痕。已经工作的程序员又面临着学习软件工程规范和技术更新换代的压力,不明朗的软件行业前景让他们在走向三十的路途上遭遇困惑。对于已经具
备相当水平的资深技术专家或者技术领导者来说,风险投资对软件项目的谨慎使得创业变得更加困难。即便如此,仍然有很多非软件专业的人员源源不断的加入到这
个大队伍中,更为市场增加了很多竞争的对象。 与企业需求接轨是学生的职业规划的第一步
很久以前,企业要承担起学生的培训工作,毕业的学生要在公司经过一段时间后,才能融入整个开发团队。而现在,很少有公司愿意承担这样的培训费用了,在激烈
的市场竞争下,发着工资却无法带来利润的职员是大部分企业无法容忍的。
大学教育是普适教育,教给学生的只是知识,而企业对学生的需求是技能。这之间就是一个很难弥补的差距。中科天博总经理王健华表示:"大学生学习完了之后,
只是知道是什么,根本不会用。学习了C、Delphi,学习了Java,只能够按照书本的案例照着做下来。但让他做一个最小的项目,包括一个带后端数据库
的小网站,都很难独立承担。" 前IBM
软件部高级软件工程师李巨锋现在担任科瑞尔思培训中心专职教师,他也表达了同样的看法,"企业更关心你学习到了什么技能,做过什么,如何把学到的技术应用
到实际中去。"
应届毕业生很难克服这个困难,因为学校不具备这样的环境。IT大环境没有解决的情况下,就需要大学生自己想办法。如果在学校通过某些方式已经积累到了经
验,当然是最好的。但如果没有积累到这些知识,就必须寻找机会通过其他途径弥补了。
曾担任亚信公司软件开发技术总监,现在创办达内培训中心的韩少云有也切身的体会。
"原来我也在亚信做人才管理,需要不断从社会上招聘好的软件工程师,组建开发团队。我发现很难找到合适的人选,但是一些被淘汰的人是很可惜的,他的基本素
质,包括计算机专业的相关背景非常不错,但具备的技能和企业不能很好的匹配。"为此,韩少云萌发了要做IT培训的念头,希望将企业需要而学员缺乏的知识和
技能通过这种培训进行弥补,创办达内科技以来也取得了巨大的成功。
对学生来说,提高适应企业需求的技能也是为了寻找到适合自己的公司,职业生涯就是进入正常的轨道中。寻找到适合的入口,面临毕业的学生要谨慎的做出自
己的选择。如果不好找入口,起点太低,对未来的发展也没有好处,甚至导致以后的工作习惯都不好。金山公司负责人力资源的副总裁王春伟说:"《哈里波特3》
中有一句话让我印象非常深刻:选择比能力更重要。一个人年轻也只有很短暂的几年,如果能够选择一个精彩有活力的团队,会使他的职业生涯充满精彩。如果在选
择职业的时候,因为一些失误造成两至三年没有学到什么,自己也会非常惭愧的。"
王健华表示,企业是员工社会价值的附着。"大公司在自身的工作规范和工作习惯会更加正规,其经理的素质会更好。很多人由于没有对未来进行规划,没有想清
楚,工作之后对自己的岗位不尊重,经理也看不上他。"
企业对软件工程流程越来越重视,这是企业做事的方式。要适应企业的需要,甚至先要学会规范的文档,然后才是技术,这样企业知道你受过正规的训练。王健华
说:"我们要求学员注重4方面的能力:眼界、学习的方法、技能和规范,他要学习如何与别人合作,比如在代码风格上统一。虽然只是一名程序员,但仍需要站在
项目经理的层面上看自己的工作,这样才能更好的合作,融入到团队中。"王春伟认为团队能力也是通用软件厂商非常看中的一点,"现在不是凸现个人英雄主义的
年代,在金山公司,一个人如果很孤僻,很冰冷,沟通很艰涩就不行。我们要求程序员心态非常端正,有良好的配合意识,个性特别乐观。"
一些学生也意识到了这点,正在软件学员读大四刘未鹏就表示:"我想当杂志社编辑、从事教育工作或进入研究机构,总之得是一个能够静下心来的地方。如果非要
进公司工作,也得像趋势、金山或微软这样具有开放工作环境的公司。现在国内很多公司还是被市场所奴役,不能建立自己的个性,个人觉得不要在一个终日赶工的
小公司工作。"因此,对现在的学生来说,不断增加和企业能够顺利接轨的砝码就是职业规划的第一步。 工作后程序员的职业规划是要将技术提升与行业结合.
已经工作一段时间的程序员更要注重职业规划。很多人刚毕业时充满活力,然而几年的摸爬滚打后,冲劲就会慢慢减弱甚至消失。再加上IT大环境的起伏不定,一
旦无法跟上技术或者行业发展的步伐,便会迷茫。王洪超说:"以前晚上睡觉之前想事情,经常都不知道自己以后做什么。"
这并不奇怪,工作的新鲜感褪色后,就设法需要通过一个目标为自己输送动力。这便是职业规划。
程序员首先需要提高系统设计能力。从2000年以后,软件业的编程思维和模式、方法发生了翻天覆地的转变,与九十年代、甚至2000年前是完全不同的。但
是,很多在企业工作的程序员的知识架构很难跟上。中科天博谢新华老师直率的指出:"现在很多程序员不是按照应该如何设计最好做得,而是按照我会什么来做
的,最终造成设计思想落后。现在做项目需要的是新的、巧的设计思想。我们可以将从无数次失败中得到的理念告诉他们,包括如何学习,如何思考,技术变化的原
因在哪里,其核心在哪里。这样程序员的理解力就会完全不同。现在,对程序员的要求降低了,但对系统设计的要求提高了。所以程序员必须要提高系统设计的能
力,否则永远出不了头。"
而曾经从事过宏观经济研究工作的李巨锋更喜欢从行业走向来谈问题。他说:"现在中国纯软件公司不多,但是,中国的经济非常好,从整个社会的经济和行业角度
可以得出很多有价值的内容。很多人不了解行业的需求情况,因此做不了判断。"
跟随一个快速发展的行业,就比较容易取得发展,而在一个发展缓慢的行业中成功就比较困难。当然,有了市场容量有多大和机会,并不代表一定会成功,但没有这
个因素成功的几率会更低。工作一两年的人,最关键的是要有一个方向感,不要太盲目,这就需要程序员有一定的判断力。
李巨锋认为,电信行业新增的市场容量每年有1万亿,而且由于是新增市场,人员也比较容易进入。此外,中国的手机市场非常大,由于无线应用的迅速发展,可以
预料,基于手机和电信结合的软件的就业前景就比较好,事实证明,很多人也在向这个方面转变。达内的成功与行业需求也不无关系,韩少云说:"从金融和电信行
业角度看,对软件人才的需求每年以30-50%的比例增长。熟悉这两个行业所需要的IT技术和背景的人才,需求量是最大,缺口也是最大的。"
中国的信息管理软件市场也是容易就业的市场。现在市场上有数不胜数的小管理软件公司,这是同中国社会走转型之路相关的。李巨锋说,"基于商流、资金
流、信息流、物流的行业都有相当的发展前景。通过产权交易的商流,带动资金流和信息流的发展。物流是新兴的行业,这些都需要主流的IT技术。中国的物流成
本现在是20.9%,美国是9.8%,差距非常很大,这其中有1万亿的市场容量,如果软件和信息化服务占20%的份额,也有几千亿的市场,行业需求很大
了。
事实也证明供应链等类型的软件市场发展非常迅猛。"
而对于游戏,李巨锋认为这不能称之为一个产业,因为它不像电信那样带动很大的一个产业链,现在取得成功的只有几个游戏,而且还主要偏重销售,不是一个上下
游的产业。尽管比较热,但李巨锋认为相比来说,至少一两年内可能不会有很大的市场容量,这就需要进行判断。
李巨锋认为,已经工作两年以上的程序员可以有几种基本的职业规划:技术专家、软件架构师、实施顾问或销售。其中程序员最主要的发展方向是资深技术专家,无
论是Java还是.NET,还是数据库领域,都要首先成为专家,然后才可能继续发展为架构师。"尽管架构师的职位可以工作一辈子,待遇也非常好,对于科班
出身的程序员最为适合,但这种工作职位非常有限。"一位毕业的学员到IBM软件开发中心后,过了一年左右,开始请教其未来的发展,结合对方的情况,建议他
先深入的技术,因为在中国架构师需要的条件比较复杂,而且需求量也比较少。
实施顾问和销售就比较偏向市场了,除了一定的技术能力外,需要掌握很多IT以外的知识。这些发展方向对于从其他专业转入IT的人员更为适合。比如用友就培
养了很多实施顾问,这些人加上行业背景,收入也很高。要做行业专家,就要比自己的行业客户还了解这个产业的发展现状。李巨锋说:"比如烟草行业,一定要了
解大的趋势,中国最多的烟民在哪里,其实是在山西和辽宁,烟草行业的产业政策是什么,直接导致我的需求是什么,这些信息对于职业发展很重要。IBM的一些
顾问都是行业内的资深专家,他们的发展就非常具有代表性。"
对大多数人来说,首先是要专,在技术上做的比较深入,然后进行工作调整,把自己转变为某个领域的专家,第三步,根据自己的情况,决定自己做软件架构师还是
高级的顾问销售,另外一部分人可能就会走向行政管理,这和个人性格有关。
要获得职业成长,培训也很有帮助。韩少云说:"在北美,职业培训是一种高端的培训,即便是专业的人员也需要职业培训。一个人在一生中需要经过很多职业培训
才能成为一个资深或者专业的人员。而在国内大家的观念中,职业培训还没有被大家广泛的认同。"
对于更高级的技术人员,他们所关注的就不是解决基本的生活问题了。他们所关注的就是发展和成就感。从现在看,他们更为缺乏的是交流,尤其是和水平差不
多或者更高的人进行交流。李巨锋说:"我建议他们做一些国产的产品,做一些自主产权的技术。比如,我们还有产品开发的部门就在做国产数据库设计。"
职业规划带动更好的成长
从中国的软件开发人员的层次看,工作几年以内处于初级水平的程序员占据最大的比例,至少在50%之上,高级的人员最多也就10%左右。但无论处在哪个层
面,一定要有规划,按照自己的个性和优势做一些规划。
一个程序员的成长,学习过程中首先要读到好书,然后是交到好的学友,找到好的老师,在这三个外围的条件下,找到适合的工作环境,结合自己的特点,然后在一
些重要的环节上遇到适合的人和合适的项目,这样才能成功。有些职位可能在开始的时候不适合,但工作一段时间后,可能就可以走上更高的职位。最近,IBM软
件学院就开始做一个"软件人才库"的项目,为的就是跟踪技术人员的成长,给他们在合适的时机提供合适的职位。业内人士说:"IT行业良性的人员流动也是很
好的。"
有了一定的职业规划,就需要补充自己缺乏的经验,只有经历过足够的项目,才有可能不断积累。对行业的判断有一定的理解之后,对一般的企业适应起来是没有问
题的,但高级的人才需要长时间的积累。
在中国,除了个人的基本技能,还需要具备一定的社会资源,行业资源和资本。学生要学会在提高智商的情况下,提高自己的情商。因为,在人和人交往中情商起很
大的作用。技术人员需要慢慢改变自己的一些思维方式。个人成长要有意识的积累社会资源,认识相关的人,了解相关的政策和行业发展的过程和规则,这些东西是
非技术的,非智商的,只要你关注就是可以得到的。我觉得最关键的就是多认识人,然后有意识的寻找合适的事情来做。情商也就需要有渠道,有多少人能够帮助
你,否则成不了。
机遇和经历对于职业的发展有相当的影响力。某些人或者某些事情都可能会影响到未来的发展。王洪超也认为成为微软MVP是自己的一个转折点,由于成为
MVP,便有机会接触更多的技术朋友和微软人,为自己的发展奠定了基础。
总而言之,每个人至少三年要点评一下自己:是环境的原因,还是自己个人的原因,如果是环境的原因,可以考虑是否需要换地方,如果知道自己,可以调整自
己或者设的目标现实一些。从一个普通的程序员做起,3年时间至少能够做到做独立需求分析和设计的。
JNDI是 Java 命名与目录接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之一,不少专家认为,没有透彻理解JNDI的意义和作用,就没有真正掌握J2EE特别是EJB的知识。
那么,JNDI到底起什么作用?
要了解JNDI的作用,我们可以从“如果不用JNDI我们怎样做?用了JNDI后我们又将怎样做?”这个问题来探讨。
没有JNDI的做法:
程序员开发时,知道要开发访问MySQL数据库的应用,于是将一个对 MySQL JDBC 驱动程序类的引用进行了编码,并通过使用适当的 JDBC URL 连接到数据库。
就像以下代码这样:
- Connection conn=null;
- try {
- Class.forName("com.mysql.jdbc.Driver",true, Thread.currentThread().getContextClassLoader());
- conn=DriverManager.getConnection("jdbc:mysql://MyDBServer?user=qingfeng&password=mingyue");
- ......conn.close();
- } catch(Exception e) {
- e.printStackTrace();
- } finally {
- if(conn!=null) {
- try {conn.close();
- } catch(SQLException e) {}
- }
- }
这是传统的做法,也是以前非Java程序员(如Delphi、VB等)常见的做法。这种做法一般在小规模的开发过程中不会产生问题,只要程序员熟悉Java语言、了解JDBC技术和MySQL,可以很快开发出相应的应用程序。
没有JNDI的做法存在的问题:
1、数据库服务器名称MyDBServer 、用户名和口令都可能需要改变,由此引发JDBC URL需要修改;
2、数据库可能改用别的产品,如改用DB2或者Oracle,引发JDBC驱动程序包和类名需要修改;
3、随着实际使用终端的增加,原配置的连接池参数可能需要调整;
4、......
解决办法:
程
序员应该不需要关心“具体的数据库后台是什么?JDBC驱动程序是什么?JDBC
URL格式是什么?访问数据库的用户名和口令是什么?”等等这些问题,程序员编写的程序应该没有对 JDBC
驱动程序的引用,没有服务器名称,没有用户名称或口令 ——
甚至没有数据库池或连接管理。而是把这些问题交给J2EE容器来配置和管理,程序员只需要对这些配置和管理进行引用即可。
由此,就有了JNDI。
用了JNDI之后的做法:
首先,在在J2EE容器中配置JNDI参数,定义一个数据源,也就是JDBC引用参数,给这个数据源设置一个名称;然后,在程序中,通过数据源名称引用数据源从而访问后台数据库。
具体操作如下(以JBoss为例):
1、配置数据源
在JBoss
的 D:\jboss420GA\docs\examples\jca 文件夹下面,有很多不同数据库引用的数据源定义模板。将其中的
mysql-ds.xml 文件Copy到你使用的服务器下,如 D:\jboss420GA\server\default\deploy。
修改 mysql-ds.xml 文件的内容,使之能通过JDBC正确访问你的MySQL数据库,如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <datasources>
- <local-tx-datasource>
- <jndi-name>MySqlDS</jndi-name>
- <connection-url>jdbc:mysql://localhost:3306/lw</connection-url>
- <driver-class>com.mysql.jdbc.Driver</driver-class>
- <user-name>root</user-name>
- <password>rootpassword</password>
- <exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
- <metadata>
- <type-mapping>mySQL</type-mapping>
- </metadata>
- </local-tx-datasource>
- </datasources>
这里,定义了一个名为MySqlDS的数据源,其参数包括JDBC的URL,驱动类名,用户名及密码等。
2、在程序中引用数据源:
- Connection conn=null;
- try {
- Context ctx=new InitialContext();
- Object datasourceRef=ctx.lookup("java:MySqlDS");
-
- DataSource ds=(Datasource)datasourceRef;conn=ds.getConnection();
- ......c.close();
- } catch(Exception e) {
- e.printStackTrace();
- } finally {if(conn!=null) {
- try {conn.close();} catch(SQLException e) { }
- }
- }
直接使用JDBC或者通过JNDI引用数据源的编程代码量相差无几,但是现在的程序可以不用关心具体JDBC参数了。
在系统部署后,如果数据库的相关参数变更,只需要重新配置 mysql-ds.xml 修改其中的JDBC参数,只要保证数据源的名称不变,那么程序源代码就无需修改。
由此可见,JNDI避免了程序与数据库之间的紧耦合,使应用更加易于配置、易于部署。
JNDI的扩展:
JNDI在满足了数据源配置的要求的基础上,还进一步扩充了作用:所有与系统外部的资源的引用,都可以通过JNDI定义和引用。
所以,在J2EE规范中,J2EE 中的资源并不局限于 JDBC 数据源。引用的类型有很多,其中包括资源引用(已经讨论过)、环境实体和 EJB 引用。特别是 EJB 引用,它暴露了 JNDI 在 J2EE 中的另外一项关键角色:查找其他应用程序组件。
EJB
的 JNDI 引用非常类似于 JDBC
资源的引用。在服务趋于转换的环境中,这是一种很有效的方法。可以对应用程序架构中所得到的所有组件进行这类配置管理,从 EJB 组件到 JMS
队列和主题,再到简单配置字符串或其他对象,这可以降低随时间的推移服务变更所产生的维护成本,同时还可以简化部署,减少集成工作。外部资源”。
总结:
J2EE
规范要求所有 J2EE 容器都要提供 JNDI 规范的实现。JNDI 在 J2EE 中的角色就是“交换机” —— J2EE
组件在运行时间接地查找其他组件、资源或服务的通用机制。在多数情况下,提供 JNDI
供应者的容器可以充当有限的数据存储,这样管理员就可以设置应用程序的执行属性,并让其他应用程序引用这些属性(Java 管理扩展(Java
Management Extensions,JMX)也可以用作这个目的)。JNDI 在 J2EE
应用程序中的主要角色就是提供间接层,这样组件就可以发现所需要的资源,而不用了解这些间接性。
在 J2EE 中,JNDI 是把 J2EE 应用程序合在一起的粘合剂,JNDI 提供的间接寻址允许跨企业交付可伸缩的、功能强大且很灵活的应用程序。这是 J2EE 的承诺,而且经过一些计划和预先考虑,这个承诺是完全可以实现的。
使用脚本能比较稳定和安全的将数据导入导出.不会丢失.
子查询:
用子查询能解决的问题
假想你想要写一个查询来找出挣钱比陆涛的薪水还多的人。为了解决这个问题,你需要两个查询:一
个找出陆涛的收入,第二个查询找出收入高于陆涛的人。
你可以用组合两个查询的方法解决这个问题,放置一个查询到另一个查询中。
内查询或子查询返回一个值给外查询或主查询。使用一个子查询相当于执行两个连续查询并且用第一个
查询的结果作为第二个查询的搜索值。
子查询语法:
SELECT select_list
FROM table
WHERE expr operator
(SELECT select_list
FROM table);
1.子查询(内查询) 在主查询之前执行一次
2.子查询的结果被用于主查询(外查询)
首先执行子查询 (内查询) 显示子查询返回的值,然后用内查询返回的结果执行外查询,最后,执行整个查询 (包括子查询),显示相同的结果。
子查询可嵌套的位置:
子查询是一个SELECT 语句,它是嵌在
另一个 SELECT 语句中的子句。
使用子查询你可以用简单的语句构建功能强大的语句。当你需要从表中用依赖于表本身的数据选择行时
它们是非常有用的。
也可以放在
WHERE 子句 HAVING 子句 FROM 子句。
在语法中:
operator 包括比较条件,例如 >、= 或 IN
比较条件分为两个种类:单行运算符 (>, =, >=, <, <>, <=) 和多行运算符 (IN, ANY, ALL)。
子查询通常涉及一个嵌套的 SELECT、子-SELECT 或内 SELECT 语句。字查询通常执行一次。并且它的输出被用于完成主或外查询的查询条件。
另外,子查询可以被放在 CREATE VIEW 语句中、CREATE TABLE 语句、UPDATE 语句、INSERT 语句的 INTO 子句和 UPDATE 语句的 SET 子句中。
使用子查询的原则:
1. 子查询放在圆括号中
2.将子查询放在比较条件的右边, 可以增加可读性。
在子查询中的ORDER BY 子句不需要,除非你正在执行Top-N 分析。
Oracle8i 以前的版本中,子查询不包含 ORDER BY 子句。对一个 SELECT 语句只能用一个 ORDER BY 子句,并且如果指定了它就必须放在主 SELECT 语句的最后。从 Oracle8i 开始,ORDER BY 子句可以使用,并且在进行 Top-N 分析时是必须的。
3.在单行子查询中用单行运算符,在多行子查询中用多行运算符,
在子查询中可以使用两种比较条件:单行运算符和多行运算符。
子查询的个数:
Oracle 服务器没有强制限制子查询的数目;限制只与查询所需的缓冲区大小有关。
子查询的类型:
1. 单行子查询:从内 SELECT 语句只返回一行的查询
2. 多行子查询:从内 SELECT 语句返回多行的查询
3. 还有多列子查询:从内 SELECT 语句返回多列的查询。
单行子查询
单行子查询是从内查询返回一行的查询。在该子查询类型中用一个单行操作符
SELECT last_name, job_id
FROM employees
WHERE job_id =
(SELECT job_id
FROM employees
WHERE employee_id = 141);
SELECT last_name, job_id, salary
FROM employees
WHERE job_id =
(SELECT job_id FROM employees WHERE employee_id = 141) AND salary >(SELECT salary FROM employees WHERE employee_id = 143);
该例子可以由三个查询块组成:外查询和两个内查询。内查询块首先被执行,产生查询结果分别为 ST_CLERK 和 2600。然后处理外查询块,并且使用内查询的返回值来完成它的查询条件。
两个内查询返回单个值 (分别是 ST_CLERK 和 2600),所以这种 SQL 语句被称为单行子查询。
注:外和内查询可以从不同的表中取得数据。
在子查询中使用组函数:
SELECT last_name, job_id, salary
FROM employees
WHERE salary =
(SELECT MIN(salary) FROM employees);
你可以从主查询中显示数据,该主查询使用一个带组函数的单行子查询。子查询放在圆括号中并且放在比较条件的后面。
例子显示所有其薪水等于最低薪水的雇员的 last name、job ID 和 salary。 MIN 组函数返回单个的值 (2500) 给外函数。
带子查询的HAVING 子句:
1.Oracle 服务器首先执行子查询
2.Oracle 服务器返回结果到主查询的HAVING 子句中
例
找出平均薪水为最低平均薪水的工作岗位。
SELECT job_id, AVG(salary)
FROM employees
GROUP BY job_id
HAVING AVG(salary) = (SELECT MIN(AVG(salary))
FROM employees
GROUP BY job_id);
子查询错误
使用子查询的一个常见的错误是单行子查询返回返回了多行。
SELECT employee_id, last_name
FROM employees
WHERE salary =
(SELECT MIN(salary) FROM employees GROUP BY department_id);
ERROR at line 4:ORA-01427: single-rowsubqueryreturns more thanone rowERROR
子查询包含一个 GROUP BY 子句,这就暗示该子查询将返回多行,每个对应它所找到的一组,在这种情况下,子查询的结果将是 4400、6000、2500、4200、7000、17000 和 8300。
外查询得到子查询的结果 (4400、6000、2500、4200、7000、17000、8300) 并且在它的 WHERE 子句中使用这些结果。WHERE 子句包含一个等号 (=) 运算符,这是一个单行比较运算符,只能使用一个值。 = 操作符不能接受来自子查询的多个值,并且因此产生错误。
为了纠正该错误,改变 = 操作为 IN。
子查询的另一个常见问题是内查询没有返回行。
,子查询包含一个 WHERE 子句,推测起来,其目的是找名字为 Haas 的雇员,该语句是正确的,但在执行时选择无行返回。
没有名叫 Haas 的雇员,所以子查询无返回行,外查询得到子查询的结果 (null) 并且在 WHERE 子句中使用该结果,外查询找不到一个 job ID 等于 null 的雇员,所以也没有行返回。如果一个 job 存在 null 值,也没有返回行,因为比较两个空值还是空,因此 WHERE 子句的条件不为 true。
多行子查询:
多行子查询
子查询返回多行被称为多行子查询。对多行子查询要使用多行运算符而不是单行运算符。多行运算符期待多个值。
例
查找各部门收入为部门最低的那些雇员。
SELECT last_name, salary, department_id
FROM employees
WHERE salary IN (SELECT MIN(salary)
FROM employees
GROUP BY department_id);
内查询先被执行,产生一个查询结果,然后主查询块处理和使用由内查询返回的值完成它的搜索条件。事实上,在 Oracle 服务器看起来主查询象是下面这样:
SELECT last_name, salary, department_id
FROM employees
WHERE salary IN (2500, 4200, 4400, 6000, 7000, 8300, 8600, 17000);
在多行子查询中使用ANY 运算符
SELECT employee_id, last_name, job_id, salary
FROM employees
WHERE salary < ANY(SELECT salaryFROM employeesWHERE job_id = 'IT_PROG')
AND job_id <> 'IT_PROG';
ANY 运算符 (和它的同义词, SOME 运算符) 比较一个值与一个子查询返回的每一个值。幻灯片中的例子显示不是 IT 程序员的雇员,并且这些雇员的的薪水少于IT 程序员。挣钱最多的程序员的薪水是 $9,000。
<ANY
摘要:
Java接口和Java抽象类
在没有好好地研习面向对象设计的设计模式之前,我对Java接口和Java抽象类的认识还是很模糊,很不可理解。
刚学Java语言时,就很难理解为什么要有接口这个概念,虽说是可以实现所谓的多继承,可一个只有方法名,没有方法体的东西,... 阅读全文
|
|
CALENDER
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
---|
26 | 27 | 28 | 29 | 30 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 1 | 2 | 3 | 4 | 5 | 6 |
|
常用链接
留言簿(3)
随笔分类(22)
随笔档案(76)
文章分类(12)
文章档案(17)
搜索
积分与排名
最新评论
评论排行榜
Powered By: 博客园 模板提供:沪江博客
|