浅谈JDBC的升级之路

JDBC:

虽然由于快节奏的开发,编程速度的追求,越爱越多的MVC框架出现,比如持久层的hibernate,
mybatis等等,他们对Dao层的支持都很强大,既快速,又简便。但是他们的底层同样是使用了JDBC,
为了追求高速简便,我们可以不使用JDBC,但一定要了解JDBC。了解JDBC也有助于学习其他持久层框架。

java和数据库交互需要中间程序作为中转。在很早以前,数据库厂商还没有一套统一的API作为
java语言和数据库的接口,开发程序是一件很头疼的事。对不同的数据库需要写不同的程序来作为交互。
     java访问-----oracle程序-----oracle数据库
     java访问------mysql程序------mysql数据库
     java访问-------db2程序--------db2数据库
那到底什么是JDBC呢:
    JDBC是代表Java数据库连接,这对java编程语言和广泛的数据库之间独立于数据库的连接标准的Java API
     有了JDBC开发就统一了很多。
     java访问-----JDBC程序-----oracle数据库
     java访问------JDBC 程序------mysql数据库
     java访问------JDBC 程序--------db2数据库

以下简介JDBC针对oracl和mysql数据库。
oracle提供的jdbc接口的实现类-------ojdbc5.jar------jdk5.0  ojdbc6.jar-----jdk6.0
mysql提供的jdbc接口实现类--------mysql-connector-java-6.0.6
如果要开发JDBC程序,必然少不了这些jar包。下载可以去官网

JDBC常用API简介:
java.sql.Connection:代表一个数据库连接;
java.sql.Statement:发送sql语句1至数据库;(发送sql的工具)
DriverManager:(类) 管理多个数据库驱动类
java.sql.ResultSet:结果集,存放查询语句执行后返回的数据结果

下图1.0为JDBC访问数据库流程

                                             
                                                                           图1.0

JDBC开发步骤
1: 加载驱动
如果是oracle数据库:
将oracle ojdbc.jar 复制到项目里,点击项目,右键build path --add to buidpath 
Class.forName("oracle.jdbc.OracleDriver");
如果是mysql数据库:
将mysql  mysql-connector-java-6.0.6-bin 复制到项目里,build path --add to buidpath 
Class.forName("com.mysql.jdbc.Driver") ;   
2:连接数据库
     user:root----数据库用户名
     password:root------数据库密码
     url: oracle ---- 协议  : 子协议  :thin:@ip:端口:SID(SID数据库的实例名)。
     eg: String url = "jdbc:oracle:thin:@localhost:1521:XE";
     url:mysql ------   协议:子协议://ip:端口: 数据库名
   eg:String   url   =   "jdbc:mysql://localhost:3306/zdx" ;

3:准备sql
     sql字符串中不能有分号;
4:Statement将sql发送至数据库
      int i=  executeUpdate(sql); //返回受影响的行数
5:如果是查询语句,返回结果集,处理ResultSet。
6:关闭连接,释放资源,按照先打开后关闭的原则。

以上6步骤时开发JDBC程序的标准步骤。

ResultSet结果集简介及使用方法:
     ResultSet结果集:存放查询结果数据。
     ResultSet rs = Statement. executeQuery();
     rs指针初始位置在第一行数据之前
     boolean rs.next(): ,
     将rs的指针向下移动一位,当前行无数据返回false,有数据返回true,并获得查询数据。
     提供一组getXXX(int列序号或String列名)方法用于取得数据

以上是JDBC简介,下面是JDBC例子,例子才是王道。
JDBC1.0版本------是1.0版本。后面还会有更优秀的版本娓娓道来!

