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