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