JDBC_ResultSet.java
public   class  JDBC_ResultSet {
        public   static   void  main(String[]  args throws  ClassNotFoundException, SQLException {
              //1:加载驱动
              //Class.forName("oracle.jdbc.OracleDriver");
             Class.forName( "com.mysql.jdbc.Driver" );
              //2:获得数据库连接
             String  user  =  "root" ;
             String  password  =  "root" ;
              //String url = "jdbc:oracle:thin:@localhost:1521:XE";
             String  url  =  "jdbc:mysql://localhost:3306/zdx?serverTimezone=UTC" ;
             Connection  conn  = DriverManager.getConnection( url , user , password );
              //3:准备sql
             String  sql  =  "select * from student" ;
              //4:创建Statement,发送sql
             Statement  stm  =  conn .createStatement(); //获的statement对象
              //5:如果是查询,处理结果集
             ResultSet  rs  =  stm .executeQuery( sql );
              while ( rs .next()){
                     int   id  =  rs .getInt(1);
                    String  name  =  rs .getString(2);
                     int   age  =  rs .getInt(3);
                    String  phone  =  rs .getString(4);
                    System. out .println( id + "---" + name + "---" + age + "---" + phone );
             }
              //6:关闭连接,释放资源,原则,先打开后关闭
              stm .close();
              conn .close();
             
       }
}


到了此处,我们的JDBC1.0已经写完了,但是为什么说是1.0呢,因为这个版本有一个很大的漏洞。
没错,那就是——依赖注入问题。
=============================华丽丽的分割线==============================
下面我们可以看这个例子

JDBC_Statement.java

public   class  JDBC_Statement {
        public   static   void  main(String[]  args throws  Exception {
             
             Scanner  sc  =  new  Scanner(System. in );
             System. out .println( "请输入卡号" );
             String  card_id  =  sc .nextLine();
             System. out .println( "请输入密码" );
             String  pwd  =  sc .nextLine();
              //1:加载驱动
             Class.forName( "com.mysql.jdbc.Driver" );
              //2:获得数据库连接
             String  user  =  "root" ;
             String  password  =  "root" ;
             String  url  =  "jdbc:mysql://localhost:3306/zdx?serverTimezone=UTC" ;
             Connection  conn  = DriverManager.getConnection( url , user , password );
              //3:准备sql
             String  sql  =  "select * from account where card_id =" + card_id
                                 + " and password = '" + pwd + "'" ;
             System. out .println( sql );
              //4:创建Statement,发送sql
             Statement  stm  =  conn .createStatement(); //获的statement对象
              //5:如果是查询,处理结果集
             ResultSet  rs  =  stm .executeQuery( sql );
              if ( rs .next()){
                    System. out .println( "可以取钱了" );
             } else {
                    System. out .println( "密码账户不正确" );
             }
              //6:关闭连接,释放资源,原则,先打开后关闭
              stm .close();
              conn .close();
       }
}


这是一个银行取钱的例子,如代码所写。
数据库有这样一张account表,数据和字段如下:

现在在控制台输入卡号密码,卡号密码正确即可取钱,
错误即提示账户不正确。

现在控制台输入正确卡号密码



然后,我输入错误的卡号密码竟然也可以取钱!输入错误的卡号 “123 or 1=1 -- ”密码 "zxc"结果如下:



可见依然能访问数据库,这对于银行可是致命性错误,密码错误还可以取钱。
这就是依赖注入引起的著名错误。
依靠人为输入破坏sql结构.
select * from account where id = 1001 or 1=1 -- and password = 'xxx'
这条sql里,其中--是sql里的注释 or 1=1 永远为真并且还是or连接,
所以这条sql只执行到 or 1=1 ,1=1又是恒等。所以跳过了密码。

为了解决这个问题,我们就要说PreparedStatement!

PreparedStatement简介及使用

PreparedStatement构建动态SQL,通过PreparedStatement执行SQL语句,解决注入攻击。
PreparedStatement是Statement的子接口。
通过连接创建PreparedStatement,创建时将SQL语句中发生变化的部分用占位符“?“ 代替。
功能:和statement一样,发送sql语句。 
          但是执行多个同构sql效率高。同构sql能省去①②③步骤。
使用步骤:
     1.创建pstm
     String sql = "select * from account where card_id = ? and password = ?"
     PreparedStateement pstm = conn.prepareStatement(sql);
          ①验证权限
          ②验证语法
          ③将sql转换内部指令
2.绑定参数
     pstm.setInt(1,值);
     pstm.setString(2,值);
3.发送绑定参数至DB数据库
     pstm.executeUpdate();//曾删改
     pstm.executedQuery();//查询
          ④执行内部指令操作数据库
mysql内部执行sql步骤:
          ①验证权限
          ②验证语法
          ③将sql转换内部指令
          ④执行内部指令操作数据库
使用PareparedStatement解决注入攻击问题后的代码如下:
JDBC2.0版本 是2.0版本>_< 。后面还会有更优秀的版本娓娓道来!
JDBC_PreparedStatement.java

public   class  JDBC_PreparedStatement {
        public   static   void  main(String[]  args throws  Exception {
             
             Scanner  sc  =  new  Scanner(System. in );
             System. out .println( "请输入卡号" );
             String  card_id  =  sc .nextLine();
             System. out .println( "请输入密码" );
             String  pwd  =  sc .nextLine();
              //1:加载驱动
              //Class.forName("com.mysql.jdbc.Driver");
              //2:获得数据库连接
             String  user  =  "root" ;
             String  password  =  "root" ;
             String  url  =  "jdbc:mysql://localhost:3306/zdx?serverTimezone=UTC" ;
             Connection  conn  = DriverManager.getConnection( url , user , password );
              //3:准备sql
             String  sql  =  "select * from account where card_id =? and password =?" ;
             PreparedStatement  pstm  =  conn .prepareStatement( sql );
              pstm .setInt(1, Integer.valueOf( card_id ));
              pstm .setString(2,  pwd );
              //5:如果是查询,处理结果集
             ResultSet  rs  =  pstm .executeQuery();
              if ( rs .next()){
                    System. out .println( "可以取钱了" );
             } else {
                    System. out .println( "密码账户不正确" );
             }
              //6:关闭连接,释放资源,原则,先打开后关闭
              rs .close();
              pstm .close();
              conn .close();
       }
}
现在用PreparedStatement程序执行,输入卡号,密码


出现异常,不会再出现密码错误却取钱的问题了!!!

总结对比一下 Statement 和PreparedStatement



Statement
PreparedStatement
关系
父接口
子接口
安全
存在注入隐患
解决sql注入
效率
执行异构sql快
执行同构sql快

至此JDBC2.0结束。JDBC基本内容结束,以下是JDBC进阶内容。和优化版JDBC例子。

=================================华丽丽的分割线===========================

封装数据访问对象
1:通过分析总结,所有对数据库表的操作都可以总结为通过JDBC对表的增删改查,为了减少冗余代码,
     使得每次操作表时,不必都写JDBC程序,所以将对一张表的所有数据访功能,封装在数据访问对象
     (Data Access Object)中,方便调用。
2:为了方便数据传输,往往会将java程序中所有相关操作的零散字段值,封装成一个实体对象--entity。
     实体封装原则:
     表----实体类
     字段---属性
     实现序列化
     提供set,get方法。

以下代码就是利用Dao数据访问对象写出的JDBC程序,利用封装简化了JDBC开发步骤。
试想一下,如果,有10个添加,20个删除的请求,还像JDBC1.0,2.0版本那样写的话,要写10遍,20遍大量相似代码,
基本一致的代码。这不仅耗时耗力,也不符合JAVA三大特性。所以,利用Dao数据访问层,将对表的常用操作,
封装成方法,这样再有10个添加或20个删除,我们只需要调用10次,20次这个封装好的添加或删除方法,
而不用再写那么多遍方法,大大简化了开发工作量。
以下是利用Dao思想实现的JDBC3.0是3.0版本后面还有更好的版本娓娓道来!>_<
JDBC_Dao.java
public   class  JDBC_Dao {
        /**
        * 向account表增加一条记录
        */
        public   void  add(Account  account ){
             Connection  conn  =  null ;
             PreparedStatement  pstm  =  null ;
              try  {
                     //1加载驱动
                    Class.forName( "com.mysql.jdbc.Driver" );
                     //2创建连接
                     conn  = DriverManager.getConnection(
                                   "jdbc:mysql://localhost:3306/zdx?serverTimezone=UTC" ,
                                  "root" ,
                                  "root" );
                     //3准备sql
                    String  sql  =  "insert into account values(?,?,?,?)" ;
                     //4创建Statement,发送sql
                     pstm  =  conn .prepareStatement( sql );
                     pstm .setString(1, account .getCardId());
                     pstm .setString(2,  account .getPassword());
                     pstm .setDouble(3,  account .getBalance());
                     pstm .setString(4,  account .getPhone());
                     int   i  =  pstm .executeUpdate();
                     //5如果是查询的话,处理结果集
             }  catch  (Exception  e ) {
                     e .printStackTrace();
             }
              finally {
                     //6释放资源
                     try  {
                            pstm .close();
                            conn .close();
                    }  catch  (SQLException  e ) {
                            e .printStackTrace();
                    }
             }
       }
        /**
        * 向account表查询一条记录
        *  @param  account
        *  @return
        */
        public  Account query(Account  account ){
             Connection  conn  =  null ;
             PreparedStatement  pstm  = null ;
             ResultSet  rs  =  null ;
              try  {
                     //1加载驱动
                    Class.forName( "com.mysql.jdbc.Driver" );
                     //2创建连接
                     conn  = DriverManager.getConnection(
                                   "jdbc:mysql://localhost:3306/zdx?serverTimezone=UTC" ,
                                  "root" ,
                                  "root" );
                     //3准备sql
                    String  sql  =  "select * from account where "
                    +  "card_id = ? and password = ?" ;
                     //4创建Statement发送语句
                     pstm  =  conn .prepareStatement( sql );
                     pstm .setString(1,  account .getCardId());
                     pstm .setString(2,  account .getPassword());
                     rs  =  pstm .executeQuery();
                     //5处理结果集
                     while  ( rs .next()) {
                            account .setCardId( rs .getString( "card_id" ));
                            account .setPassword( rs .getString( "password" ));
                            account .setBalance( rs .getDouble( "balance" ));
                            account .setPhone( rs .getString( "phone" ));
                    }
             }  catch  (Exception  e ) {
                     e .getStackTrace();
             }
              finally {
                     //6释放资源
                     try  {
                            rs .close();
                            pstm .close();
                            conn .close();
                    }  catch  (SQLException  e ) {
                            e .printStackTrace();
                    }
             }
              return   account ;
       }
}



以上代码依旧使用account表,需要再写一个实体对象用来承载数据,一个test类用来调用方法,
,这些最基础的,这里就不赘述了,不清楚的同学就要自行参阅java语言了。
如果我查询A卡,B卡,C卡,三张银行卡信息,按照JDBC2.0要写三个查询方法,现在,只需要把参数传递过去,
调用三次query()方法就好了!
总结:可见利用Dao数据访问层封装JDBC常用方法,可以大大简化方法步骤,不用重复写方法,只需重复调用。
这就是JDBC3.0版本
=============================华丽丽的分割线==============================
但是仔细的读者一定发现了,这里还存在不少缺陷,没错,我们还可以改进它。
在JDBC3.0版本里,可以发现,查询,添加方法,存在大量冗余代码,比如:
      ①同的加载驱动,
      ②相同的创建连接,
      ③相同的释放资源。
在上个版本的代码里我只写了添加查询方法,如果还有删除,修改,查询所有等方法呢,
没错这些方法,也存在相同的创建连接,释放链接。找见了问题,就好解决了。
那么解决的办法还是----封装。
我们可以尝试把1注册驱动,2创建连接,6释放资源,这三个步骤做成工具类-----JDBCutil
这样,我们在Dao层里面的JDBC方法,在遇到1,2,6等步骤时,不用再去写代码,只需调用封装好的工具即可。
没错程序员都是很懒得!
以下是JDBC4.0是4.0版本,后面还有更完善的版本娓娓道来!

public   class   JDBC_Util {
        /**
        *   @return   返回链接
        */
        public   static   Connection getConnection()   throws   Exception {
             Connection   conn   =   null ;
             Class.forName( "com.mysql.jdbc.Driver" );
             conn = DriverManager.getConnection(
                            "jdbc:mysql://localhost:3306/zdx?serverTimezone=UTC" ,
                            "root" ,
                            "root" );
             
              return   conn ;
       }
        /**
        * 释放资源
        */
        public   static   void   release(ResultSet   rs ,
                    Statement   stm ,
                    Connection   conn ){
              try   {
                     if ( rs != null ){ rs .close();}
                     if ( stm != null ){ stm .close();}
                     if ( conn != null ){ conn .close();}
             }   catch   (SQLException   e ) {
                     e .printStackTrace();
             }
       }
}
public   class   JDBC_Dao2 {
        /**
        * 向account表增加一条记录
        */
        public   void   add(Account   account ){
             Connection   conn   =   null ;
             PreparedStatement   pstm   =   null ;
              try   {
                     conn   = JDBC_Util.getConnection();
                     //3准备sql
                    String   sql   =   "insert into account values(?,?,?,?)" ;
                     //4创建Statement,发送sql
                     pstm   =   conn .prepareStatement( sql );
                     pstm .setString(1, account .getCardId());
                     pstm .setString(2,   account .getPassword());
                     pstm .setDouble(3,   account .getBalance());
                     pstm .setString(4,   account .getPhone());
                     int   i   =   pstm .executeUpdate();
                     //5如果是查询的话,处理结果集
             }   catch   (Exception   e ) {
                     e .printStackTrace();
             }
              finally {
                     //6释放资源
                    JDBC_Util.release( null ,   pstm ,   conn );
             }
       }
        /**
        * 向account表查询一条记录
        *   @param   account
        *   @return
        */
        public   Account query(Account   account ){
             Connection   conn   =   null ;
             PreparedStatement   pstm   = null ;
             ResultSet   rs   =   null ;
              try   {
                     conn   = JDBC_Util.getConnection();
                     //3准备sql
                    String   sql   =   "select * from account where "   +   "card_id = ? and password = ?" ;
                     //4创建Statement发送语句
                     pstm   =   conn .prepareStatement( sql );
                     pstm .setString(1,   account .getCardId());
                     pstm .setString(2,   account .getPassword());
                     rs   =   pstm .executeQuery();
                     //5处理结果集
                     while   ( rs .next()) {
                            account .setCardId( rs .getString( "card_id" ));
                            account .setPassword( rs .getString( "password" ));
                            account .setBalance( rs .getDouble( "balance" ));
                            account .setPhone( rs .getString( "phone" ));
                    }
             }   catch   (Exception   e ) {
                     e .getStackTrace();
             }
              finally {
                     //6释放资源
                    JDBC_Util.release( rs ,   pstm ,   conn );
             }
              return   account ;
       }
}

细心地读者会发现在代码里原本创建连接和释放资源的位置都变成了方法调用。
  conn   = JDBC_Util.getConnection();
 JDBC_Util.release( rs ,   pstm ,   conn );
4.0版本通过工具类调用的方式进一步精简了代码,那么4.0版本还有没有缺陷了呢。
=============================华丽丽的分割线=================================
对于JDBC_Util.java来说,还有许多不足。
1:
             conn = DriverManager.getConnection(
                            "jdbc:mysql://localhost:3306/zdx?serverTimezone=UTC" ,
                            "root" ,
                            "root" );
比如创建连接的方式,是在代码里写死的用户名和密码,以及连接url,而java文件运行的时候,会编译成class文件
也就是说,JDBC_Util最终会是JDBC_Util.class文件,那么一旦数据库改变密码,用户名,或改换数据库,
整个文件还需要重新编译执行。对此,我们可以把经常变动的代码放到properties配置文件里去。

2:每次调用获得连接的方法都需要加载驱动,
Class.forName( "com.mysql.jdbc.Driver" );
调用10次则加载10次,大大浪费了JVM内存,其实对于加载驱动只需要加载一次,我们可以尝试把加载驱动放到
静态代码块里。静态代码块在类加载时执行,只执行一次。

properties配置文件简介及使用:
1: InputStream is = new FileInputStream("配置文件路径");
     BufferedReader br = bew BufferedReader(new InputStramReader(is));
     String as = br.readLine();
2:properties 是Map的实现类:
     1:获得配置文件的输出流。
     2:调用load(is);加载配置文件里的信息至Properties对象中。

下面的JDBC5.0版本是对JDBC_Util的改进。采用了静态代码块加读取配置文件的优化方案。
包括test.java,JDBC_Util2.java,JDBC_Dao2.java,properties文件,共四个。
JDBC_Uril2.java
public   class  JDBC_Util2 {
        private   static   final  Properties  prop  =  new  Properties();
        static {
             InputStream  is  =  null ;
              try  {
                     is  = JDBC_Util2. class .getResourceAsStream( "jdbc.properties" );
                     prop .load( is );
                    String  driverName  =  prop .getProperty( "driverName" );
                    Class.forName( driverName );
             }  catch  (Exception  e ) {
                     e .printStackTrace();
             }
       }
        /**
        *  @return  返回链接
        */
        public   static  Connection getConnection()  throws  Exception {
             Connection  conn  =  null ;
             String  user  =  prop .getProperty( "user" );
             String  password  =  prop .getProperty( "password" );
             String  url  =  prop .getProperty( "url" );
              conn  = DriverManager.getConnection( url , user , password );
              return   conn ;
       }
        /**
        * 释放资源
        */
        public   static   void  release(ResultSet  rs ,
                    Statement  stm ,
                    Connection  conn ){
              try  {
                     if ( rs != null ){ rs .close();}
                     if ( stm != null ){ stm .close();}
                     if ( conn != null ){ conn .close();}
             }  catch  (SQLException  e ) {
                     e .printStackTrace();
             }
       }
}

JDBC_Dao2.java

public   class  JDBC_Dao2 {
        /**
        * 向account表增加一条记录
        */
        public   void  add(Account  account ){
             Connection  conn  =  null ;
             PreparedStatement  pstm  =  null ;
              try  {
                     conn  = JDBC_Util2.getConnection();
                     //3准备sql
                    String  sql  =  "insert into account values(?,?,?,?)" ;
                     //4创建Statement,发送sql
                     pstm  =  conn .prepareStatement( sql );
                     pstm .setString(1, account .getCardId());
                     pstm .setString(2,  account .getPassword());
                     pstm .setDouble(3,  account .getBalance());
                     pstm .setString(4,  account .getPhone());
                     int   i  =  pstm .executeUpdate();
                     //5如果是查询的话,处理结果集
             }  catch  (Exception  e ) {
                     e .printStackTrace();
             }
              finally {
                     //6释放资源
                    JDBC_Util2.release( null pstm conn );
             }
       }
        /**
        * 向account表查询一条记录
        *  @param  account
        *  @return
        */
        public  Account query(Account  account ){
             Connection  conn  =  null ;
             PreparedStatement  pstm  = null ;
             ResultSet  rs  =  null ;
              try  {
                     conn  = JDBC_Util2.getConnection();
                     //3准备sql
                    String  sql  =  "select * from account where "
                    +  "card_id = ? and password = ?" ;
                     //4创建Statement发送语句
                     pstm  =  conn .prepareStatement( sql );
                     pstm .setString(1,  account .getCardId());
                     pstm .setString(2,  account .getPassword());
                     rs  =  pstm .executeQuery();
                     //5处理结果集
                     while  ( rs .next()) {
                            account .setCardId( rs .getString( "card_id" ));
                            account .setPassword( rs .getString( "password" ));
                            account .setBalance( rs .getDouble( "balance" ));
                            account .setPhone( rs .getString( "phone" ));
                    }
             }  catch  (Exception  e ) {
                     e .getStackTrace();
             }
              finally {
                     //6释放资源
                    JDBC_Util2.release( rs pstm conn );
             }
              return   account ;
       }
}

jdbc.properties文件

driverName= com.mysql.jdbc.Driver
user= root
password= root
url= jdbc:mysql://localhost:3306/zdx?serverTimezone=UTC

Test_Dao.java
public   class  Test_Dao {
        public   static   void  main(String[]  args ) {
             JDBC_Dao2  jd  =  new  JDBC_Dao2();
             Account  account  =  new  Account( "10004" , "44444" ,99, "12345678900" );
              jd .add( account );
       }
}


