TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
12#
发表于 2015-06-02 12:46:40
|只看该作者
六、 JDBC1 o1 V" J4 \' s
1.DAO设计思想与搭建骨架
% P- v+ S9 G$ t" i4 S% t! x(1).建立Domain包,在包中建立一个实体对象(bean).* M3 b2 b$ Y1 Y; v
public class User{9 S7 G( v. Z+ a
private int id;
8 l$ F: ]/ P' s7 ]4 N9 T5 g, f( xprivate String name;& P; U$ h' O8 z: w4 s r
private Date birthday;//java.util.Date3 p) ]% H* ]7 R
private float money;
, Z# m+ h, z1 M2 F! h* K! w//生成对应的get/set方法,省略& E7 c i% Q% w" D
}$ p5 Y' g; h" s/ [
: W {! S' K/ r, y% P; n3 ]- W(2).定义Domain接口:
3 Q- {7 Y6 G( M$ D. z, Rpublic interface UserDao{: W" g; q+ [+ ~
public void addUser(User user);4 Y$ ]* t/ a& u# d; P* _
public User getUser(int userid);0 N* i0 u; a3 H0 A' w8 Z
public void update(User user);
F: N& s9 I. z epublic void delete(User user);
. x p* p$ m5 n. H/ ^8 Z( Qpublic User findUser(StringloginName,String password);! J/ {( C5 j; O$ U
}- m {( A5 r3 {
这个接口是给service层使用的.# |1 X% F! O- @3 [! z) a, H. ]
P; @6 \# O, f- j( P- Q(3).实现UserDao接口- L: ?9 a3 v7 M$ h$ B
public class UserDaoImpl implements UserDao{7 f+ R3 }1 O f) t: _
public void addUser(User user){};' X" C2 @* W: F; K6 y* V
public User getUser(int userid){};
4 b9 z& _2 f0 V# Mpublic void update(User user){};
5 B% Q8 [( |& o2 a8 ~4 Kpublic void delete(User user){};' m0 q/ R. T5 S# p
public User findUser(StringloginName,String password){};7 E& q% y( M0 R& T7 V, g
, |! W6 u$ e8 y7 `8 W}3 \8 {% V0 H* f+ ^, E, k4 N: ^
2 c b/ x6 j& Q0 P+ d+ y, c
(4).在UserDaoImpl中抛出的异常进行包装,定义一个异常类
! f% `1 o8 A# D
2 P8 ~. _8 k" n/ p8 ^: X4 H$ e(5).工厂模式:UserDao userDao = DaoFactory.getInstance().getUserDao();+ L' Q0 B; p2 A% G9 i6 U' ?
- Z' U7 ]& k; p1 V) w) t2 R2.Java的动态代理及使用该技术完善连接代理, s9 ~( x* c+ I; y. A0 _' i9 u
(1).前面说到的静态代理模式,有点麻烦,因为需要实现接口Connection的所有方法.
5 d. J# y, `9 J2 n. ? ; R1 }1 u H C% K
(2).public classMyConnectionHandler implements InvocationHandler{
4 V H# w. q- U9 nprivate Connection realConnection;
% T h' Y" ~1 T1 c# U# b* \MyConnectionHandler(){
0 e5 q& ]. f3 u* P9 [}
/ j6 i+ t2 D8 b3 [" ]4 ^6 _Connectionbind(Connection realConn){//通过此方法将连接传进来
( r: {8 {) ?% ?: B- p$ OConnectionwarpedConnection = (Connection)Proxy.newProxyInstance(this.getClass().getClassLoader(),newClass[]{Connection.class},this);//动态的编写一个类,这个类实现Connection接口,最终会把Connection的所有方法都交给InvocationHandler处理器处理,在内存中直接产生一个字节码.4 G; _2 d4 }/ q7 J
returnwarpedConnection;
- b7 s n* d' c7 I+ O! h}* a% }( Y m9 B5 l
public Objectinvoke(Object proxy,Method method,Object[] args){( B: ?' f- c+ ^/ U% U) B( d* k8 v
if("close".equals(method.getName())){//是close方法( U! K( s8 s& c* f
this.dataSource.connectonsPool.addList(this.warpedConnection);4 F9 W8 S% v% x$ o) h( Y- Z# Q
}% ?$ c* N; N, T
returnmethod.invoke(this.realConnection,args);
+ V9 h0 B- o7 F8 S5 V}' L( G" u q. [- B1 u
}
' ^$ B. H! D% F8 X0 a" q2 r# m. |" X) n这就是动态代理模式,不管是动态的,还是静态的,最终到底都是关心操作Connection的方法.& v0 E, l. s1 W& ?) ]
) J' l5 W) \, e+ X! G5 g; d# t3.JdbcTemplate类中的其他各个查询方法
! L" V" }7 }6 L3 T(1).Spring的JdbcTemplate
7 K3 b( q" K$ E第一:查询带有参数,和行映射方法: @2 i& T3 f/ l
public ObjectqueryForObject(String sql,Object[]args,RowMapper rowMapper),使用自定义的UserRowMapper完成映射
7 G: a; [& Y- o2 n一个RowMapper的常用实现BeanPropertyRowMapper,该实现可将结果集转换成一个Java Bean(字段名与Java Bean属性名不符合规范,可用别名处理)返回一条记录.
& D' H j4 Y! Z5 E c! |第二:public List query(String sql,Object[]args,RowMapperrowMapper)返回多条记录; b' E! {' I! N1 }) G
第三:public int queryForInt(String sql)(如:selectcount(*) from user),其他结果比如String可用queryForObject方法向下转型
9 L0 w5 |8 q$ h. Qpublic MapqueryForMap(String sql,Object[]args)返回不是对象类型的Map(key:字段名或别名,value:列值);当查询的结果不是一个对象时,就是用一个Map进行存放结果.查询共多少条记录,最大值,最小值等信息时,当返回的是String类型时,就是用queryForObject(String sql);只是要对返回类型进行转换.
; s( W+ ?6 y+ Z6 d- \) Q! y第四:public List queryForList(String sql,Object[]args)返回多个Map( W, l1 w6 E9 S4 J6 F8 @. z0 F
+ V. t, i! G$ O5 ^- k5 k. M$ w
4.JDBC的理论概述
. I" P* z1 Z0 D0 ^6 }* I7 Z(1).JDBC(Java数据库连接)由一些借口和类构成的api,j2se的一部分,由java.sql,javax.sql包组成5 Y, ^* `( X" c, w
3 W( S+ I3 r2 Y1 \
(2).应用程序、JDBC API、数据库驱动及数据库之间的关系:
" X$ k6 p! @9 ^3 P& v. P9 A应用程序-->JDBC-->MySql Driver,Oracle Driver,DB2Driver--->MySql,ORacle,DB2' O! U) w3 J+ a1 K" ^- M
7 H9 n0 t+ Z& R* B; H
5.jdbc中数据类型与日期问题8 X1 x5 B m% \: {+ Y. F
(1).rs.getInt("id"),getString("name"),rs.getDate("birthday"),rs.getFloat("money")),不同的类型的获取数据.
- I4 C' U2 G; T2 {! N
; ^$ m+ X& w5 O6 g(2).java.sql.Date是继承java.util.Date,java.util.Date是日期和时间的,而java.sql.Date只有日期,而没有时间.- d) M3 O! V0 C/ m0 U- t2 H
, K" {% c: f; S1 h+ o
(3).不能将java.util.Date赋给java.sql.Date,所以:newjava.sql.Date(birthday.getTime()));这样就可以将java.util.Date转换成java.sql.Date,java.sql.Date直接赋给java.util.Date可以的.
1 X9 M+ n9 R0 C* k& ]' x
^: I9 _: t. @) P' z(6).st.executeUpdate(sql),带参数的方法是Statement的,不带参数的方法是PreperedStatement的
! T7 T. F7 F2 b" X4 i O0 Y: S& R; x" h( u
6.JTA分布事务的简要介绍1 c! G& ?" _ X# f) n3 |9 {4 t* p; w/ Y& t
(1).跨多个数据源的事务,使用JTA容器实现事务,分成两个阶段提交
2 q3 J# K+ l- m' `, g+ e. `* l0 ]5 rjavax.transaction.UserTransactiontx=(UserTransaction)ctx.lookup("jndiName");8 f) A* J m, |* k8 J* w
tx.begin();//connection1,connection2(可能来自不同的数据库)
0 k6 T1 p$ p" Vtx.commit()//tx.rollback();
o* x. \, {" a" \8 Q 0 p1 \+ T8 P5 N1 f {( C0 }( g
(2).tomcat不支持这种容器.weblogic可以支持.+ v: C" b) K s( _; O4 h2 w' L
0 D% U! o; y$ _) V5 u(3).第一阶段:向所有的数据库提交事务的请求,当有事务回滚的请求,所有的数据库都回滚,第二阶段:当没有回滚请求,就进行提交事务.# p, e8 R ~/ _/ X3 r K
) O3 K! [* c! w. w* k(4).分布式事务处理.- H; N# _7 Q* L4 d) b+ p3 q# m, U
6 \7 |5 U8 y0 Y3 ^5 \ }7.Statement的sql注入问题9 P" C0 G* s9 v6 E3 c! `
(1).SQL注入:PreparedStatement和Statement:6 ]6 q6 P4 X5 z& s$ _
在sql中包含特殊字符或SQL的关键字(如:'or 1 or')时,Statement将出现不可预料的结果(出现异常或查询的结果不正确),可用PreparedStatement来解决' [' p4 x7 v3 k% `0 u* U6 b
PreperedStatement(从Statement扩展而来)相对Statement的优点:7 S/ o2 Z0 [1 _: n$ E
第一:没有SQL注入的问题
8 v& `1 C: [! R2 @2 w第二:Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出) q. ]( k4 y, b6 h6 V A# \
第三:数据库和驱动可以对PreperedStatement进行优化(只有在相关联的数据库连接没有关闭的情况下有效)
$ _% M9 H1 C6 C! e8 B/ m" Q' |! jPreparedStatement是Statement的子接口.0 ?. h' J6 D# V- V) q& `4 g+ q; T+ F; G
* |- j4 w X; d, }- u) k# ~( S
(2).
; A# J& y) W$ F- j. K3 pPreparedStatementps=null;//预处理接口,需要进行预处理,所以在构造的时候就需要SQL语句了,ps=conn.prepareStatement(sql);而Statement是在查询的时候需要SQL语句.
$ ]% x/ Y* k+ _$ l" s0 u; F1 vStringsql="select id,name from user where name=?";?问号是占位符
* e8 ^8 a& B% v7 \3 ^ps.setString(1,name);将传过来的name参数替换第一个占位符?,在此过程中,将name进行的处理,将特殊符号去除,当执行查询时,不需要SQL语句了,不然会报错,rs=ps.executeQuery();4 W4 i; u u2 c( y( M M7 G* R
! W1 H# e1 N2 _/ Z* A/ z2 S9 U
(3).建立连接最消耗时间的,当程序执行多次时,PreperedStatement比Statement除去建立连接的时间,前者效率高.% t' u& I+ `8 Q% H& c5 x; d9 j
8 j, }/ M1 _! G9 s- m$ V. W* e8.编写一个基本的连接池来实现连接的重复使用
: P: X6 v8 H+ O+ [4 _% Q& ?4 Y! b(1).连接池经常使用到插入和删除,所以使用LinkedList,
$ p5 K( @1 [; K5 h/ z5 I9 A6 L/ npublic class MyDataSource{
2 `# q8 s P. c: K. b1 q" c& Dprivate LinkedList<Connection>connectionsPool = new LinkedList<Connection>();
5 Z' ~, c1 u7 L5 ?+ ~* Cpublic MyDataSource(){
* c( A) ?9 j; u9 b3 T" @ M2 Efor(int i=0;i<10;i++){//开始时创建10个连接' J+ c U* Z3 c' p$ S
this.connectionsPool.addLast(this.createConnection());7 Z# e2 x: |2 {
}: x ^' U' o6 }( d4 l9 ^
}
8 Y8 H' [% n! D" M* E3 r+ l. hpublic Connection createConnection() {//创建连接7 R) t+ v9 U$ K3 d! i; f7 I
returnDriverManager.getConnection(url,user,password);+ B4 D5 S) J y3 ?9 a7 C- E0 |
}
$ g( e# Y3 @* R' l) ~public Connection getConnection(){//获取连接" Z4 E: |, E% m9 V5 k
return this.connectionPool.removeFirst();
" O% ]9 }; e+ [0 g) H2 @. n}: }! K5 t/ z; |& V% H
public void free(Connection conn){//释放连接
/ L4 ~! r- }' j0 N9 x5 U* ythis.connectionsPool.addList(conn);- D$ c- V4 q( j2 `8 k B
}( Z* v; J$ S4 @5 d" h" |
}4 W/ ^. b9 ^4 D% u2 i$ S
得到连接并不是重复的.想重复的拿取连接,创建的连接数n<用户取连接数m,即可.
! t, ?, }: I; h1 ~8 ` + v* V6 Y" s" u# \ }# s
(2).( F: g2 q) m$ C- z u' f1 ]
private static int initCount=5;//定义初始化连接数4 x# P, M9 Z) C
private static int maxCount=10;//最大连接数
6 f! F8 E G1 @# L# Vprivate static int currentCount=0;//当前创建的连接数! s9 j, l' a% j7 K k! r- b4 v
* `, O/ m! X# N( O3 y9 Z
(3).为了保证并发操作,需要在获取连接中同步:- k) B$ d/ q8 [5 |6 k W# N
synchronized(connectionsPool){$ e" ~8 C& U; v ^+ z1 j
if(this.connctionPool.size()>0)//连接池中还有连接! f+ [/ x& q: d2 o
return this.connectionsPool.removeFirst();
9 F; P2 H! D; o) dif(this.currentCount<maxCount)//当前连接数没有超过最大连接数,可以接着创建连接,
7 q# W) P4 F3 e- @ x* b9 Ereturn this.createConnection();
8 F# y! l, {+ b! Jthrow new SQLException("已经没有连接了");//超过了最大连接数,抛出异常.
( n# \7 y C* Z2 H5 ?' Q}- i" Q; Y$ o- n4 X8 @$ W9 g: n7 I) n
5 M5 o' I4 U# ]( V6 O9 @4 u9.编写一个简单的JDBC的例子
: H1 w' y1 I# O- k, \! W0 [, n(1).连接数据的步骤:' l0 q% F+ d/ S( z X" Z
第一步:注册驱动(只做一次), o- s. ?3 I) l+ y8 a
DriverManager.registerDriver(newcom.mysql.jdbc.Driver());
0 y; f9 P7 H2 O" o! d z0 J; u" h第二步:建立连接(Connection)( R9 I/ H1 L. V9 G g2 B1 n# h
Connectionconn=DriverManager.getConnection("jdbc:mysql://localhost:3305/jdbc","root","");没有密码
% s' ]' H8 j' N2 O* U5 }第三步:创建执行SQL的语句(Statement)
2 P0 L- P- s2 T/ a$ G& y& sStatementst=conn.createStatement();3 w+ E$ F2 Z! O9 m9 e
第四步:执行语句
- P# Z- d3 a2 {* D7 T9 DResultSet rs =st.executeQuery("select * from user");4 Y% i6 @5 N- b. c& b$ w
第六步:处理执行结果(ResultSet)0 f ^: \$ s, i0 c' U
while(rs.next()){//遍历行! z( T- @. K; W
System.out.println(rs.getObject(1)+'\t'+rs.getObject(2));//第一列,第二列
( X2 E w1 q8 B9 U: h}
/ V: z O+ ~1 D9 R$ Z. ]第七步:释放资源
4 E3 P8 V+ O5 k) H# v* \/ frs.close();//关闭资源和打开资源的顺序是相反的
9 ^0 k+ H0 f& y" d& ?; gst.close();# w. [1 m; [* ^) ^
conn.close();% b# r' p \0 Z
/ c% ]9 J. N. {& O1 n6 D! }7 @7 t
10.参数的元数据信息& W4 b& U! z+ _) C8 g' l8 K
(1).
2 q, [9 p" V9 ?; \0 J( P) [Connection conn=JdbcUtils.getConnection();
3 v. s: C( W5 i/ s, W0 IPreparedStatementps=null;, I9 i5 h& s+ W' E( j
ResultSet rs=null;
1 h1 a% S4 i) {4 t" D' Aps.conn.prepareStatement(sql);//sql中可能含有参数(占位符),Object[]params存储参数,可以动态的查看sql中含有哪些参数.
# B; a2 I" S6 {, mParameterMetaDatapmd=ps.getParameterMetaData();
1 e8 R* h% O# p2 A B& zintcount=pmd.getParameterCount();//得到参数的个数) ^7 c' Q8 h9 ]( s
for(inti=1;i<count;i++){6 {& f' J5 u, U& k1 W- b1 r
System.out.println(pmd.getParameterClassName(i));//得到参数的类名) o& ?5 h' f- C8 ^+ Q' t6 f
System.out.println(pmd.getParameterType(i));//得到参数的类型
* F( a5 ~0 B7 hps.setObject(i,parames[i-1]);//遍历替换参数
( c- d5 K4 W ~/ [}
5 E" R, |" O2 V; H; V. {8 oString sql ="select * from user where name=? and birthday<? and money>?";
4 w. e9 K$ W, y$ r9 ?; O直接返回的类型都是String,VARCHAR" U) R( |1 A' [3 K) ?9 T
" F) n$ |3 ^8 e3 i( l0 Z9 F; G11.分析jdbc程序的编写步骤和原理
. A+ x* D. G$ F8 Y, P- `8 z/ k; _/ T(1).连接是通过底层的TCP/IP协议进行的8 K1 H6 u6 ]& A% C
9 a' m8 |& Y* K8 D1 q3 ^
(2).注册驱动:
2 L/ }0 L* Z0 B# x9 p1 j方式一:Class.forName("com.mysql.jdbc.Driver");5 Z: y2 z t6 h" W$ t9 o
推荐这种方式,不会对具体的驱动类产生依赖,类加载到内存中,会调用静态代码块:
5 s0 \4 K# z) _- \: a4 J& rstatic{
+ g( ?& t' a" y2 ]try{8 c: H: R8 f: j: S. f( o
DriverManager.registerDriver(new Driver());
& J5 c3 ^: ?& E. g. Z5 e; `}catch(SQLException e){
# b& X' k* @$ x/ ]# W+ qthrows RuntimeException();9 |( n- L8 [& F& _" T5 a; ^
}
4 n8 ^$ G" T! g}' z' _4 f, B0 A4 ~; g% e
方式二:DriverManager.registerDriver(newcom.mysql.jdbc.Driver());1 u+ B: W8 s* T, h* d9 }) h5 H$ o
会造成DriverManager中产生两个一样的驱动,并会对具体的驱动类产生依赖,其内部定义了一个Vector列表,将多个驱动存放到Vector中
5 B5 H% [; w+ k方式三:System.setProperty("jdbc.drivers","driver1:driver2");9 W2 {8 M; n1 ~! i: b
虽然不会对具体的驱动类产生依赖;但注册不太方便,所以很少使用,可以注册多个驱动
& z) \- |& [: {' G( i. L1 @) U $ Z3 f5 Z2 o- z6 k" l
(3).方式一接受的是一个字符串,方式二接受的是一个驱动类,所以具有依赖关系
' o) L3 ]& i' s& g( s
( M# E- h7 F3 C/ c$ R(4).创建连接:5 D, C1 b/ g; w: H; x5 U$ ^$ n v; x
Stringurl="jdbc:mysql://localhost:3394/jdbc";$ ?# W. j( n% B7 `, P1 h
格式:jdbc:子协议:子名称//主机名:端口/数据库名
1 ]5 _) }& ], n5 SString user="root";. T2 Q4 L$ J$ W# ^* C3 [
String password="";
, g% n# S8 {$ _6 p+ ]% c4 \Connectionconn=DriverManager.getConnection(url,user,password");
% q: O3 g9 i7 [7 x$ h3 Q3 G 2 ? @4 G, w8 [( N
(5).释放资源:数据库建立连接的个数也是有限制的,当数据库创建了多个连接,数据库可能运行的很慢,可能导致数据库崩溃,占用系统资源.
# i8 s/ P0 j# h( I) \ * |' c+ M' W! ^( m& k8 j. @/ p) i- D
12.分析在实际项目中该如何应用JDBC" @7 e. j" x5 _6 Q
(1).三层架构:2 a- M: I$ Q& W- w2 b
表示层:基于web的jsp、servlet、struts、webwork、spring web MVC等,基于客户端的swing,swt等
4 ]1 g& z/ a( [+ Y* x. `2 k8 g业务逻辑层:Pojo(service,manager),Domain,session EJB、spring
" G# A5 e1 e0 M, |7 e h1 p数据访问层:JDBC,IBatis,Hibernate,JDO,Entity Bean3 Y1 V3 ?/ N; B; x- C6 A
层与层之间用接口隔离+ m! c" `* V- |6 |- L; ?9 W7 T
' \& k+ C8 @6 D+ f: Q13.规范和封装JDBC程序代码4 s0 l- Z% u1 E7 c$ o5 d& @
(1).规范的代码:/ W/ X! ~- ? G, X% X7 @- I
Stringurl="jdbc:mysql://localhost:2332/jdbc";0 r* r( y3 R$ P3 D/ E8 U" {
String user="root";
/ W: x& ]6 q% t; _$ f! mString password="";
8 l. a) w. b: F! ]7 lStatement st=null; Z$ J. z. \+ u3 p8 E; l5 S4 j
ResultSet rs=null;
6 Z& @$ n( `8 a" |Connecton conn=null;5 k+ s8 _9 n; T' i J: K0 p5 y
try{
; n z! y) ]( O% LClass.forName("com.mysql.jdbc.Driver");
0 ] ?! I" o% S7 tconn=DriverManager.getConnection(url,user,password);# d4 X0 S8 R# y+ m4 d2 f
st = conn.createStatement();
A) j, y" H& P: |/ D. Irs=st.executeQuery("select * fromuser");
) O6 Q$ T; C+ l% f. I}finally{( h$ y5 C# A$ \& u* }# |1 H' Z* V
try{
( a0 y) S4 d4 p- p$ [/ Rif(rs!=null)# h6 n3 V1 a- r3 T* q0 t6 X
rs.close();
9 X$ N% M3 k4 v' o}finally{if(st!=null)
2 p$ a% x' o6 k) K/ J) w" Ftry{8 z5 B% x, [& Z5 G
st.close();! t" ~; L8 r: R8 I+ p0 b0 `
}finally{& V* {1 m" |; a J
if(conn!=null)$ h) f5 H2 n7 f( D7 x5 b
conn.close();
" E4 Y/ T% }8 v) U3 p9 `1 g4 M- j}
7 R& ]5 I! o/ {5 s+ n: \}1 B0 ^: ~4 [% A
}
* b9 r! _" z6 {& a1 e' k}4 v2 U7 n' q8 ?
3 s7 W2 T" \7 f; H% P. l: X! t0 o
(2).设计一个工具类:
4 s: j0 q; R. lpublic final class JdbcUtils{
9 c2 ~* e: W7 ?' s) D% F) Z; Tprivate static Stringurl="jdbc:mysql://localhost:2332/jdbc";
9 A+ P7 O: s! R6 \private static String user="root";( S2 c' T3 j9 X2 C
private static String password="";
" H7 T- W- }' L: h) N; Vprivate JdbcUtils(){//不允许实例化
/ r/ r" h, ~, f}& f0 \* V" q, U% h8 M& g
static{//驱动只注册一次$ @! J4 W# Y4 S" c3 ~
try{ @& e) o4 }; H) B S+ h
Class.forName("com.mysql.jdbc.Driver");
5 A5 |( o! q- H* P6 s {}catch(ClassNotFoundException e){
4 L4 V6 `, Y8 d+ s% cthrow new ExceptionInitializerError(e);4 V! k& F& F1 O. \1 E; p# X
}7 t/ l0 Q* W6 W# m) Y/ I
}* B |# N3 B- |" u, W
public static Connection getConnection(){//创建连接
% M/ I7 c7 ?: D$ Z+ O rreturnDriverManager.getConnection(url,user,password);! R2 Z0 e! R/ K3 w$ j
}( G0 S* S& ]& y. t
public static void free(ResultSetrs,Statement st,Connection conn){//释放资源
( W; U3 z I% Y0 S. Y, p+ O try{
: |! m6 T. ]4 w$ U9 g: Tif(rs!=null)3 |- B1 b% V' E9 F! p0 i
rs.close();
; F5 K: I) l4 h0 D$ c+ {2 z* k3 L}catch(SQLException e){) `# F8 d, E: _! Z5 y
e.printStackTrace();
# z% t' B0 `5 D; F}finally{if(st!=null)
6 B# N' _" M' T2 ~/ Ltry{
' U% s# ]! Q; h- P5 l st.close();
* H* U, e! |; A- t3 c}catch(SQLException e){* G* W9 w3 Z0 e2 j6 b- s0 A( O
e.printStackTrace();5 m4 t3 c7 {$ s/ C
}finally{
, \6 q1 v" s6 q& c# @2 |if(conn!=null)
/ F7 J/ a9 {& l* }, Q" r" u8 j2 W5 yconn.close();
3 u' |5 c0 n" a1 M}2 a1 H7 b9 i. l* E7 H# M8 @
}7 J, }- C1 n' U
}
0 S5 p5 `- x3 \& v8 ]}
7 Y9 C) K5 j. g" H ' H3 |% d- d8 \" h
}
& ~! p7 I) G* S+ Y; q# Y' n ' o! ?+ i" t% Z5 x/ h' p
14.将Dao中的修改方法提取到抽象父类中
) e6 L$ x' I+ o- u" Z, H% Q(1).对于代码的重构,焦点就是将代码变化的部分和不变的部分分离开来.一个是sql语句不同,参数不同,提取一个超类,相同的部分,放到超类中,不同的部分由子类实现.! S# x4 K! x# V5 ?7 e9 L6 m5 e* f* t
publci abstract class AbstractDao{
) ?( g" h/ f$ {& S' G5 [public void update(String sql,Object[]args){
! ~, K& J( f; p# J5 N3 A1 rConnection conn=null;& [1 W" {- x- W7 T# D! ?- A+ C
PreparedStatement ps=null;9 `( R/ [8 v; S/ Z0 l7 a4 J; \ f
ResultSet rs=null;* n M# G. N* W% T+ r
conn=JdbcUtils.getConnection();) a6 w6 m3 a, z9 \% }( c* j
ps=conn.prepareStatement(sql);
7 t Q+ w- n3 Q- C( h" m* Gfor(int i=0;i<args.length;i++){//用args参数列表,更新数据# q6 [+ F& Y7 J( @5 k/ ?$ @
ps.setObject(i+1,args[i]);. t3 n# `$ ?9 T" Y
}
7 }. I7 _5 B4 o/ b1 N. ^" P8 K}
! J; I' Y2 S& o8 j r- b+ M//args就是参数列表,, L4 b6 H( K I/ L; ^2 b
}5 H7 r, g9 V, C! W2 o0 Y- W2 Z$ J
5 m0 p q2 e4 Q; b/ `8 i
public class UserDaoImpl extendsAbstractDao{
8 R# g& b) t$ J( n6 N6 ~public void update(User user){ T; r. A! v! {2 Z4 F
String sql="update user setname=?,birthday=?,money=?,where id=?";5 |9 E$ L5 j' g$ r8 H Z, R& ]7 h6 g
Object[] args=new Object[]{user.getName(),user.getBirthday(),user.getMoney(),user.getId()};' }7 }: w/ h$ ~3 z# v4 D5 j8 E
super.update(sql,args);//调用父类的update方法.# _# b3 N% W) O" F. [
}
+ i2 c" T0 o2 B* u, I}" w: A8 a' A+ M
: F( y! ]' {2 p
15.可更新和对更新敏感的结果集: Y3 ?7 r, Z# P
(1).7 a. F! E9 B5 I! b* k* x% c5 Z5 |7 a
st=conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,Result.CONUR_UPDATABLE);; }( i1 ]+ y, K# Y6 W: X. A) l
在读取数据时,可以更改数据,可更新结果集.
; z; B, _9 u0 u& R4 C
2 g9 s5 C4 u9 c/ }8 F# V6 u, M(2): `2 ~* l. A" E( n% t7 B
while(rs.next()){+ t2 E1 @: V' P' ~8 o4 e
rs.getObject("name");
0 E' p r3 {. j, Nrs.getObject("money");
* ~3 b7 a+ Z5 I! WString name= rs.getString("name");9 o- R* a5 ?6 r, j
if("lisi".equals(name)){
% x" G8 v6 n* f2 C2 f. y1 Vrs.updateFloat("money",200f);
, _" P7 k5 {! B& x5 u* J% J/ prs.updateRow();//更新行
$ D" z! P5 C% B3 F# C* j}# f) d; ]% B9 a
}' v- X z6 `2 Y8 C- O% D& V5 j
(3).这种方式不常用,在查询时,把数据更改了,给人一种不明确感.
0 x7 t' S) p8 v2 f在查询结果集时,更新数据时数据库能不能感知到数据更新了.数据库敏不敏感SENSITIVE7 k2 B% }8 I0 ]7 l
5 t; G. x7 x4 O/ f7 v: Y( Z16.利用结果集元素数据将查询结果封装为map
3 H! N* b3 E/ f: j# o0 K(1).将查询结果放在map中; ^2 d! F! `8 a0 F! _
key:列的名称
: J! ^1 R8 q& L7 k8 J" Ovalue:列的值/ m+ Q5 y" P" }& \$ q
) ]! A1 T. l( H5 l4 k& _; x+ S2 k(2).
0 z, L: M3 O/ ~2 K: qConnection conn=JdbcUtils.getConnection();
8 G \% _: ]4 f, v9 Lps=conn.prepareStatement(sql);
) M a1 p+ R, d; X0 q, {rs=ps.executeQuery();
1 D& q ^' E0 TResultSetMetaData rsmd =rs.getMetaData();//获取结果集的元数据" ]6 w9 A. H) z0 D8 a+ e5 |0 o& G
int count= rsmd.getColumnCount();//结果有多少列
7 _& f% Q# k1 j2 [for(int i=1;i<=count;i++){//循环遍历列5 r' y, }4 J" H; A8 j
System.out.println(rsmd.getColumnClassName(i));//每一列的类型名$ f- ]1 r/ \: ~/ u, w4 G j' H3 v
System.out.println(rsmd.getColumnName(i));//每一列的名称$ \& U1 ] j8 p" c* v% C- t
System.out.println(rsmd.getColumnLabel(i));//每一列的别名
+ e1 S3 {% J+ x- T}
9 t3 H" h) ?- w: _7 n/ e这里就可以准确的得到每一列的类型,而不像前面的都是String类型.
( ]/ {" N' v- QString[]colName=new String[count];//存放每一列的名称
: M H0 O0 O, z8 h+ [* AMap<String,Object> data=null;
) B+ ^- i0 R+ Y& U. z1 l& Owhile(rs.next()){//按行循环
: t/ E; ^' I4 S& B0 jdata = new HashMap<String,Object>();
& n7 u% v. K0 Ffor(int i=0;i<colNames.length;i++){//按列循环- y- p, s( g3 [' W
data.put(colNames[i],rs.getObject(colNames[i]));//根据类名得到列的值- B: o8 w* M* D8 g* w9 v
}- B) m7 U. [! l4 K
}* i9 ?$ v9 [0 {" p% F1 B
灵活性非常高.能够按照各种方式查询
1 I! n; S) J1 I8 R4 U
! ]% w1 k' V) j6 u16.如何使用开源项目DBCP
' W) M* s9 X; F K(1).dbcpconfig.properties数据源的配置文件:
, s% ]+ A0 {9 c% X9 o9 ^6 b连接配置:( T% d: t0 ~0 R, ]; F
driverClassName=com.mysql.jdbc.Driver
+ T% p& O$ N% X, r; @0 I. Qurl=jdbc:mysql://localhost:3306/test) ?, L1 ] z! ~- h! n% [0 N- N
username=root
1 \& A% ^2 w: r' @# \( a9 D5 o' t+ Jpassword=root
+ {0 f) H" W' t初始化连接:, v9 \4 o9 V7 d
initialiSize=10: q- l/ F2 f" _- B4 l" S) \8 w2 A
最大连接数量:
; _( }* ^! W) K9 [% FmaxActive=50
; L. x" G; h& Y# O. [5 ]最大空闲连接://不同的时间段创建的连接不同,可能出现空闲连接.
: }1 n) m) f& m5 |- _, T, @% c* ?9 zmaxIdle=20
5 o, x- u0 f$ E; c5 E# L4 [" R最小空闲连接:
, u/ z6 L+ j" o8 M8 x0 j( m+ QminIdle=5) Q0 q* Q/ \( x
超过等待时间以毫秒为单位 6000毫秒/1000等于60秒; E* F" i& h& y+ Y/ ]. S
maxWait=60000
4 L$ p+ v+ M" G//当没有连接可取的时候,让当前线程等待一段时间,在去拿连接
0 ]4 g# M# i0 w4 G# r4 Z3 d; cJDBC驱动建立连接时附带的连接属性,属性的格式必须为这样:[属性名=property;],注意:"user" 与"password"两个属性会被明确地传递,因此这里不需要包含它们(url后面携带的值)
0 i' ~! _# L( pconnectionProperties=userUnicode=true;characterEncoding=gbk; c. C; M9 k/ P( B
指定由连接池所创建的连接的自动提交状态! S+ @3 N( ^" N# |) v* W; x$ s
defaultAutoCommit=true
% s# F' I& O$ i+ J# [3 D! Mdriver default指定由连接池所创建的连接的只读(read-only)状态,如果没有设置该值,则"setReadOnly"方法将不被调用,(某些驱动并不支持只读模式,如:Informix)
$ _1 h2 l) L+ q/ y9 B* ^defaultReadOnly=
2 D$ W6 j. Y" R7 sdriver default指定 由连接池所创建的连接事务级别(TransactionIsoation),可用值为下列之一(详情可见javadoc)NONE,READ,UNCOMMITTED,READ_COMMITTE( k7 ]4 `% x \( {
defaultTransactionIsolation=READ_UNCOMMITTED
9 `. ~& Y" x7 t 4 \% V9 J) V Y
(2).DBCP是apache的开源项目.实现了DataSource接口.Data Base Connection Pool,修改代码需要从新编译,打包,修改配置文件只需要重启即可.$ Z# @+ [& u& z, s; \5 D
8 e% L7 O' y v0 F: U; O(3).
E+ ?4 }/ J X$ ?/ p( L5 W% ~Properties prop = new Properties();4 E; ~' i- p8 o% `! ]
InputStream is =JdbcUtils.class.getClassLoader().getResource.AsStream("dbcp.property");//读取property文件 f% [0 i7 u9 |. G4 E
prop.load(is);
7 s. k$ D* e# }" t+ ?7 v8 T [private static DataSoruce myDataSource=null;
* V$ d) E" L0 d/ |myDataSource=BasicDataSourceFactory.createDataSource(prop);* n" C. Y' e0 p. e. B+ ?# X
' j, r* S7 D! v- W5 J; z% v(4).DataSource用来取代DriverManager来获取Connection;通过DataSource获得Connection速度快;通过DataSource获得Connection都是已经被包裹过的(不是驱动原来的连接),它的close方法已经被修改了;一般DataSource内部会用一个连接池来缓存Connection,这样可以大幅度提高数据库的访问速度;连接池可以理解成一个能够存放Connection的Collection;我们的程序只和DataSource打交道,不会直接访问连接池.
- }* L8 w. p1 i+ X
" k) V* p) v6 r+ N- m2 `3 T(5).使用dbcp需要的三个包:common-dbcp.jar,common-collections.jar,common-pool.jar8 ^8 ^9 w2 U5 S* X$ G: H4 t
y$ h# u/ R5 o* y# Q17.使用JDBCTemplate工具类简化对象查询) ?# B6 d5 u5 Z9 Q# y( N
(1).Spring框架中提供了一个JdbcTemplate工具类,JdbcTemplate类对JDBC API进行了很好的封装,这个类就像我们自己对JDBC进行封装一样,只是代码更健壮和功能更强大而已,我们以后在实际项目中可以使用JdbcTemplate类来完全替换直接使用JDBC API,这与直接使用JDBC API没有太大的性能区别,使用JdbcTemplate类需要额外从spring开发包中导入spring.jar和commons-logging.jar包% }5 U7 B% @# M" K- ^2 N3 Q
r; h, \# H7 x3 S# H7 Y% o; G, P(2).JdbcTemplate的设计思想和前面的MyDaoTemplate类是相同的6 i# ~8 W& N, U1 n
static User findUser(String name){4 `( x$ l- Q6 j' v
JdbcTemplate jdbc = newJdbcTemplate(JdbcUtils.getDataSource());//参数是拿到一个数据源" N: q6 {: ]8 [* t* j$ t
String sql = "select id,name from userwhere name=?";/ W& T$ ]7 {+ ^9 h/ V" P
Object[]args=new Object[]{name};( V* K4 q* v' Z' o; d3 C1 ]
jdbc.queryForObject(sql,args,newRowMapper(){//行映射器' C+ @2 r* C& g b% a( R
public Object mapRow(ResultSet rs,introwNum){
D6 H6 s" l/ q7 f6 r4 N- xUser user = new User();! B9 c( A) \7 a, |# z, C7 n. a
user.setId(rs.getInt("id"));
1 X$ ~" w* b" m5 F( m' _% I8 C- l! ]) Treturn user;* _" \- M e- w* [( O, |
}2 j% ?, W8 N9 E4 v" B; v3 ~0 d
});
8 U+ \' s/ J- E$ e) w) v" Kreturn null;6 W" e% b3 J& @
}( G$ g+ J6 f2 i3 A! E
//这里可以不需要实现行映射器,可以用类代替:
: S m$ n# G, s3 n9 {9 D" @new BeanPropertyRowMapper(User.class);即可
! ?/ }" Z0 X! X" d; B6 p, o! [
$ \9 T) J; V! M# }; d18.使用JdbcTemplate实现Dao和用工厂模式灵活切换实现# E& ^8 @: v# ?/ r4 T4 g a
(1).
" U# |' W0 D3 H q; @+ p) ?public class UserDaoSpringImpl implementsUserDao{' t6 K4 t+ L1 q% U( n2 C6 o5 n0 Z" G
private SimpleJdbcTemplatesimpleJdbcTemplate
4 ?1 Z8 q3 p& R, n2 T= new SimpleJdbcTemplate(JdbcUtils.getDataSource());
* o7 H# O- v6 {. M//增加用户0 E/ M; o! N5 e; C
public void addUser(User user){8 w7 N4 A/ ~3 ?* n- v
String sql = "insert into user(name,money,birthday)values(:name,:money,:birthday);
8 P9 |) i$ w: a. s3 Q! _KeyHolder keyHolder = newGeneratedKeyHolder();
, \/ V+ W' M( @" n0 Y3 E) BSqlParameterSource param = new BeanPropertySqlParameterSource(user);
; R4 p0 p& w5 S, W) l: {8 G; xthis.simpleJdbcTemplate.getNamedParameterJdbcOperations().update(sql,param,keyHoler);+ \9 T2 d4 D: S. A( i: v
/ t' C! f/ r) x; u0 w
user.setId(keyHolder.getKey().intValue());
/ T* p6 }8 Z7 w}
' C$ v* o# t2 u) W! D7 R}
- {2 w {% [( k# U, f2 P+ w' _//增加user的代码减少了太多了.! l) I( s" j( ^5 c5 w1 B) V2 B
delete,update等方法都是大同小异, J9 j' [8 @/ {& X8 J' p% P
//删除用户
/ T6 d4 u8 r0 l' jpublic void delete(User user){4 W6 M" n2 M) q& X+ k
String sql = "delete from user whereid=?";" q8 J+ s5 `' E4 y4 L
this.simpleJdbcTemplate.update(sql,user.getId());9 ?- p! B. R: S/ I( `% f+ a
//使用了可变参数的特性,不需要复杂的Map存储参数( s3 `# p6 Q5 ]) G
}3 x, P2 e/ _* C4 {# y- q/ e
//查询用户7 N% j J: W1 F
public User findUser(StringloginName,String password){
4 w3 i+ n1 ~3 V- u! W3 Q4 S//如何简化返回查找集) J7 W8 x& q6 `/ w F: ?. s9 R
String sql = "selectid,name,money,birthday from user where name=?";
$ A" v: K( W _' L; U* W7 R' X& Q( b% Nreturnthis.simpleJdbcTemplate.queryForObject(sql,ParameterizedBeanProertyRowMapper.newInstance(User.class),userId);8 w6 y8 Q* P' N! ]9 c) e4 M
}& E e9 u- j) _* a
//更新用户
! z/ X; J0 d/ `( n" C& mpublic void update(User user){//如何替换占位符?
7 f9 [) B- e5 N# f& QString sql ="update user setname=?,birthday=?,money=? where id=?";8 _, t2 z J* ~/ Q e/ I+ }" N
this.simpleJdbcTemplate.update(sql,user.getName(),user.getBirthday(),user.getMoney(),user.getId());* k/ C, u Y8 d* m; y
//这里也可以使用bean属性的参数源; Q. |: r1 B3 m* s- v
}6 G+ b* w% a+ X) m; G5 S% W" o6 U
代码量大大减少了.* U+ o$ K+ v# h6 O! R: V- t; n; U5 W1 y
% O( H$ y- m! C2 ~% ]1 X8 j
19.使用JDBC的批处理功能- ~# Y4 n; L4 |! q$ Q8 j: U" C
(1).和数据库打交道的成本是很高的,当需要发送多条sql语句时,成本更高了,这时就需要使用批处理技术,将多条查询语句打成一个包., {& D( K' o- ?3 N4 i
; h' y! I' T! ]( y. Q(2).
8 w; {- f4 s( ufor(int i=0;i<10000;i++){0 D1 R5 H* k& y$ |6 k8 d3 H$ A. m
ps.setString();
2 S) R+ a5 b; \9 y' T, S9 Rps.setName();& L4 ^: p/ M& B$ Q7 ~' V3 G6 c
ps.addBatch();//把一条更新语句增加到包中
. Q- X( q7 {1 r$ V% I}
* K) m( I- R# r+ @4 i+ e- w- Eint[] a = ps.executeBatch();//执行批处理,不是ps.executeUpdate();
}7 d1 S$ h! a- a1 O4 E) a ~8 L0 E* G. m8 I+ [9 O
(3).首先将语句打包时,并不是包越大越好,如果包过大的话,可能造成内存溢出,所以可能将一个打包在分成几个小包进行发送,不同的数据库,包的最适合大小是不同的.
; N3 Z5 L, H$ e5 p7 g; e! t9 |1 e " W% _; K! R& v
(4).并不是所有的批处理都能提高性能,这和不同的数据库以及数据库驱动决定的.
& y* V0 E: l2 c, g, [& E / p! _0 A* d& e) ?& J+ u
(5).Hibernate就是用了批处理技术,但是它进行了一些优化技术.
( z# u+ f& {) t6 p
( _# n7 ^' Z- m& ?20.使用JDBC调用的存储过程8 ~6 y- h; r$ g) |! ]* [
(1).存储过程经常用在以前的两层结构中,现在的三层结构已经就用不到了& T' k3 I5 W8 U6 L6 g
" t# @4 g- p0 _$ b8 r(2).CallableStatement(从PreparedStatement继承来的)
0 T7 O$ E' U! _; njava代码:
: T/ Y; l8 R; y% I: hCallableStatement cs=null;5 ~+ v5 J2 P9 \2 z. p
String sql="{calladdUser(?,?,?,?)}";% i5 {5 I# a$ X2 `7 P. m( ~ F
cs=conn.prepareCall(sql);$ f( N0 L9 S7 T3 \$ Z% O/ H1 e7 }
//替换参数2 r& L" T1 E5 W+ f- ?( Q r$ _7 V
cs.registerOutParameter(4,Types.INTEGER);
7 e& Q# \) c. t- {5 ~6 F4 jcs.setString(1,"ps name");. U7 m5 |+ L6 S3 x
cs.setDate(2.new java.sql.Date(System.currentTimeMills()));! Q* N8 Y1 i* n- C
cs.setFloat(3,100f);' H3 c6 c. P) E; p
cs.executeUpdate();
8 f8 s( e3 Q: X- Z) c5 F4 i2 Pint id = cs.getInt(4);
; O, d. U. a2 I9 S' [& o4 ISystem.out.println(id);
' c1 o$ `3 W# v3 |+ R存储过程:
% W7 ~! R+ A) I+ R3 d- Acreate procedure 'jdbc'.'addUser' (in pnamevarchar(45),in birthday date,in money float,out pid int)
! F7 l9 _: Y6 I. B2 }; D//in:输入参数,out:输出参数6 ?& n" o5 ~3 G/ ]$ J$ o
begin4 g2 I5 y3 E. `( s& @7 r
insert intouser(name,birthday,money)values(pname,birthday,money);
- r9 j. N9 M" e S/ zselect last_insert_id() into pid;
; x5 N7 N6 g( Z g2 J" w) t//last_insert_id()是一个函数,最后一次插入的id号
* o0 g1 L1 z$ F1 j) y( n" qend $$4 p$ w9 {3 N, j- d
1 V A, y2 @1 z- U: H: n
21.使用SimplejdbcTemplate和泛型技术简化代码
+ O6 n x5 G, \% A(1).
- P8 [# N4 u4 d) e/ q% {3 spublic class SimpleJdbcTemplateTest{
# B E" C8 r5 U5 wstatic SimpleJdbcTemplate simple = newSimpleJdbcTemplate(JdbcUtils.getDataSource());
- b0 [! ~" {: |% ?5 jstatic <T> T find(String nameClass<T> clazz){! [/ j' u7 v; |" p4 K
String sql = "selectid,name,money,birthday from user where name=? and money=?";
; }: k' ?& f1 S( ]: _3 lUser user =
0 X& a4 j" H8 Vsimple.queryForObject(sql,ParameterizedBeanPropertyRowMapper.newInstance(User.class),name,100f);
6 ]1 Z" H; V9 _; `' l/ b* b0 W}//使用了可变参数功能,没有使用了参数数组,参数Map;使用泛型,将查询的类型也当做是参数传递过来.
- H( s0 q% o+ Z6 w* S * F; U* X! i; h3 X3 Z8 i4 M. J
}" j2 n8 q+ c2 |7 a
; W0 r7 w: M# p! k& B+ X
(2).它的内部也是包装了NamedParameterJdbcOperations类,当将对象可变参数变成数组后,剩下的工作都交给NamedParameterJdbcOperations类做.
2 y+ [1 r6 n' z, H; n" N4 ~" _. jsimple.getNamedParameterJdbcOperations()获取NamedParameterJdbcOperations对象
- Z2 N( @# N; p4 V- h+ ^# Q9 }' `simple.getJdbcOperations()获取JdbcTemplate对象# V0 ~1 y; Z. `8 a
( v* {3 F F r+ }. Y) }7 ~" q
22.使用策略模式对模板方法设计模式进行改进
, Y+ A$ { S% x(1).对于不同的查询语句,返回的结果集可能不同,只要一个name,但是把所有的信息都查询出来了,这就要求不同的映射结果.
" f/ E" ~4 z. N- @+ Qpublic StringfindUserName(int id){
& ~# h- |6 e: u) d8 S. j8 s HStringsql="select name from user where id=?";
" I: V* K/ N3 Z6 n3 p4 MObject[]args=newObject[]{id};
" B) R, |& ?& S! M' Y" G}( v" k: o3 X" r/ x
protected ObjectrowMapper(ResultSet rs){//从新覆盖rowMapper方法+ a* i: G) ~) r4 S+ d
returnrs.getString("name");
0 Y4 `2 s0 d C4 l}
8 W3 h. U3 O0 j( R# U2 {这种方式可能导致有多少条不同的查询语句,就需要覆盖多少次rowMapper方法.
1 C) {3 C4 K, w% c; {; o) F4 M2 q
- [: F# m# S, ~8 f& |(2).java中是不允许传递方法的,但是可以传递一个类,接口# @3 G" R# W& [& r; Y$ y3 D8 k& D
根据不同的sql中的内容,查询的列不同,如:
* P% X5 p) u w- F1 P: fselect name fromuser:可以得到name一列
: g. ]0 B. D9 ~ _6 ~% M7 S, aselect id,namefrom user:可以得到id,name这两列
) A8 {9 s: `6 ?! Nselectid,name,money from user:可以得到id,name,money这三列.. i) K& S! b5 o
1 ^: k- A" G7 p- g8 S. G q+ J
public classMyDaoTemplate{( d1 C8 }. o1 S E
public Objectfind(String sql,Object[]args,RowMapper rowMapper){
6 Y( n% m) u6 p4 @obj =rowMapper.mapRow(rs);//映射的过程由一个接口去做
+ R3 d z R% A$ J- r2 T2 i}9 H8 v" h9 V/ n. Y" G
}
% q4 k4 \8 y: O$ q+ x# u6 a9 h & T* n! t- e2 ]+ s# L8 |$ a
public interfaceRowMapper{//定义一个行映射器接口
+ s- Y0 {6 F0 t, Qpublic ObjectmapRow(ResultSet rs);, s- p `: C! d5 c6 B8 F& k
}
$ _) K/ N3 i! @
2 K/ ~; V* P( [! A6 Apublic classUserDaoImpl2{
4 R: F" R8 E! ^8 rMyDaoTemplate template= new MyDaoTemplate();' ?2 n6 K6 }) j( z$ v9 ~
public UserfindUser(String loginName,String password){: {7 ], x$ V: ?$ C8 U+ t; }
Stringsql="select id,name,money,birthday from user where name=?";
" F1 t7 a+ _1 O4 hObject[] args =new Object[]{loginName};0 i- O7 e+ p# B6 M: l+ l) E: d
Object user =this.template.find(sql,args,new UserRowMapper());7 I* F3 u9 y: _0 q0 p) e8 ^
retrun (User)user;' V. R) S5 V3 b, F% v0 I
}: `, J% S/ @* q Q" u
}
8 C3 }' |, ?! n+ Z" y8 l- JclassUserRowMapper implements RowMapper{6 w5 k2 N4 o* Y9 u0 Z0 q+ i: r
public ObjectmapRow(ResultSet rs){//行映射器 V/ P: U, W0 O# r% _
User user = newUser();
, g# j3 F! F8 t j) C8 Muser.setId(rs.getInt("id"));
; I& t- r- @! o; guser.setName(rs.getString("name"));5 B* _* r" t, c: B) i
user.setMoney(rs.getFloat("money"));, L7 `- F0 I+ v! {! D- f
return user;, ] b2 T6 ~" Y5 o+ D7 N$ o
}
2 G$ s0 N+ O" S8 ^ F+ x3 b# @}
7 J/ Q. D- z; T3 ]' d8 S//当需要不同的查询结果集,只需实现RowMapper接口就行了(可以使用匿名内部方式实现)
% w: Z9 `1 s* a( Y2 R # H8 J& W. r+ M) e+ A2 _
(3).这是一种策略模式,根据不同的功能,调用不同的方法(策略),实现类组合的方式(在UserDaoImpl2类中定义一个MyDaoTemplate类)实现的,模板模式是根据继承的方式实现的.- c# G7 @+ w7 R7 N8 e
1 R! ^ i$ ?" x# K2 r: ^23.使用模板方法设计模式处理DAO中的查询方法
+ W7 v; g8 |6 _* ?3 hpublc abstractclass AbstractDao{& H l3 w) k/ f: m
public Object find(String sql,Object[]args){//相同的部分在父类中实现
3 W( E G# b$ t" _, {Connectionconn=null;
; ?9 J, |4 G$ F) uPreparedStatementps=null;3 P) Z. m. ~; u& M7 Y8 b5 W
ResultSet rs=null;
* A' \* R) s* o' [% [8 A3 @, econn=JdbcUtils.getConnection();
% \+ }- \4 u. |! ~ps=conn.prepareStatement(sql);4 H+ ~0 r" V6 d/ N' _. o
for(inti=0;i<args.length;i++){
' H/ K, k s/ Z# c) L ps.setObject(i+1,args[i]);) U8 k- \6 U! a( b
}2 u7 |& s# z! \2 n
rs=ps.executQuery();
( @, c. b# e/ k: C) n% C; A) `Object obj-null; F& \- t* E( v. o ^; `9 O2 C
while(rs.next()){1 e! t5 w) y! w% P
obj=rowMapper(rs);' Q' ], ~& c: \- D
}
4 ~+ {% e# g! z8 {return obj;
% _; E' S. s2 k" I9 l6 l/ r# q8 Q}2 O$ o: ~+ R' Q+ l. `% _. R3 N; U- O
abstract protectedObject rowMapper(ResultSet rs);//父类中不知道具体的查询结果集.放到子类实现该方法.0 g! C6 b# t* M8 S
}& E. k7 M3 ], ~
8 {- Q7 H% ?4 ~public classUserDaoImpl extends AbstractDao{ o; s- x+ `* R! R, G0 K4 D
public UserfindUser(String loginName,String password){//不变的部分放到子类实现.
/ k1 e4 W8 Z4 z( Q: f9 sStringsql="select id,name,money,birthday from user where name=?";
" C! q+ ?/ b, b$ { J& P& A0 wObject[] args =new Object[]{loginName};
# s H4 V3 ^1 P, e! u; J2 zObject user = super.find(sql,args);6 f. P& [1 z* U1 n v
return (User)user;
* }6 X \/ s* K' T& r}
1 u m2 b6 p+ f9 r+ a@Override
# v2 }! W* F2 l2 {+ D' R5 a8 V6 hprotected ObjectrowMapper(ResultSet rs){//在子类中知道查询结果有几列' F1 m5 y4 s2 s9 N# o
User user=newUser();6 h* O t! @7 j
user.setId(rs.getInt("id"));9 Z% h7 O0 d5 `( R
user.setName(rs.getString("name"));6 K7 b. s. Z0 p Z8 z c
user.setMoney(rs.getFloat("money"));* D9 u3 y' t/ [, `7 G/ [3 `& r
user.setBirthday(rs.getDate("birthday"));
# ]" \/ D2 P) m# R' Z9 m2 Qreturn user;& u" C+ M/ M! R7 F. A) x$ r
}: b* m; w6 {: ?; ?7 O6 @0 n
. y2 }& D( d# h( }2 D# U& l
}
) s b$ d1 t6 P2 K5 [
/ _8 C7 o4 x/ h* l' q* E假设现在有一个账户AccountDao
0 }3 [. z* ? a& h4 x( h2 _+ o/ I7 ?public classAccountDaoImpl extends AbstractDao{
/ [, \; p1 B8 A4 Xpublic UserfindAccount(int id){//不变的部分放到子类实现.- R9 n8 ^9 l/ w
Stringsql="select id,name,money from account where id=?";
) o+ L" I$ W3 d% t4 H$ S- q4 CObject[] args =new Object[]{id}; D; ]5 K1 Y( q5 [6 j' y4 s3 a; c! z8 p
Object user =super.find(sql,args);/ |: A2 \: w; Z/ G" M! m
return(Account)account;
* h$ j6 F& I$ X* g V+ d C1 g: v}# b. T0 }* `' G) c$ x8 l
! ~( b9 O# D' J& ~@Override
, W7 d& {1 J* j& K) Z1 n" X r+ @/ lprotected ObjectrowMapper(ResultSet rs){//在子类中知道查询结果有几列
- k& S {9 B. d7 K5 P+ S( R* T7 zAccountaccount=new Account();" K9 z3 D8 Y8 K& P6 f. R. ^# H
account.setId(rs.getInt("id"));
1 F R. ~. l( W3 B( l6 Z' T% {' [account.setName(rs.getString("name"));% A( T6 Y/ m" t( ]
account.setMoney(rs.getFloat("money"));
_; J% g- I0 D7 y/ g" Kreturn account;( e$ h% _% W" D+ p$ Z, R
}9 }3 D' g1 b# L
" ?' d, y s$ Y% K4 L9 \}
e. `$ }+ {" q& q
5 }% i/ ?5 _- S" T, C9 ?public classAccount{
' E+ m; m: F9 ~) s2 Z# Nprivate int id;7 C- n" G* j7 l" k/ D
private Stringname;
$ \4 i7 u. g7 V) _private floatmoney;
: O8 G, m8 O; j//get/set方法省略8 J6 s8 C8 g9 C$ Y2 J
}! M* g% m5 r) c1 Q( l( D; I f
5 d X5 M. r- a4 A ], i- d" q) a0 H
模板模式,相同的步骤放到父类中,不同的步骤放到子类中设计,service方法,doGet(),doPost()方法,首先调用service方法.service会根据method参数的值来调用doGet(),doPost()方法.6 T" n6 ]2 h( a* S
~& h+ g& s# _0 ] Q
24.使用支持命名参数的JdbcTemplate1 ~8 f. \: m: u6 Z9 B# i- W$ P
(1).Spring的NamedParameterJdbcTemplate. e d7 o6 F F2 M# Z
第一:NamedParameterJdbcTemplate内部包含了一个JdbcTemplate,所以JdbcTemplate能做的事情NamedParameterJdbcTemplate都能干,NamedParameterJdbcTemplate相对于JdbcTemplate主要增加了参数可以命名的功能
9 M+ V2 |9 F2 ]2 V, t- e5 I* O+ ~第二:public Object queryForObject(String sql,MapparamMap,RowMapper rowMapper)
x! J% ?6 j' |$ k. ~第三:public Object queryForObject(Stringsql,SqlParameterSoruce paramSource,RowMapper rowMapper)% {* c) G: {: o6 \1 O
SqlParameterSource的两个主要实现MapSqlParameterSource和BeanPropertySqlParameterSource
. ^& B6 B/ a2 c第四:public int update(String sql,SqlParameterSourceparamSource,KeyHolder generatedKeyHolder)保存数据获得主键, ^3 t, }& H/ L9 K
; u6 i5 R6 K+ T, I% g(2).在传递参数时,需要将参数Object[]args与?占位符的位置对应好,如果对应错了,就会出现问题,这时,我们就可以给占位符起个别名
2 r, V, {+ J7 a6 |8 a3 n/ U SstaticNamedParameterJdbcTemplate named = new NamedParameterJdbcTemplate();! I2 g7 n8 w# }* D$ A" E
Stringsql="select id from user where name=:n and money>:m andid<:id";
+ h5 ^% x+ k; VMap params=newHashMap();//使用Map存放参数,而不是数组了
- G, g* s8 P+ z* D/ P6 Aparams.put("n",user.getName());8 T2 i* P' p# {& [: z- p
params.put("m",user.getMoney());
$ n) N9 D3 {1 p0 wparams.put("id",user.getId());
" C; a, F3 k- t, V/ D/*Object[]args=new Object[]{user.getName(),user.getMoney(),user.getId()};*/ D2 k) n* }( V
Object u =named.queryForObject(sql,params,new BeanPropertyRowMapper(),User.class));
% I6 _) r) q S- s8 H& l//注意sql的书写,将占位符?替换了,注意替换的规则.NamedParameterJdbcTemplate只干了一件事,就是将占位符?替换成变量名,将参数命名话后,之后的操作都会交给JdbcTemplate处理. j9 b' `" I& X9 h
9 y# u/ V L4 W/ @3 R
(3).为什么要使用命名参数:
! r A6 a7 S; T$ m' ^# ]SqlParameterSourceps = new BeanPropertySqlParameterSource(user);//关于user的bean参数源
|- m& U, b. o6 @0 f* _/ C' L! l$ z4 FStringsql="select id from user where name=:name and money>:money andid<:id";7 A9 {3 r5 q# @6 e2 p8 M
Object u =named.queryForObject(sql,ps,new BeanPropertyRowMapper(),User.class));7 T- J, K/ g% v/ v
这时参数就存放在user参数源中,参数名必须和user的属性名一样,将参数封装成一个类(参数源),符合面向对象设计思想1 J+ i$ e" r: h$ @0 A( M
5 O6 Y, N* x+ S( m$ J z(4).保存数据,拿到记录的主键.当主键是符合类型(就是多列组成),也可能是String类型的.
/ o6 N" G: P" v& a* ^static voidaddUser(User user){
9 N& H6 n9 H+ p9 FString sql ="insert into user(name,birthday,money) value(:name,:birthday,:money);
# E f0 f4 _* v3 S) d9 \5 N" C) iSqlParameterSourceps = new BeanPropertySqlParameterSource(user);//关于user的bean参数源' y" b2 P1 S" s' w
KeyHolderkeyHolder = new GeneratedKeyHolder(); w$ ]- d( P. A0 c
named.update(sql,ps,keyHolder);
( H$ E7 q6 B: G7 F& a; v//插入的记录的主键放到keyHoler中
: `) v/ o3 I- r0 |1 [7 v9 p3 B' Jint id =keyHolder.getKey().inValue();
1 d3 X- _! C$ j3 g0 Buser.setId(id);* G7 z( l) }2 T; h$ r' Y& M
Map map =keyHolder.getKeys();//主键由多列组成的时候
. h; ^9 p6 D# k' w- q}//重点
' e9 N1 y8 e- o 8 Q# F- I, d" l5 C* ]! f; Y- [
9 f: u+ S! N2 c- u' W9 r7 }2 O
25.事务的保存点处理% {* k9 o( f% \" \
(1).当事务进行回滚时,不是全部进行回滚,有时只想回滚一部分的操作,' |* C; g( M* T
: t8 c x9 A9 k) p# M, Y1 Z
(2).Savepoint sp=null;; c2 B" _) ~" K4 l. u: z/ g6 `
sp=conn.setSavepoint();//设置保存点
9 J4 _: ?7 ~, V* z/ ~- gif(conn!=null&&sp!=null){
4 t6 O! d" S. {& dconn.rollback(sp);//将保存点当做参数,只回滚到保存点
# Y8 e. E7 w, C# O, A}
: U* ~+ G5 l, C5 x26.事务的概念与JDBC事务处理! L' r, P: a' L8 p/ E0 T5 B
(1).事务的特性:(ACID)
5 o# ^, ^6 q, H, H [: k原子性(atomicity):组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分
0 W- E) r, v) q5 _7 L: `7 g一致性(consistency):在事务处理执行前后,数据库是一致的(数据库数据完整性约束)
" B5 l w7 e( l1 R+ j隔离性(isolcation):一个事务处理对另一个事务处理的影响持久性(durability):事务处理的效果能够被永久保存下来
9 t+ A" T. D: E; }9 B& u
( v$ [& K; J/ G8 {2 Q W(2).connection.setAutoCommit(false)//打开事务8 V S. |4 R5 K2 x8 V8 z$ |2 q
connection.commit();//提交事务
" z6 [8 Y7 T8 @0 e* r2 Uconnection.rollback();//回滚事务
. A9 V! A: U- X! R
2 L; W1 M' @4 d0 |! B+ J; C(3).查看数据库表的引擎是否支持事务的操作
& Q( u$ \) I2 o! a- x
6 q3 y V" s/ J: Q- ?$ h7 x" }27.事务的隔离级别
$ Z/ u$ u- ~2 W7 C(1).当两个事务同时去操作同一个数据源,这就是隔离性
4 Y* T# k: x) H, k% b% `
8 p9 j. D9 U* X) X, \7 q6 ^* W(2).设置隔离级别:connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);7 B& T7 g% }( \1 ^; }
隔离级别:
# n5 H$ u! X' W' o, w* l4 E6 ]读未提交:可能出现脏读,不可重复度,幻度4 S$ [, ~. L& [
读已提交:不可能脏读,可能出现不可重复读,幻读
3 E' c. w3 H: }可重复读:不可能脏读,不可重复读,可能出现幻读
% n5 v) H1 W4 I4 U( p可串行化:不可能脏读,不可重复读,幻读
3 p- F1 s, n4 D* E ! J6 b. T0 [0 n6 q
级别逐渐提高.当级别越高的时候,需要的资源越多,对并发的操作有影响.
% T( h- g# J6 L6 G; ]脏读:别人的数据没有提交,我就读到了.% m/ I; B. I; A) V% T, @
不可重复读:第一次读和第二次读的结果不同- q! @8 s6 s( Z. {, p; n/ L; ~3 L3 k( p
幻读:当我们正在查询id>10的记录,这时有另外一个事务增加一条正好id>10的记录.+ }9 G6 j" _- `
1 q" s3 M$ V8 X( [$ s! p0 ?% g
(3).隔离级别的缺省值和数据库相关.不同数据库拥有的隔离级别的个数也是不同的. X U6 H x% {( X' U3 U
4 S( q+ j% x7 t28.数据库的元数据信息
0 G( W) H6 K+ g$ \(1).可以得到数据库的相关信息,是否支持事务,数据库的名称,版本号,隔离级别等信息.
8 u2 r8 x8 ] V3 T( L3 QConnectionconn=JdbcUtils.getConnection();2 N; r5 V! X) Q/ o! H) q2 H
DatabaseMetaDatadbmd =conn.getMetaData()//返回数据的元信息
" Z) t2 ^. Q% m6 r2 v9 n1 ^dbmd.getDatabaseProductName();//得到数据库的名称$ @6 Y8 g; D0 n( e
- `2 n; T3 {( o+ m6 W; z! F, F+ M
(2).hibernate支持各种数据库,所以它肯定需要知道所有数据库的相关信息.
# e0 N3 K* e& u4 W/ O, ? 2 N) _5 y, ]+ C; }; W
29.通过代理模式来保持用户关闭连接的习惯2 b$ A! M6 c5 _" M$ _/ W; M
(1).用户可能不使用JdbcUtils.free()方法释放连接,而是按照conn.close()方法释放连接,这时我们创建的连接池就没有用了,连接数也就减少了,所以我们希望用户始终使用我们自己编写的方法进行释放连接6 ?+ l* K% d1 [- Z0 a% @+ W, n, k, r
' R* u' X7 W# [4 m7 g6 A/ H2 z(2).通过close()方法,还是能够将连接方法连接池中,所以我们要拦截close()方法,组合优先继承
& [4 q7 N3 k% V% _( f# F6 G. _
, V( \' ~; e! F& X- H' ^: E(3).public classMyConnection implements Connection{
3 c4 S5 L& `1 m0 g: H& m+ Cprivate ConnectionrealConnection;//使用组合方式3 g8 T. ^3 T$ t0 h% }
privateMyDataSource dataSource;* N5 `, |' r- g4 A- i
MyConnection(ConnectionrealConnection,MyDataSource dataSource){0 b) o) n2 I5 n# J) Z
this.realConnection=connection;, G& t5 o& F- O6 Y6 m Q
this.dataSource=dataSource;" n% L! ?% h! f5 q. ~$ j" n
}
) w) a" ?) |$ r+ k% e//实现Connection的所有方法- W. W' W# d8 ]5 \5 f! C; @8 n
public voidclose(){//这里就可以实现Connection的close()方法了
6 R B6 t; N! N' w3 D4 T* ]7 othis.dataSource.connectionPool.addLast(this);//把自己重新放到池中.
5 ~' X2 J+ L5 u! n, ?; n- _}. E6 L* W; J4 i
1 Y4 M: r# G/ v$ T* t
(4).此时代码中的Connection处都使用MyConnection,这就是面向接口编程的好处.同时类MyConnection的访问权限是包访问权限,不准用户访问的,但是允许在dataSource中访问.% D! D3 b6 R1 U( U( ^& y& F3 \! E
, y! {5 k! D( q/ x; N( b2 {(5).DataSource类和MyConnection类之间相互调用.8 S. P8 ]" s6 G% r! Y
+ j2 P! V; y7 x- `4 o# ~* Q(6).MyConnection是个代理,是Connection的代理模式,实现Connection的close()方法.这就是静态代理设计模式,在MyConnection类中定义一个Connection,这是组合方式,也可以使用集成方式实现代理.
4 k) D. Q I+ h/ K3 j/ c7 W & q1 H; E3 y- ~' k9 H) ?3 a) g
30.完成数据库的CRUD操作
+ ]# N8 n" u$ d1 p" m# }) K(1).书写SQL语句时应该注意的问题:select * from user,就是不应该写星号,最好书写列名,得到数据,可以根据列的索引号,也可以根据列名,建议使用根据列名取数据.
9 C$ z( a' O( A1 f1 `" j7 M
0 B; L5 |; n% g3 M- c! U% L2 i31.用jdbc访问大段文本数据" K# O! ~6 {3 n; Q
(1).数据库中的varchar最大是255个字节,所以就需要使用大文本类型TEXT.只有纯文本格式才能放进去.
, J; O/ ^ v( I$ x& P! N 8 B c' y# D! U0 E ]. E& O/ V
(2).2 [4 f H z: v+ j8 \/ W( A( s0 h
Stringsql="insert into clob_test(big_text) value(?)";
) g* r+ ~0 l% Lps=conn.prepareState(sql);
+ o( P# G# X5 t8 x5 r. `File file=newFile("src/cn/itcast/jdbc/JdbcUtils.java");
$ E" v9 [- r: e- @3 Y6 ~Reader reader =new BufferedReader(new FileReader(file));//可能含有IO的异常4 E: Q1 }/ P5 o- Z
ps.setAsciiStream(1,reader,(int)file.length());//需要一个Reader,字符流的长度Length,这个方法只能用于文本只含有Ascii码的
; ~: w% D% C ^4 @1 dinti=ps.executeUpdate(sql);/ ~3 l8 `% ^& L" C
reader.close();
0 d6 M0 P; H! g* y7 D- n6 P % I: {% ^" E6 Q8 `+ j0 z2 n) s
rs=st.executeQuery("selectbig_text from clob_test");//读取文本类型数据; H. O6 j0 I- J
while(rs.net()){4 |' a0 Q) v+ p& @
Clob clob =rs.getClob(1);+ `' w# k" y8 y+ T2 z
Reader reader =clob.getCharacterStream();9 p" \6 e" N) r4 z( l8 h
File file=newFile("JdbUtils_bak.java");+ x/ h* U& V- B# v, f G5 r( R
Writer writer=newBufferedWriter(new FileWriter(file));0 T E+ W9 k' C! c, j% X* ~8 A
char[]buff=newchar[1024];
/ i; S% m) S0 T/ p% Cfor(inti=0;(i=reader.read(buff))>0;){2 q, x8 d; E8 R
writer.write(buff,0,i);
}( ^; N. c& {" n3 p}9 w. D; c8 {6 A! H6 b; r
}
9 F+ m5 o3 b% ]writer.close();( Q7 v7 l3 Z0 v2 Q3 l9 U( \+ h
reader.close(); |
|