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