      解决了依赖注入,冗余代码,资源浪费等问题之后,JDBC5.0完毕。这个版本可以算得上是较为完善的版本了,但是还有瑕疵。
后面关于JDBC的问题涉及到事务,以及MVC模式,这里由于篇幅问题,无法细细详谈,但我会竟可能详细的写。
请小伙伴自行阅读有关oracle事务一章节,还有有关mvc编程思想书籍。

=================================华丽丽的分割线=========================
JDBC6.0最终版。
数据访问层Dao
业务逻辑层service
我们在Dao层中封装了对表的常用操作,增删改查。
我们在Util里封装了JDBCUtil工具类解决冗余问题。
现在我们有一个银行转账问题:
1.根据卡号,密码,先查询
2.转出账户再余额足够的情况下,减去转出资金。
3转入账户添加转入资金。
对于2,3步骤我们因当把他们看作是一个事务,事务的原子性,一致性,隔离性。要求
要么一起成功,要么一起不成功。当然从现实考虑,也确实应当这样,如果在2,3步骤之间失败了
就回退。
这整个转账逻辑,我们可以称之为业务。所以针对软件的三层结构。我们把这部分称为-----业务层(service)

在service层调用Dao层的方法。
而在service层里的连接对象和Dao层里的连接对象不是同一个对象,这就会造成混乱,打破事务的一致性。
从而导致,转出钱减去了,转入账户没加钱。

Service方法中的事务控制失败。
原因:service控制事务时过的conn和DAO访问数据库使用的conn时两个不同对象
解决:
     保证service和Dao使用同一个conn对象
     1:通过参数传递。
     2:每个线程对象有一个Map属性,可以存储数据
     在service获得conn,将conn放入当前Thread对象,在DAO访问数据库时取时,从Thread取出conn
     ThreadLocal简介及使用:
          操作当前线程对象中的一小块空间。
          1创建ThreadLocal对象 ThreadLocal<Connection> tdl = new ThreadLocal<Connection>();
          2tdl.set(conn);将conn添加到当前线程。
          3tdl.get();获得当前线程中的数据
          4tdl.remove()移除当前线程里的数据
     
话不多说我们来看代码有三个文件 JDBC_Util3.java JDBC_Dao3.java JDBC_Service.java
JDBC_Util3.java
public   class   JDBC_Util3 {
        private   static   final   Properties   prop   =   new   Properties();
        private   static   final   ThreadLocal<Connection>   tdl   =   new   ThreadLocal<Connection>();
       
