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