        static {
             InputStream   is   =   null ;
              try   {
                     is   = JDBC_Util3. class .getResourceAsStream( "jdbc.properties" );
                     prop .load( is );
                    String   driverName   =   prop .getProperty( "driverName" );
                    Class.forName( driverName );
             }   catch   (Exception   e ) {
                     e .printStackTrace();
             }
             
       }
        /**
        *   @return   返回链接
        */
        public   static   Connection getConnection()   throws   Exception {
             Connection   conn   =   null ;
              conn   =   tdl .get(); //获得当前线程连接
              if ( conn   ==   null ){
                     //说明当前线程没有conn
                    String   user   =   prop .getProperty( "user" );
                    String   password   =   prop .getProperty( "password" );
                    String   url   =   prop .getProperty( "url" );
                     conn   = DriverManager.getConnection( url , user , password );
                     //将当 conn存入线程
                     tdl .set( conn );
             }
              return   conn ;
       }
        /**
        * 释放资源
        */
        public   static   void   release(ResultSet   rs ,Statement   stm ,Connection   conn ){
              try   {
                     if ( rs != null ){ rs .close();}
                     if ( stm != null ){ stm .close();}
                     if ( conn != null ){
                            conn .close();
                            tdl .remove(); //移除当前线程对象中的conn
                           }
             }   catch   (SQLException   e ) {
                     e .printStackTrace();
             }
       }
}


JDBC_Dao3.java
public   class  JDBC_Dao3 {
        /**
        * 更新账户
        *  @param  toAcc
        */
        public   void  upDateAccount(Account  toAcc ) {
             Connection  conn  =  null ;
             PreparedStatement  pstm  =  null ;
              try  {
                     conn  = JDBC_Util3.getConnection();
                    String  sql  =  "update account set card_id=?,"
                                 +  "password=?,balance=?,phone=? where card_id=?" ;
                     pstm  =  conn .prepareStatement( sql );
                     pstm .setString(1,  toAcc .getCardId());
                     pstm .setString(2,  toAcc .getPassword());
                     pstm .setDouble(3,  toAcc .getBalance());
                     pstm .setString(4,  toAcc .getPhone());
                     pstm .setString(5,  toAcc .getCardId());
                     int   i   =  pstm .executeUpdate();
             }  catch  (Exception  e ) {
                     e .printStackTrace();
             } finally {
                    JDBC_Util.release( null pstm null );
             }
       }
        /**
        * 查询账户
        *  @param  toCardId
        *  @return
        */
        public  Account queryAccount(Integer  toCardId ) {
             Connection  conn  =  null ;
             Statement  stm  =  null ;
             ResultSet  rs  =  null ;
             Account  acc  =  null ;
              try  {
                     conn  = JDBC_Util3.getConnection();
                     stm  =  conn .createStatement();
                    String  sql  =  "select * from account where card_id = "
                    +  toCardId ;
                    System. out .println( sql );
                     rs  =  stm .executeQuery( sql );
                     while  ( rs .next()) {
                            acc  =  new  Account();
                            acc .setCardId( rs .getString( "card_id" ));
                            acc .setPassword( rs .getString( "password" ));
                            acc .setBalance( rs .getDouble( "balance" ));
                            acc .setPhone( rs .getString( "phone" ));
                    }
             }  catch  (Exception  e ) {
             } finally {
                    JDBC_Util.release( rs stm null );
             }
              return   acc ;
       }
       
}
JDBC_Service.java
public   class  JDBC_Service {
       
        public   void  transfer(Integer  fromCardId ,String  password ,
                    Integer  toCardId ,Double  money ){
             Connection  conn  =  null ;
              try {
                     conn  = JDBC_Util2.getConnection();
                     //事务自动提交关闭
                     conn .setAutoCommit( false );
                    JDBC_Dao3  dao  =  new  JDBC_Dao3();
                     //验证卡号是否存在
                    Account  fromAcc  =  dao .queryAccount( fromCardId );
                     if ( fromAcc  ==  null ){
                            throw   new  RuntimeException( "卡号不存在" );
                    }
                     //验证密码是否正确
                     if ( null == password ||! password .equals( fromAcc .getPassword())){
                            throw   new  RuntimeException( "密码错误" );
                    }
                     //验证余额
                     if ( fromAcc .getBalance()< money ){
                            throw   new  RuntimeException( "余额不足" );
                    }
                     //转出账户更新
                     fromAcc .setBalance( fromAcc .getBalance()- money );
                     dao .upDateAccount( fromAcc );
                    Account  toAcc  =  dao .queryAccount( toCardId );
                     //验证到账卡号
                     if ( toAcc  == null ){
                            throw   new  RuntimeException( "到账卡号不存在" );
                    }
                     //转入账户更新
                     toAcc .setBalance( toAcc .getBalance()+ money );
                     dao .upDateAccount( toAcc );
                     //提交事务
                     conn .commit();
             } catch (Exception  e ){
                     e .printStackTrace();
                     try  {
                            conn .rollback();
                    }  catch  (SQLException  e1 ) {
                            e1 .printStackTrace();
                    }
             } finally {
                    JDBC_Util.release( null null conn );
             }
       }
}


最后main函数测试
             JDBC_Service  sv  =   new  JDBC_Service();
              sv .transfer(10002,  "123321" , 10004, 30.00);
 
转账成功:1004:99+30    10002:99-30


=========================华丽丽的终结=====================================
总结:虽然由于快节奏的开发,编程速度的追求,越爱越多的MVC框架出现,比如持久层的hibernate,
mybatis等等,他们对Dao层的支持都很强大,既 快速,又简便。但是他们的底层同样是使用了JDBC,
为了追求高速简便,我们可以不使用JDBC,但一定要了解JDBC。了解JDBC也有助于学习其他持久层框架。

以上就是我对JDBC全部心得。


限于文章篇幅原因,这里仅仅介绍冰山一角。由于笔者的水平有限,编写时间也很仓促,
文中难免会出现一些错误或者不准确的地方,不妥之处恳请读者批评指正。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值