jdbc详解-阿里云开发者社区

开发者社区> 神棍先生> 正文

jdbc详解

简介: 今天我们来说一下关于JDBC的相关知识,关于JDBC我想大家都不陌生了,而且我记得早就开始使用它了,记得那是大二的时候做课程设计,但是那时候是为了完成任务,所以遇到问题就google,那时候也没有时间去整理,所以这次就来详细说一下关于JDBC的知识 摘要: JDBC(Java Data Base Connectivity,java数据库连接),由一些接口和类构成的API。
+关注继续查看

今天我们来说一下关于JDBC的相关知识,关于JDBC我想大家都不陌生了,而且我记得早就开始使用它了,记得那是大二的时候做课程设计,但是那时候是为了完成任务,所以遇到问题就google,那时候也没有时间去整理,所以这次就来详细说一下关于JDBC的知识

摘要:

JDBC(Java Data Base Connectivity,java数据库连接),由一些接口和类构成的API。
J2SE的一部分,由java.sql,javax.sql包组成。

应用程序、JDBC API、数据库驱动及数据库之间的关系



JDBC的使用步骤

1.注册驱动 (只做一次)

方式一:Class.forName(“com.mysql.jdbc.Driver”);
推荐这种方式,不会对具体的驱动类产生依赖。
方式二:DriverManager.registerDriver(com.mysql.jdbc.Driver);
会造成DriverManager中产生两个一样的驱动,并会对具体的驱动类产生依赖。
方式三:System.setProperty(“jdbc.drivers”, “driver1:driver2”);
虽然不会对具体的驱动类产生依赖;但注册不太方便,所以很少使用。 
驱动类型(四种类型)


2.建立连接(Connection) 

[java] view plain copy
  1. Connection conn = DriverManager.getConnection(url, user, password);  
url格式:
JDBC:子协议:子名称//主机名:端口/数据库名?属性名=属性值&…
User,password可以用“属性名=属性值”方式告诉数据库;
其他参数如:useUnicode=true&characterEncoding=GBK。


3.创建执行SQL的语句(Statement)

[java] view plain copy
  1. Statement  
  2. Statement st = conn.createStatement();  
  3. st.executeQuery(sql);  
  4. PreparedStatement  
  5. String sql = “select * from table_name where col_name=?”;  
  6. PreparedStatement ps = conn.preparedStatement(sql);  
  7. ps.setString(1, “col_value”);  
  8. ps.executeQuery();  


4.处理执行结果(ResultSet)

[java] view plain copy
  1. ResultSet rs = statement.executeQuery(sql);  
  2. While(rs.next()){  
  3. rs.getString(“col_name”);  
  4. rs.getInt(“col_name”);  
  5. //…  
  6. }  


5.释放资源

释放ResultSet, Statement,Connection.
数据库连接(Connection)是非常稀有的资源,用完后必须马上释放,如果Connection不能及时正确的关闭将导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。


下面来看一下完整的Demo:

工具类:JdbcUtils

[java] view plain copy
  1. package com.weijia.firstdemo;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.DriverManager;  
  5. import java.sql.ResultSet;  
  6. import java.sql.SQLException;  
  7. import java.sql.Statement;  
  8.   
  9. import javax.sql.DataSource;  
  10.   
  11. public class JdbcUtils {  
  12.       
  13.     private static String user = "root";  
  14.     private static String password = "123456";  
  15.     private static String dbName = "test";  
  16.     private static  String url = "jdbc:mysql://localhost:3306/"+dbName+"?user="+user+"&password="+password+"&useUnicode=true&characterEncoding=gb2312";  
  17.       
  18.     private static DataSource dataSource = null;  
  19.       
  20.     /** 
  21.      * 加载驱动 
  22.      */  
  23.     static{  
  24.         try{  
  25.             Class.forName("com.mysql.jdbc.Driver");  
  26.         }catch(Exception e){  
  27.             System.out.println("Exception:"+e.getMessage()+"");  
  28.             throw new ExceptionInInitializerError(e);  
  29.         }  
  30.     }  
  31.       
  32.     private JdbcUtils(){  
  33.     }  
  34.       
  35.     /** 
  36.      * 获取连接 
  37.      * @return 
  38.      * @throws SQLException 
  39.      */  
  40.     public static Connection getConnection() throws SQLException{  
  41.         return DriverManager.getConnection(url);  
  42.     }  
  43.       
  44.     public static DataSource getDataSource(){  
  45.         return dataSource;  
  46.     }  
  47.       
  48.     /** 
  49.      * 释放资源 
  50.      * @param rs 
  51.      * @param st 
  52.      * @param conn 
  53.      */  
  54.     public static void free(ResultSet rs,Statement st,Connection conn){  
  55.         try{  
  56.             if(rs != null){  
  57.                 rs.close();  
  58.             }  
  59.         }catch(SQLException e){  
  60.             e.printStackTrace();  
  61.         }finally{  
  62.             try{  
  63.                 if(st != null){  
  64.                     st.close();  
  65.                 }  
  66.             }catch(SQLException e){  
  67.                 e.printStackTrace();  
  68.             }finally{  
  69.                 try{  
  70.                     if(conn != null){  
  71.                         conn.close();  
  72.                     }  
  73.                 }catch(SQLException e){  
  74.                     e.printStackTrace();  
  75.                 }  
  76.             }  
  77.         }  
  78.               
  79.     }  
  80.   
  81. }  


测试类:

[java] view plain copy
  1. package com.weijia.firstdemo;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.DriverManager;  
  5. import java.sql.ResultSet;  
  6. import java.sql.SQLException;  
  7. import java.sql.Statement;  
  8.   
  9. public class Demo {  
  10.       
  11.     public static void main(String[] args) throws Exception{  
  12.         //测试代码:  
  13.         test();  
  14.         //标准规范代码:  
  15.         template();  
  16.     }  
  17.       
  18.     //模板代码  
  19.     public static void template(){  
  20.         Connection conn = null;  
  21.         Statement st = null;  
  22.         ResultSet rs = null;  
  23.         try {  
  24.             conn = JdbcUtils.getConnection();  
  25.             //创建语句  
  26.             st = conn.createStatement();  
  27.             //执行语句  
  28.             rs = st.executeQuery("select * from user");  
  29.             //处理结果  
  30.             while(rs.next()){  
  31.                 System.out.println(rs.getObject(1) + "\t" + rs.getObject(2) + "\t" + rs.getObject(3) + "\t");  
  32.             }  
  33.         }catch(SQLException e){  
  34.             e.printStackTrace();  
  35.         }catch(Exception e){  
  36.             e.printStackTrace();  
  37.         }finally{  
  38.             JdbcUtils.free(rs, st, conn);  
  39.         }  
  40.     }  
  41.       
  42.     //测试  
  43.     static void test() throws Exception{  
  44.         //注册驱动  
  45.         DriverManager.registerDriver(new com.mysql.jdbc.Driver());  
  46.         //通过系统属性来注册驱动  
  47.         System.setProperty("jdbc.drivers","");  
  48.         //静态加载驱动  
  49.         Class.forName("com.mysql.jdbc.Driver");  
  50.           
  51.         //建立连接  
  52.         String url = "jdbc:mysql://localhost:3306";  
  53.         String userName = "root";  
  54.         String password = "";  
  55.         Connection conn = DriverManager.getConnection(url,userName,password);  
  56.           
  57.         //创建语句  
  58.         Statement st = conn.createStatement();  
  59.           
  60.         //执行语句  
  61.         ResultSet rs = st.executeQuery("select * from user");  
  62.           
  63.         //处理结果  
  64.         while(rs.next()){  
  65.             System.out.println(rs.getObject(1) + "\t" + rs.getObject(2) + "\t" + rs.getObject(3) + "\t");  
  66.         }  
  67.           
  68.         //释放资源  
  69.         rs.close();  
  70.         st.close();  
  71.         conn.close();  
  72.     }  
  73.   
  74. }  

注意:这里还要记住引入额外的jar.这个网上很多的,这里使用的是MySql,搜一下MySql驱动的jar就行了。这里我们将一些操作都放到一个工具类中,这种方式是很优雅的。


使用JDBC来实现CRUD的操作

我们这里就采用分层操作:Dao层,Service层

首先看一下domain域中的User实体

[java] view plain copy
  1. package com.weijia.domain;  
  2.   
  3. import java.util.Date;  
  4.   
  5. public class User {  
  6.       
  7.     private int id;  
  8.     private String name;  
  9.     private Date birthday;  
  10.     private float money;  
  11.       
  12.     public User(){  
  13.           
  14.     }  
  15.       
  16.     public User(int id,String name,Date birthday,float money){  
  17.         this.id = id;  
  18.         this.name = name;  
  19.         this.birthday = birthday;  
  20.         this.money = money;  
  21.     }  
  22.       
  23.     public int getId() {  
  24.         return id;  
  25.     }  
  26.     public void setId(int id) {  
  27.         this.id = id;  
  28.     }  
  29.     public String getName() {  
  30.         return name;  
  31.     }  
  32.     public void setName(String name) {  
  33.         this.name = name;  
  34.     }  
  35.     public Date getBirthday() {  
  36.         return birthday;  
  37.     }  
  38.     public void setBirthday(Date birthday) {  
  39.         this.birthday = birthday;  
  40.     }  
  41.     public float getMoney() {  
  42.         return money;  
  43.     }  
  44.     public void setMoney(float money) {  
  45.         this.money = money;  
  46.     }  
  47.   
  48.     @Override  
  49.     public String toString(){  
  50.         return "[id="+id+",name="+name+",birthday="+birthday+",money="+money+"]";  
  51.     }  
  52. }  

再来看一下Dao层结构:

接口:

[java] view plain copy
  1. package com.weijia.domain;  
  2.   
  3. public interface UserDao {  
  4.   
  5.     //添加用户  
  6.     public void addUser(User user);  
  7.     //通过userid查询用户,id是唯一的,所以返回的是一个user  
  8.     public User getUserById(int userId);  
  9.     //更新用户信息  
  10.     public int update(User user);  
  11.     //删除用户信息  
  12.     public int delete(User user);  
  13.       
  14. }  

实现类:

[java] view plain copy
  1. package com.weijia.domain;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.Date;  
  5. import java.sql.PreparedStatement;  
  6. import java.sql.ResultSet;  
  7.   
  8. import com.weijia.firstdemo.JdbcUtils;  
  9.   
  10. public class UserDaoImpl implements UserDao{  
  11.   
  12.     /** 
  13.      * 添加用户 
  14.      */  
  15.     public void addUser(User user) {  
  16.         Connection conn = null;  
  17.         PreparedStatement st = null;  
  18.         try{  
  19.             conn = JdbcUtils.getConnection();  
  20.             String sql = "insert into user(id,name,birthday,money) values(?,?,?,?)";  
  21.             st = conn.prepareStatement(sql);  
  22.             st.setInt(1,user.getId());  
  23.             st.setString(2,user.getName());  
  24.             //日期格式的转换(utils.date转化成sql.date)  
  25.             st.setDate(3,new Date(user.getBirthday().getTime()));  
  26.             st.setFloat(4, user.getMoney());  
  27.             int count = st.executeUpdate();  
  28.             System.out.println("添加记录条数:"+count);  
  29.         }catch(Exception e){  
  30.             throw new DaoException(e.getMessage(),e);  
  31.         }finally{  
  32.             JdbcUtils.free(null, st, conn);  
  33.         }  
  34.     }  
  35.   
  36.     /** 
  37.      * 删除用户 
  38.      */  
  39.     public int delete(User user) {  
  40.         Connection conn = null;  
  41.         PreparedStatement st = null;  
  42.         try{  
  43.             conn = JdbcUtils.getConnection();  
  44.             String sql = "delete from user where id=?";  
  45.             st = conn.prepareStatement(sql);  
  46.             st.setInt(1,user.getId());  
  47.             int count = -1;  
  48.             count = st.executeUpdate();  
  49.             System.out.println("删除记录条数:"+count);  
  50.             return count;  
  51.         }catch(Exception e){  
  52.             throw new DaoException(e.getMessage(),e);  
  53.         }finally{  
  54.             JdbcUtils.free(null, st, conn);  
  55.         }  
  56.     }  
  57.   
  58.     /** 
  59.      * 通过userId获取用户信息 
  60.      */  
  61.     public User getUserById(int userId) {  
  62.         Connection conn = null;  
  63.         PreparedStatement st = null;  
  64.         ResultSet rs = null;  
  65.         try{  
  66.             conn = JdbcUtils.getConnection();  
  67.             String sql = "select * from user where id=?";  
  68.             st = conn.prepareStatement(sql);  
  69.             st.setInt(1,userId);  
  70.             rs = st.executeQuery();  
  71.             if(rs.next()){  
  72.                 User user = new User();  
  73.                 user.setId(userId);  
  74.                 user.setName(rs.getString("name"));  
  75.                 user.setBirthday(rs.getDate("birthday"));  
  76.                 user.setMoney(rs.getFloat("money"));  
  77.                 return user;  
  78.             }  
  79.         }catch(Exception e){  
  80.             throw new DaoException(e.getMessage(),e);  
  81.         }finally{  
  82.             JdbcUtils.free(rs, st, conn);  
  83.         }  
  84.         return null;  
  85.     }  
  86.   
  87.     /** 
  88.      * 更新用户信息 
  89.      */  
  90.     public int update(User user){  
  91.         Connection conn = null;  
  92.         PreparedStatement st = null;  
  93.         try{  
  94.             conn = JdbcUtils.getConnection();  
  95.             String sql = "update user set name=?,birthday=?,money=? where id=?";  
  96.             st = conn.prepareStatement(sql);  
  97.             st.setString(1,user.getName());  
  98.             st.setDate(2,new Date(user.getBirthday().getTime()));  
  99.             st.setFloat(3,user.getMoney());  
  100.             st.setInt(3,user.getId());  
  101.             int count = 0;  
  102.             count = st.executeUpdate();  
  103.             System.out.println("更新的记录数:"+count);  
  104.             return count;  
  105.         }catch(Exception e){  
  106.             throw new DaoException(e.getMessage(),e);  
  107.         }finally{  
  108.             JdbcUtils.free(null, st, conn);  
  109.         }  
  110.     }  
  111.   
  112. }  


然后是Servic层:

[java] view plain copy
  1. package com.weijia.domain;  
  2.   
  3. public class UserService {  
  4.       
  5.     private UserDao userDao;  
  6.       
  7.     public UserService(){  
  8.         //通过工厂实例化UserDao对象  
  9.         userDao = DaoFactory.getInstance().createUserDao();  
  10.         System.out.println("userDao:"+userDao);  
  11.     }  
  12.       
  13.     /** 
  14.      * 注册用户 
  15.      * @param user 
  16.      */  
  17.     public void regist(User user){  
  18.         if(user == null){  
  19.             System.out.println("注册信息无效!!");  
  20.         }else{  
  21.             userDao.addUser(user);  
  22.         }  
  23.           
  24.     }  
  25.       
  26.     /** 
  27.      * 查询用户 
  28.      * @param userId 
  29.      * @return 
  30.      */  
  31.     public User query(int userId){  
  32.         User user = userDao.getUserById(userId);  
  33.         if(user == null){  
  34.             System.out.println("查询结果为空!!");  
  35.         }else{  
  36.             System.out.println(user.getId()+"\t"+user.getName()+"\t"+user.getBirthday()+"\t"+user.getMoney());  
  37.         }  
  38.         return userDao.getUserById(userId);  
  39.     }  
  40.       
  41.     /** 
  42.      * 更新用户 
  43.      * @param user 
  44.      */  
  45.     public void update(User user){  
  46.         if(user.getId()<=0){  
  47.             System.out.println("用户id无效,无法更新");  
  48.         }else{  
  49.             userDao.update(user);  
  50.         }  
  51.     }  
  52.       
  53.     /** 
  54.      * 删除用户 
  55.      * @param user 
  56.      */  
  57.     public void delete(User user){  
  58.         if(user.getId()<=0){  
  59.             System.out.println("用户id无效,无法删除!!");  
  60.         }else{  
  61.             userDao.delete(user);  
  62.         }  
  63.     }  
  64.       
  65. }  


这里我们还需要额外的两个类:

一个是异常类,因为我们需要自定义我们自己的一个异常,这样方便进行捕获:

[java] view plain copy
  1. package com.weijia.domain;  
  2.   
  3. public class DaoException extends RuntimeException{  
  4.   
  5.     private static final long serialVersionUID = 1L;  
  6.       
  7.     public DaoException(){  
  8.           
  9.     }  
  10.       
  11.     public DaoException(Exception e){  
  12.         super(e);  
  13.     }  
  14.       
  15.     public DaoException(String msg){  
  16.         super(msg);  
  17.     }  
  18.       
  19.     public DaoException(String msg,Exception e){  
  20.         super(msg,e);  
  21.     }  
  22.   
  23. }  


同时,我们这里面采用工厂模式进行实例化UserDao对象:

[java] view plain copy
  1. package com.weijia.domain;  
  2.   
  3. import java.io.FileInputStream;  
  4. import java.util.Properties;  
  5.   
  6. public class DaoFactory {  
  7.     /** 
  8.      * 单例模式 
  9.      */  
  10.     private static UserDao userDao = null;  
  11.     private static DaoFactory instance = new DaoFactory();  
  12.       
  13.     private DaoFactory(){  
  14.         /** 
  15.          * 通过读取属性文件来动态的加载Dao层类 
  16.          */  
  17.         Properties prop = new Properties();  
  18.         try{  
  19.             FileInputStream fis = new FileInputStream("src/com/weijia/domain/daoconfig.properties");  
  20.             prop.load(fis);  
  21.             String className = prop.getProperty("userDaoClass");  
  22.             Class<?> clazz = Class.forName(className);  
  23.             userDao = (UserDao)clazz.newInstance();  
  24.             fis.close();  
  25.         }catch(Throwable e){  
  26.             throw new ExceptionInInitializerError(e);  
  27.         }  
  28.     }  
  29.       
  30.     public static DaoFactory getInstance(){  
  31.         return instance;  
  32.     }  
  33.       
  34.     public UserDao createUserDao(){  
  35.         return userDao;  
  36.     }  
  37.   
  38. }  

这里面是读取properties文件,然后去读取类名进行加载,这种方式是很灵活的


测试:

[java] view plain copy
  1. package com.weijia.domain;  
  2.   
  3. import java.util.Date;  
  4.   
  5. public class TestDemo {  
  6.       
  7.     public static void main(String[] args) throws Exception{  
  8.         UserService userService = new UserService();  
  9.         System.out.println("添加用户:");  
  10.         userService.regist(new User(1,"jiangwei",new Date(System.currentTimeMillis()),300));  
  11.     }  
  12.   
  13. }  

这里我们看到其实这些操作真的很简单,就是按照那样的几个步骤来操作即可,同时我们还需要将结构进行分层,以便管理,我们这里面测试的时候,撇开了创建数据库的一个环节,至于那个环节,也是不难的,可以从网上搜索一下即可。


Statement中的sql依赖注入的问题

接着来看一下关于我们上面的例子中使用了Statement进行操作的,其实这里面是存在一个问题的,就是会有sql注入的问题,我们先来看一下这个问题:

查询学生信息:

[java] view plain copy
  1. /** 
  2.      * 使用Statement读取数据 
  3.      * @param name 
  4.      * @throws SQLException 
  5.      */  
  6.     static void read(String name) throws SQLException{  
  7.         Connection conn = null;  
  8.         Statement st = null;  
  9.         ResultSet rs = null;  
  10.         try {  
  11.             conn = JdbcUtils.getConnection();  
  12.             //创建语句  
  13.             st = conn.createStatement();  
  14.             //执行语句(不建议使用*)  
  15.             String sql = "select id,name from user where name='"+name+"'";  
  16.             rs = st.executeQuery(sql);  
  17.             //根据列名取数据  
  18.             while(rs.next()){  
  19.                 System.out.println(rs.getObject("id") + "\t" + rs.getObject("name") + "\t");  
  20.             }  
  21.         }catch(SQLException e){  
  22.             e.printStackTrace();  
  23.         }catch(Exception e){  
  24.             e.printStackTrace();  
  25.         }finally{  
  26.             JdbcUtils.free(rs, st, conn);  
  27.         }  
  28.     }  

我们使用代码测试一下:

[java] view plain copy
  1. read("'or 1 or'");  

我们运行会发现,将查询出所有的学生的记录,这个是什么原因呢?我们不妨将sql打印一下会发现:

[java] view plain copy
  1. select id,name from user where name=''or 1 or''  

擦,因为sql语句中把1认为是true,又因为是或的关系,所以将所有的学生的信息查询出来了,这个就是sql注入,因为Statement会把传递进来的参数进行一下转化操作,用引号包含一下,所以会出现这个问题,那么我们该怎么解决呢?有的同学说我们可以添加一句过滤的代码,将传递的参数取出单引号,这个方法是可行的的,但是这个只能解决那些使用单引号的数据库,可能有的数据库使用的是双引号包含内容,那就不行了,所以应该想一个全套的方法,那么这里我们就是用一个叫做:PreparedStatement类,这个类是Statement类的子类,关于这两个类的区别可以查看我的另外一片文章:

http://blog.csdn.net/jiangwei0910410003/article/details/26143977

我们这里只看这个sql注入的问题:

我们将上面读取用户信息的代码改写成PreparedStatement:

[java] view plain copy
  1. /** 
  2.      * 使用PreparedStatement 
  3.      * @param name 
  4.      * @throws SQLException 
  5.      */  
  6.     static void readPrepared(String name) throws SQLException{  
  7.         Connection conn = null;  
  8.         PreparedStatement st = null;  
  9.         ResultSet rs = null;  
  10.         try{  
  11.             conn = JdbcUtils.getConnection();  
  12.             //执行语句(不建议使用*)  
  13.             String sql = "select id,name from user where name=?";  
  14.             //创建语句  
  15.             st = conn.prepareStatement(sql);  
  16.             st.setString(1, name);  
  17.             rs = st.executeQuery();  
  18.             //根据列名取数据  
  19.             while(rs.next()){  
  20.                 System.out.println(rs.getObject("id") + "\t" + rs.getObject("name") + "\t");  
  21.             }  
  22.         }catch(Exception e){  
  23.               
  24.         }  
  25.     }  


之后我们在执行:

[java] view plain copy
  1. readPrepared("'or 1 or'");  

就不会全部查出来了,只会查询空结果,因为表中没有一个学生的名字叫做 'or 1 or'。


JDBC中特殊数据类型的操作问题

第一个是日期问题:

我们在操作日期问题的时候会发现,使用PreparedStatement进行参数赋值的时候,有一个方法是:setDate(...),但是这个方法接收的参数是sql中的Date类,而不是我们平常使用的util中的Date类,所以我们要做一次转化,通常我们是这样做的,就是在定义实体类的时候将其日期型的属性定义成util中的Date类型,在进行数据库操作的时候.

进行一次转换:setDate(x,new Date(birthday.getTime());,这里birthday就是一个util.Date类型的一个属性,而new Date是sql.Date类型的,这样转化就可以了,同样我们在读取数据的时候将转化操作反过来即可。


第二个问题就是大文本数据的问题

因为有时候我们会存入一些文本内容,因为varchar的大小在mysql中也是有上线的,所以我们这里要使用blob类型了,我们这里来看一下实例:

[java] view plain copy
  1. /** 
  2.      * 插入大文本 
  3.      */  
  4.     static void insert(){  
  5.         Connection conn = null;  
  6.         PreparedStatement ps = null;  
  7.         ResultSet rs = null;  
  8.         try{  
  9.             conn = JdbcUtils.getConnection();  
  10.             String sql = "insert into clob_test(bit_text) values(?)";  
  11.             ps = conn.prepareStatement(sql);  
  12.             File file = new File("src/com/weijia/type/ClubDemo.java");  
  13.             Reader reader = new BufferedReader(new FileReader(file));  
  14.             //ps.setAsciiStream(1, new FileInputStream(file), (int)file.length());//英文的文档  
  15.             ps.setCharacterStream(1, reader, (int)file.length());  
  16.             ps.executeUpdate();  
  17.             reader.close();  
  18.         }catch(Exception e){  
  19.             e.printStackTrace();  
  20.         }finally{  
  21.             JdbcUtils.free(rs,ps,conn);  
  22.         }  
  23.     }  


我们将一个Java代码文件插入到数据库中

我们查询一下clob_test表:


我们看到文件内容存入到库中了。同样我们也可以从表中读取一段文本出来,使用

[java] view plain copy
  1. Clob clob = rs.getClob(1);  
  2. InputStream is = clob.getAsciiStream();  

或者读取一个Reader也是可以的,这里的InputStream和Reader是针对不同流,一个字节流,这个不需要关心编码问题的,Reader是字符流需要关心编码问题。


JDBC中事务的概念

我们当初在学习数据库的时候就了解事务的概念了,事务在数据库中的地位是很重要的。在JDBC中默认情况事务是自动提交的,所以我们在进行CRUD操作的时候不需要关心开启事务,提交事务,事务回滚的一些操作,那么下面我们就来看一下怎么手动的操作一些事务:

下载我们假定这样的一个场景:

有来两个用户1和2,现在

将用户1中的账户的钱减少10
查询用户2中的账户的钱,如果钱少于300,就增加10,否则抛出异常

看一下代码:

[java] view plain copy
  1. static void test() throws Exception{  
  2.         Connection conn = null;  
  3.         Statement st = null;  
  4.         ResultSet rs = null;  
  5.         try{  
  6.             conn = JdbcUtils.getConnection();  
  7.             /**************事务START********************/  
  8.             conn.setAutoCommit(false);  
  9.             st = conn.createStatement();  
  10.               
  11.             String sql = "update user set money=money-10 where id=1";  
  12.             st.executeUpdate(sql);  
  13.               
  14.             sql = "select money from user where id=2";  
  15.             rs = st.executeQuery(sql);  
  16.             float money = 0.0f;  
  17.             if(rs.next()){  
  18.                 money = rs.getFloat("money");  
  19.             }  
  20.             if(money>300){  
  21.                 throw new RuntimeException("已经超过最大值");  
  22.             }  
  23.             sql = "update user set money=money+10 where id=2";  
  24.             st.executeUpdate(sql);  
  25.             conn.commit();  
  26.             /*******************事务END*********************/  
  27.         }catch(RuntimeException e){  
  28.               
  29.         }finally{  
  30.             JdbcUtils.free(rs, st, conn);  
  31.         }  
  32.     }  

我们运行测试一下,因为我们这里想让它抛出异常,所以我们将用户2中的钱改成大于300的,运行一下,结果抛出异常了,但是我们发现了用户1中的钱少了10,但是由于抛出异常,所以后面的代码不执行了,用户2中的钱没有变化,那么这样的操作明显不对的,所以我们这时候要解决这个问题,使用事务的回滚操作,在捕获到异常的时候需要做回滚操作:

[java] view plain copy
  1. if(conn != null){  
  2.    conn.rollback();  
  3. }  

这样即使抛出了异常,这些操作也会进行回滚的,那么用户1中的钱就不会少10了。

同时上面我们看到,我们是在开始的时候手动的关闭事务的自动提交,然后再手动的提交事务,下面再来看一下事务的保存点的问题。

场景:在上面的基础上,我们添加一个用户3,同时对用户1和用户3中的钱进行减少10,用户2的操作不变,但是当抛出异常的时候,我们希望用户1的操作还是有效的,用户3的操作还原,这时候我们需要将事务回滚到用户3的那个点就可以了,这就是事务的保存点的概念,看一下代码:

[java] view plain copy
  1. static void test() throws Exception{  
  2.         Connection conn = null;  
  3.         Statement st = null;  
  4.         ResultSet rs = null;  
  5.         Savepoint sp = null;  
  6.         try{  
  7.             conn = JdbcUtils.getConnection();  
  8.             /**************事务START********************/  
  9.             conn.setAutoCommit(false);  
  10.             st = conn.createStatement();  
  11.               
  12.             String sql = "update user set money=money-10 where id=1";  
  13.             st.executeUpdate(sql);  
  14.             sp = conn.setSavepoint();//设置回滚点  
  15.               
  16.             sql = "update user set money=money-10 where id=3";  
  17.             st.executeUpdate(sql);  
  18.               
  19.             sql = "select money from user where id=2";  
  20.             rs = st.executeQuery(sql);  
  21.             float money = 0.0f;  
  22.             if(rs.next()){  
  23.                 money = rs.getFloat("money");  
  24.             }  
  25.             System.out.println("money:"+money);  
  26.             if(money>300){  
  27.                 throw new RuntimeException("已经超过最大值");  
  28.             }  
  29.             sql = "update user set money=money+10 where id=2";  
  30.             st.executeUpdate(sql);  
  31.             conn.commit();  
  32.             /*******************事务END*********************/  
  33.         }catch(SQLException e){  
  34.             if(conn != null && sp != null){  
  35.                 conn.rollback(sp);  
  36.                 conn.commit();  
  37.             }  
  38.         }finally{  
  39.             JdbcUtils.free(rs, st, conn);  
  40.         }  
  41.     }  

我们在用户1之后设置一个保存点,在异常中只需要回滚到保存点就可以了。


下面再来看一下事务的隔离级别,因为这部分的内容比较重要和繁琐,请看另外一篇文章:

http://blog.csdn.net/jiangwei0910410003/article/details/24960785

JDBC中调用存储过程

关于MySql中的存储过程的知识请看这两篇文章:

http://blog.csdn.net/jiangwei0910410003/article/details/24964331

http://blog.csdn.net/jiangwei0910410003/article/details/24965087

当我们会创建存储过程的时候,我们在到JDBC中去调用这个存储过程,

下面看一下实例:

我们现在想在插入一条数据的时候能够得到主键id的值(因为我们一般把主键id的值设置成自增长的形式),首先来创建一个存储过程:

[java] view plain copy
  1. delimiter $$ //修改定界符  
  2. drop procedure if exists addUser $$  
  3. create procedure addUser(in name varchar(45),in birthday date,in money float,out pid int)  
  4. begin  
  5.  insert into user(name,birthday,money) values(anme,birthday,money);  
  6.  select last_insert_id() into pid;//当前线程拿到最后一次插入的记录的赋值给pid,这里要注意,user表中的id必须是主键自增长类型,不然报错  
  7. end $$  
  8. delimiter ;  

这里name,birthday,money都是输入值是:in

pid是输出值:out

然后我们在代码中进行执行这个存储过程:

[java] view plain copy
  1. static void test() throws Exception{  
  2.         Connection conn = null;  
  3.         CallableStatement cs = null;  
  4.         try{  
  5.             conn = JdbcUtils.getConnection();  
  6.             //name,birthday,money,id  
  7.             //存储过程名称是:addUser  
  8.             String sql = "{ call addUser(?,?,?,?)}";  
  9.             cs = conn.prepareCall(sql);  
  10.             cs.registerOutParameter(4, Types.INTEGER);  
  11.             cs.setString(1,"jiangwei");  
  12.             cs.setDate(2,new Date(System.currentTimeMillis()));  
  13.             cs.setFloat(3,300);  
  14.             cs.executeUpdate();  
  15.               
  16.             int id = cs.getInt(4);  
  17.             System.out.println("id:"+id);  
  18.               
  19.             /** 
  20.              * 通过这个存储过程来获取主键id是有一个问题,不同的数据库,存储过程的编写语法是不一样的,所以这种方法是不通用 
  21.              * 还有另外一种方法是OtherApi,通过JDBC中的api来获取 
  22.              */  
  23.               
  24.         }catch(SQLException e){  
  25.             e.printStackTrace();  
  26.         }  
  27.     }  

这样我们就得到了插入一条记录的时候得到他的主键id的值

其实这种调用存储结构的方法,在早起的时候是很实用的,因为那时候没有分层架构的思想,所以会将业务逻辑层的实现放到存储过程中去做了,在调用存储过程的时候,会发现一个问题就是这样去获取主键id的值的方式,是不通用的,因为不同的数据库可能存储过程的编写是不一样的,所以做不到一致性,而且现在有了三成架构的思想,我们慢慢的就将这种方式给淘汰了,而是直接使用JDBC给我们提供的一套api来获取主键key的值:直接上代码吧:

[java] view plain copy
  1. static void test() throws Exception{  
  2.         java.sql.Connection conn = null;  
  3.         PreparedStatement ps = null;  
  4.         ResultSet rs = null;  
  5.         try{  
  6.             conn = JdbcUtils.getConnection();  
  7.             String sql = "insert into user(name,birthday,money) values('jiangwei','1987-01-01',400)";  
  8.             ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);//生成主键id  
  9.             ps.executeUpdate();  
  10.             //可能是组合主键,可能会返回一个ResultSet  
  11.             rs = ps.getGeneratedKeys();  
  12.             int id = 0;  
  13.             if(rs.next()){  
  14.                 id = rs.getInt(1);  
  15.             }  
  16.             System.out.println("id:"+id);  
  17.         }catch(Exception e){  
  18.             e.printStackTrace();  
  19.         }finally{  
  20.             JdbcUtils.free(rs,ps,conn);  
  21.         }  
  22.     }  

我们只要设置一个参数Statement.RETURN_GENERATED_KEYS就可以得到一个主键集合了,这里要注意的是,因为有的表结构中会出现组合主键的情况,所以返回的是一个主键集合。这种方式就和底层数据库摆脱了关系,做到一致性了。


JDBC来实现批处理功能

我们在前面的例子中会发现,每次都是执行一条语句,然后关闭连接,这样效率可能会很低,如果我们想一次插入几千条数据的话,这时候可以使用批处理的功能,所谓批处理就是将多个执行语句进行捆绑然后去执行,但是效率上并非就一定高,因为我们知道这个数据库连接是tcp的,所以在将多个语句捆绑在一起的时候,在传输的过程中也是会进行分包发送的,这个包的大小也不是固定的,这个大小很难掌控的,我们之后经过多次测试之后,才能得到一次批量处理的适宜数量。下面来看一下实例吧:

首先是普通的插入一条数据:

[java] view plain copy
  1. static void create() throws Exception{  
  2.         //建立一个连接的是很耗时间的  
  3.         //执行一个sql语句也是很耗时间的  
  4.         //优化的措施:批处理  
  5.         Connection conn = null;  
  6.         PreparedStatement ps = null;  
  7.         ResultSet rs = null;  
  8.         try{  
  9.             conn = JdbcUtils.getConnection();  
  10.             String sql = "insert user(name,birthday,money) values(?,?,?)";  
  11.             ps = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);  
  12.             ps.setString(1,"jiangwei");  
  13.             ps.setDate(2,new Date(System.currentTimeMillis()));  
  14.             ps.setFloat(3,400);  
  15.             ps.executeUpdate();  
  16.         }catch(Exception e){  
  17.             e.printStackTrace();  
  18.         }finally{  
  19.             JdbcUtils.free(rs, ps, conn);  
  20.         }  
  21.     }  


然后是批处理插入100条数据:

[java] view plain copy
  1. static void createBatch() throws Exception{  
  2.         //建立一个连接的是很耗时间的  
  3.         //执行一个sql语句也是很耗时间的  
  4.         //优化的措施:批处理  
  5.         Connection conn = null;  
  6.         PreparedStatement ps = null;  
  7.         ResultSet rs = null;  
  8.         try{  
  9.             conn = JdbcUtils.getConnection();  
  10.             String sql = "insert user(name,birthday,money) values(?,?,?)";  
  11.             ps = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);  
  12.               
  13.             //打包的话容量也不是越大越好,因为可能会内存溢出的,同时网络传输的过程中也是会进行拆包传输的,这个包的大小是不一定的  
  14.             //有时候打包的效率不一定就会高,这个和数据库的类型,版本都有关系的,所以我们在实践的过程中需要检验的  
  15.             for(int i=0;i<100;i++){  
  16.                 ps.setString(1,"jiangwei");  
  17.                 ps.setDate(2,new Date(System.currentTimeMillis()));  
  18.                 ps.setFloat(3,400);  
  19.                 //ps.addBatch(sql);  
  20.                 ps.addBatch();  
  21.             }  
  22.             ps.executeBatch();  
  23.         }catch(Exception e){  
  24.             e.printStackTrace();  
  25.         }finally{  
  26.             JdbcUtils.free(rs, ps, conn);  
  27.         }  
  28.     }  


测试代码:

[java] view plain copy
  1. public static void main(String[] args) throws Exception{  
  2.         long startTime = System.currentTimeMillis();  
  3.         for(int i=0;i<100;i++){  
  4.             create();  
  5.         }  
  6.         long endTime = System.currentTimeMillis();  
  7.         System.out.println("For Waste Time:"+(endTime-startTime));  
  8.         createBatch();  
  9.         System.out.println("Batch Waste Time:"+(System.currentTimeMillis()-endTime));  
  10.     }  

我们在控制台中看到他们分别消耗的时间:


我们可以看到这个批处理消耗的时间明显很少。。当然我们在开始的时候也说过了,这个批处理的最适宜的大小要掌控好。


JDBC中的滚动结果集和分页技术

我们在前面的例子中可以看到,在处理结果集的时候,我们都是一条一条向后处理的,但是有时候我们需要人为的控制结果集的滚动,比如我们想往前滚动,想直接定位到哪个结果集记录等操作,当然JDBC也是提供了一套Api让我们来操作的

[java] view plain copy
  1. static void test() throws Exception{  
  2.         Connection conn = null;  
  3.         Statement st = null;  
  4.         ResultSet rs = null;  
  5.         try{  
  6.             conn = JdbcUtils.getConnection();  
  7.             //结果集可滚动的  
  8.             /** 
  9.              * 参数的含义: 
  10.              *  ResultSet.RTYPE_FORWORD_ONLY:这是缺省值,只可向前滚动;  
  11.                 ResultSet.TYPE_SCROLL_INSENSITIVE:双向滚动,但不及时更新,就是如果数据库里的数据修改过,并不在ResultSet中反应出来。  
  12.                 ResultSet.TYPE_SCROLL_SENSITIVE:双向滚动,并及时跟踪数据库的更新,以便更改ResultSet中的数据。 
  13.                 ResultSet.CONCUR_READ_ONLY:这是缺省值,指定不可以更新 ResultSet  
  14.                 ResultSet.CONCUR_UPDATABLE:指定可以更新 ResultSet 
  15.              */  
  16.             st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);  
  17.             rs = st.executeQuery("select id,name,money,birthday from user");  
  18.             //开始的时候这个游标的位置是第一条记录之前的一个位置  
  19.             //当执行rs.next的时候这个游标的位置就到第一条记录了  
  20.             /*while(rs.next()){ 
  21.                 //print result 
  22.             }*/  
  23.             //上面的代码执行之后,这个游标就到最后一条记录的下一个位置了  
  24.             //所以这里在调用previous方法之后,这个游标就回到了最后一条记录中,所以打印了最后一条记录的值  
  25.             /*if(rs.previous()){ 
  26.                 System.out.println("id="+rs.getInt("id")+"\tname="+rs.getString("name")+"\tbirthday="+rs.getDate("birthday")+"\tmoney="+rs.getFloat("money")); 
  27.             }*/  
  28.               
  29.             //绝对定位到第几行结果集  
  30.             //这里传递的参数的下标是从1开始的,比如这里查询出来的记录有3条,那么这里的参数的范围是:1-3,如果传递的参数不在这个范围内就会报告异常的  
  31.             rs.absolute(2);  
  32.             System.out.println("id="+rs.getInt("id")+"\tname="+rs.getString("name")+"\tbirthday="+rs.getDate("birthday")+"\tmoney="+rs.getFloat("money"));  
  33.               
  34.             //滚到到第一行的前面(默认的就是这种情况)  
  35.             rs.beforeFirst();  
  36.               
  37.             //滚动到最后一行的后面  
  38.             rs.afterLast();  
  39.               
  40.             rs.isFirst();//判断是不是在第一行记录  
  41.             rs.isLast();//判断是不是在最后一行记录  
  42.             rs.isAfterLast();//判断是不是第一行前面的位置  
  43.             rs.isBeforeFirst();//判断是不是最后一行的后面的位置  
  44.               
  45.             //以上的api可以实现翻页的效果(这个效率很低的,因为是先把数据都查询到内存中,然后再进行分页显示的)  
  46.               
  47.             //效率高的话是直接使用数据库中的分页查询语句:  
  48.             //select * from user limit 150,10;  
  49.               
  50.             //以上的api实现的分页功能是针对于那些本身不支持分页查询功能的数据库的,如果一个数据库支持分页功能,上面的代码就不能使用的,因为效率是很低的  
  51.         }catch(Exception e){  
  52.             e.printStackTrace();  
  53.         }finally{  
  54.             JdbcUtils.free(rs,st,conn);  
  55.         }  
  56.     }  

我们看到结果集:rs有很多方法的,我们一次来看一下:

next():这个很常用的,就是将结果集向后滚动
previous():这个方法和next是相反的,将结果集向前滚动
absolute(int index):这个方法是将结果集直接定位到指定的记录上,这个参数是从1开始的,不是0,如果不在指定的范围内的话,会报告异常的
beforeFirst():这个方法是将结果集直接滚动到第一条记录的前面的位置(默认情况是这样的,所以我们每次在取出数据的时候,需要使用next方法,将结果集滚动到第一条记录上)
afterLast():这个方法是将结果集直接滚动到最后一条记录的后面的位置
isFirst():判断是不是在第一行记录
isLast():判断是不是在最后一行记录
isAfterLast():判断是不是第一行前面的位置
isBeforeFirst():判断是不是最后一行的后面的位置


当然我们要向实现可滚动的结果集,还要设置一下参数:

[java] view plain copy
  1. st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);  

参数的含义:

ResultSet.RTYPE_FORWORD_ONLY:这是缺省值,只可向前滚动; 
ResultSet.TYPE_SCROLL_INSENSITIVE:双向滚动,但不及时更新,就是如果数据库里的数据修改过,并不在ResultSet中反应出来。 
ResultSet.TYPE_SCROLL_SENSITIVE:双向滚动,并及时跟踪数据库的更新,以便更改ResultSet中的数据。
ResultSet.CONCUR_READ_ONLY:这是缺省值,指定不可以更新 ResultSet 
ResultSet.CONCUR_UPDATABLE:指定可以更新 ResultSet(这个后面会说到)

同时在这里我们只需要使用absolute方法就可以实现分页的功能,因为他可以随便的定位到指定的记录集中,但是这个是在全部将结果集查询处理的基础上来实现的,就是首先将所有符合条件的结果集查询出来放到内存中,然后再就行分页操作,那么这种分页的效率就很低了,所以我们强烈建议在数据库查询数据的时候就进行分页操作,比如MySql中使用limit关键字进行操作,MSSQL中使用top关键字,Oracle中使用number关键字,但是有的数据库中不支持分页查询操作,所以这时候我们只能使用上面的形式来进行分页了。


JDBC中的可更新以及对更新敏感的结果集操作

我们有时候可能有这样的需求,就是在查询出结果集的时候,想对指定的记录进行更新操作,说白了,就是将查询和更新操作放到一起进行,来看一下代码:

[java] view plain copy
  1. static void test() throws Exception{  
  2.         Connection conn = null;  
  3.         Statement st = null;  
  4.         ResultSet rs = null;  
  5.         try{  
  6.             conn = JdbcUtils.getConnection();  
  7.             //第三个字段的含义是,在读取数据的时候(已经返回了结果集到内存中了),  
  8.             //再去修改结果集中的数据,这时候数据库中的数据就可以感知到结果集中的变化了进行修改  
  9.             st = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);  
  10.             rs = st.executeQuery("select * from user");  
  11.             //这种操作是不可取的,因为查询和更新交互在一起,逻辑就乱了,只有在特定的场合中使用  
  12.             while(rs.next()){  
  13.                 //这里我们获取到name列的值,如果是lisi我们就将结果集中的他的记录中的money变成170,  
  14.                 //然后再更行行信息,这时候数据库中的这条记录的值也发生变化了,  
  15.                 //内存中的结果集中的记录的值发生改变了,影响到了数据库中的值  
  16.                 String name = rs.getString("name");  
  17.                 if("jiangwei".equals(name)){  
  18.                     rs.updateFloat("money",170);  
  19.                     rs.updateRow();  
  20.                 }  
  21.             }  
  22.         }catch(Exception e){  
  23.             e.printStackTrace();  
  24.         }finally{  
  25.             JdbcUtils.free(rs,st,conn);  
  26.         }  
  27.     }  


我们看到在循环处理结果集的时候,我们将name是jiangwei的记录的钱修改成170,并且反映到数据库中

这里一定要记得设置参数:ResultSet.CONCUR_UPDATABLE,不然会报异常的,这个参数的功能就是将更新的操作同步到数据库中的

这里我们是不建议这种做法的,因为将查询和更新的操作放到一起来操作的话,维护是很差的,我们一定要将CRUD操作进行分开处理,这里只是介绍一下相关知识,不推荐使用的。


下面来看一下通过反射技术,来实现将结果集填充到指定的实体类中,其实这部分的内容很简单的,直接上代码:

[java] view plain copy
  1. /** 
  2.      * 使用泛型 
  3.      * @param <T> 
  4.      * @param sql 
  5.      * @param clazz 
  6.      * @return 
  7.      * @throws Exception 
  8.      */  
  9.     static <T> T test(String sql,Class<T> clazz) throws Exception{  
  10.         Connection conn = null;  
  11.         PreparedStatement ps = null;  
  12.         ResultSet rs = null;  
  13.         try{  
  14.             conn = JdbcUtils.getConnection();  
  15.             ps = conn.prepareStatement(sql);  
  16.             rs = ps.executeQuery();  
  17.             ResultSetMetaData rsmd = rs.getMetaData();  
  18.             int count = rsmd.getColumnCount();  
  19.             String[] colNames = new String[count];  
  20.             for(int i=1;i<=count;i++){  
  21.                 colNames[i-1] = rsmd.getColumnLabel(i);//使用别名,让列名和User中的属性名相同  
  22.             }  
  23.               
  24.             T user = clazz.newInstance();  
  25.             //使用反射获取set方法来进行赋值  
  26.             if(rs.next()){  
  27.                 Method[] ms = user.getClass().getMethods();  
  28.                 for(int i=0;i<colNames.length;i++){  
  29.                     String colName = colNames[i];  
  30.                     String methodName = "set" + colName;  
  31.                     for(Method method:ms){  
  32.                         //通过列名来找到实体类中的属性方法(这里要注意的是set方法的格式是:setXxx首字母是大写的)  
  33.                         //这里直接使用忽视大小写的相等的方法  
  34.                         //或者使用上面的重命名来解决这个问题  
  35.                         if(methodName.equalsIgnoreCase(method.getName())){  
  36.                             method.invoke(user, rs.getObject(colNames[i]));  
  37.                         }  
  38.                     }  
  39.                 }  
  40.             }  
  41.             return user;  
  42.         }catch(Exception e){  
  43.             e.printStackTrace();  
  44.         }finally{  
  45.             JdbcUtils.free(rs,ps,conn);  
  46.         }  
  47.         return null;  
  48.     }  


测试代码:

[java] view plain copy
  1. public static void main(String[] args) throws Exception{  
  2.         //User user = test("select * from user",User.class);  
  3.         //使用别名来规定列名和属性名相同  
  4.         User user = test("select id as Id,name as Name,birthday as Birthday,money as Money from user",User.class);  
  5.         System.out.println(user);  
  6.     }  

其实就是使用反射技术将得到实体类中所有属性的set方法,然后通过set方法进行属性值的填充,这里唯一要注意的问题就是返回的结果集中的字段名称必须要和实体类中的属性名称相同,要做到这一点我们又两种方式:

一种是直接将表中的字段名称和实体类中的属性名称相同

一种是使用别名的方式来操作,将别名设置的和实体类中的属性名称相同

其实我们会发现,后面说到的Hibernate框架就是采用这种机制的


元数据的相关知识

我们知道元数据信息就是标示数据本身的一些数据信息

1.数据库的元数据信息

就是数据库的相关信息,如数据库的版本号,驱动名称,是否支持事务操作等信息,JDBC提供了接口让我们可以获取这些信息的:

[java] view plain copy
  1. static void test() throws Exception{  
  2.         Connection conn = JdbcUtils.getConnection();  
  3.         //这些信息对于那些框架的编写就很有用了,因为框架是要兼容各个数据库类型的,如Hibernate中有一个方言设置  
  4.         //如果没有设置的话,他就会自己使用以下的api进行查找是那个数据库  
  5.         DatabaseMetaData metaData = conn.getMetaData();  
  6.         System.out.println("databaseName:"+metaData.getDatabaseProductName());  
  7.         System.out.println("driverName:"+metaData.getDriverName());  
  8.         System.out.println("isSupportBatch:"+metaData.supportsBatchUpdates());  
  9.         System.out.println("isSupportTransaction:"+metaData.supportsTransactions());  
  10.     }  

这些信息对于我们使用人员来说可能没有太大的用处,但是对于开发框架的人来说用处很大的,比如Hibernate框架,他要做到兼容所有的数据库特性的话,必须要将不同特性统一起来,所以他肯定要获取数据库的元数据信息的,后面会说到Hibernate中有一个配置叫做:方言,这个就是用来设置数据库名称的。

2.查询参数的元数据信息

当我们在使用PreparedStatement来进行参数填充的时候,我们想知道参数的一些信息,直接上代码:

[java] view plain copy
  1. static void test(String sql,Object[] params) throws Exception{  
  2.         Connection conn = JdbcUtils.getConnection();  
  3.         //参数的元数据信息:Statement是没有参数的元数据信息的(因为Statement不支持?),查看源代码,返回的都是varchar  
  4.         PreparedStatement ps = conn.prepareStatement(sql);  
  5.         //必须在连接数据库中的时候添加这个参数generateSimpleParameterMetadata=true  
  6.         //不然是获取不到参数的,而且会报异常  
  7.         ParameterMetaData pMetaData= ps.getParameterMetaData();  
  8.         int count = pMetaData.getParameterCount();  
  9.         for(int i=1;i<=count;i++){  
  10.             //因为mysql没有去查询库,所以不能根据查询的字段就能获取字段的类型,所有都返回varchar  
  11.             System.out.println(pMetaData.getParameterClassName(i));  
  12.             System.out.println(pMetaData.getParameterType(i));  
  13.             System.out.println(pMetaData.getParameterTypeName(i));  
  14.             //假定我们传入的参数的顺序和sql语句中的占位符的顺序一样的  
  15.             ps.setObject(i,params[i-1]);  
  16.         }  
  17.         ps.executeQuery();  
  18.     }  


测试代码:

[java] view plain copy
  1. public static void main(String[] args) throws Exception{  
  2.         String sql = "select name,birthday,money from user where name=?";  
  3.         Object[] params = new Object[]{"jiangwei"};  
  4.         test(sql,params);  
  5.     }  


我们知道Statement是不支持参数填充的,所以不可能获取到参数的元数据信息的

我们运行测试代码,会看到如下异常信息:


这时候我们就要注意了,如果想获取到元数据信息的话,我们还需要在连接数据的url后面添加一个参数:

generateSimpleParameterMetadata=true

添加完之后,我们运行结果如下:


我们看到可以获取参数在Java中的类型,12代表的是sql包中的类型:java.sql.Types.VARCHAR,这个字段是个整型值,值就是12,他对应到数据库中varchar类型的

这里要注意一点:有时候我们会发现这里获取到数据库中的类型是错误的,比如这里我们如果将数据库中的name字段的类型修改成char类型的,这里获取到的还是varchar,这一点想一想也是对的,你想想这个是获取查询参数的信息,我们还没有进行查询操作的,系统不可能那么智能的获取到数据库中准确的字段的类型的,所以他这里就做了一个大致的对应关系,将Java中的类型和数据库中的类型对应起来的,因为数据库中char和varchar都是字符串的。所以我们不能相信这里得到的数据库中字段的类型的,需要通过结果集中的元数据类型。

3.结果集中元数据信息

就是查询结果的一般信息,比如字段的相关信息

我们在上面看到要想获取数据库中字段的真实类型的话,只有先进行查询操作才可以,在这里我们就可以获取到正确的类型了,上代码:

[java] view plain copy
  1. static void test(String sql) throws Exception{  
  2.         Connection conn = null;  
  3.         PreparedStatement ps = null;  
  4.         ResultSet rs = null;  
  5.         try{  
  6.             conn = JdbcUtils.getConnection();  
  7.             ps = conn.prepareStatement(sql);  
  8.             rs = ps.executeQuery();  
  9.             ResultSetMetaData rsmd = rs.getMetaData();  
  10.             int count = rsmd.getColumnCount();  
  11.             String[] colNames = new String[count];  
  12.             for(int i=1;i<=count;i++){  
  13.                 //这里是可以获取到真实的类型的,因为这个是已经从数据库中查询了  
  14.                 System.out.println(rsmd.getColumnClassName(i));  
  15.                 System.out.println(rsmd.getColumnName(i));  
  16.                 System.out.println(rsmd.getColumnType(i));  
  17.                 System.out.println(rsmd.getColumnLabel(i));//列的别名:select name as n from user;,有别名的话就返回的是别名,而不是原始的列名了  
  18.                 colNames[i-1] = rsmd.getColumnName(i);  
  19.             }  
  20.             //将结果构建一个Map,列名是key,列的值是value  
  21.             Map<String,Object> data = null;  
  22.             //假设查询的数据只有一条,如果是多条的话我们可以定义一个List<Map<...这样的结构  
  23.             if(rs.next()){  
  24.                 data = new HashMap<String,Object>();  
  25.                 for(int i=0;i<colNames.length;i++){  
  26.                     data.put(colNames[i], rs.getObject(colNames[i]));  
  27.                 }  
  28.             }  
  29.         }catch(Exception e){  
  30.             e.printStackTrace();  
  31.         }finally{  
  32.             JdbcUtils.free(rs,ps,conn);  
  33.         }  
  34.     }  

我们这里可以获取到结果集中字段的总数count,以及字段的类型,名称,别名等信息,同时我们这里还穿插了一段代码,就是将结果集封装成一个HashMap结构,字段名做key,字段值做value


JDBC中的数据源

首先我们要知道什么是数据源,为什么要有数据源,我们从上面的例子看到,我们每次执行操作的时候,都是打开连接,关闭连接,这个连接的建立和关闭是很好资源和时间的,所以我们就在想一个策略怎么才能优化呢?所以数据源的概念就出来的,数据源就是用来管理连接的一个池子,使用高效的算法进行调度,这样在执行操作的时候是很方便的,为了容易理解数据源的相关概念,我们自己编写一个数据源:

[java] view plain copy
  1. package com.weijia.datasource;  
  2.   
  3. import java.io.PrintWriter;  
  4. import java.sql.Connection;  
  5. import java.sql.DriverManager;  
  6. import java.sql.SQLException;  
  7. import java.util.LinkedList;  
  8.   
  9. import javax.sql.DataSource;  
  10.   
  11. /** 
  12.  * 大部分时间都是浪费在数据库连接这一块 
  13.  * 这个类是我们自己编写的一个数据源 
  14.  * @author weijiang204321 
  15.  * 
  16.  */  
  17. public class MyDataSource implements DataSource{  
  18.   
  19.     private static String user = "root";  
  20.     private static String password = "123456";  
  21.     private static String dbName = "test";  
  22.     private static  String url = "jdbc:mysql://localhost:3306/"+dbName+"?user="+user+"&password="+password+"&useUnicode=true&characterEncoding=gb2312";  
  23.       
  24.     private static int initCount = 5;//初始化的连接数  
  25.     private static int maxCount = 10;//最大连接数  
  26.     private static int currentCount = 0;//当前的连接数  
  27.     //可能频繁的取出连接和删除连接,所以用LinkedList  
  28.     private LinkedList<Connection> connectionsPool = new LinkedList<Connection>();  
  29.       
  30.     public MyDataSource(){  
  31.         try{  
  32.             for(int i=0;i<initCount;i++){  
  33.                 this.connectionsPool.addLast(createConnection());  
  34.                 currentCount++;  
  35.             }  
  36.         }catch(Exception e){  
  37.             throw new ExceptionInInitializerError(e);  
  38.         }  
  39.     }  
  40.       
  41.     public Connection getConnection() throws SQLException{  
  42.         //也有可能获取不到连接,而且这个方法也是可能被多线程访问的  
  43.         synchronized(connectionsPool){  
  44.             if(connectionsPool.size() > 0){  
  45.                 return this.connectionsPool.removeFirst();  
  46.             }  
  47.             if(currentCount < maxCount){  
  48.                 currentCount++;  
  49.                 return createConnection();  
  50.             }  
  51.               
  52.             //在这里可以让当前线程等待,抛出异常,返回null都是可以的,要视情况而定  
  53.             throw new SQLException("已经没有连接了");  
  54.               
  55.             //不能无限制的创建连接的,因为这样的话对数据库的压力很大,连接越多,最后数据库的运行速度就会变得很慢了(很硬件相关)  
  56.             //如果内存够大,cpu给力的话,数据库可以建立的连接数也会增加的  
  57.         }  
  58.     }  
  59.       
  60.     public void free(Connection conn) throws SQLException{  
  61.         this.connectionsPool.addLast(conn);  
  62.     }  
  63.       
  64.     private Connection createConnection() throws SQLException{  
  65.         return DriverManager.getConnection(url);  
  66.     }  
  67.   
  68.     public Connection getConnection(String username, String password)throws SQLException {  
  69.         return null;  
  70.     }  
  71.   
  72.     public PrintWriter getLogWriter() throws SQLException {  
  73.         return null;  
  74.     }  
  75.   
  76.     public int getLoginTimeout() throws SQLException {  
  77.         return 0;  
  78.     }  
  79.   
  80.     public void setLogWriter(PrintWriter arg0) throws SQLException {  
  81.           
  82.     }  
  83.   
  84.     public void setLoginTimeout(int arg0) throws SQLException {  
  85.           
  86.     }  
  87.       
  88. }  


然后修改一下JdbcUtils中的代码:

[java] view plain copy
  1. package com.weijia.firstdemo;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.ResultSet;  
  5. import java.sql.SQLException;  
  6. import java.sql.Statement;  
  7.   
  8. import javax.sql.DataSource;  
  9.   
  10. import com.weijia.datasource.MyDataSource;  
  11.   
  12. public class JdbcUtils {  
  13.       
  14.     private static String user = "root";  
  15.     private static String password = "123456";  
  16.     private static String dbName = "test";  
  17.     private static  String url = "jdbc:mysql://localhost:3306/"+dbName+"?user="+user+"&password="+password+"&useUnicode=true&characterEncoding=gb2312&generateSimpleParameterMetadata=true";  
  18.       
  19.     private static MyDataSource dataSource = null;  
  20.       
  21.     /** 
  22.      * 加载驱动 
  23.      */  
  24.     static{  
  25.         try{  
  26.             Class.forName("com.mysql.jdbc.Driver");  
  27.             dataSource = new MyDataSource();//初始化数据源  
  28.         }catch(Exception e){  
  29.             System.out.println("Exception:"+e.getMessage()+"");  
  30.             throw new ExceptionInInitializerError(e);  
  31.         }  
  32.     }  
  33.       
  34.     private JdbcUtils(){  
  35.     }  
  36.       
  37.     /** 
  38.      * 获取连接 
  39.      * @return 
  40.      * @throws SQLException 
  41.      */  
  42.     public static Connection getConnection() throws SQLException{  
  43.         return dataSource.getConnection();  
  44.     }  
  45.       
  46.     public static DataSource getDataSource(){  
  47.         return dataSource;  
  48.     }  
  49.       
  50.     /** 
  51.      * 释放资源 
  52.      * @param rs 
  53.      * @param st 
  54.      * @param conn 
  55.      */  
  56.     public static void free(ResultSet rs,Statement st,Connection conn){  
  57.         try{  
  58.             if(rs != null){  
  59.                 rs.close();  
  60.             }  
  61.         }catch(SQLException e){  
  62.             e.printStackTrace();  
  63.         }finally{  
  64.             try{  
  65.                 if(st != null){  
  66.                     st.close();  
  67.                 }  
  68.             }catch(SQLException e){  
  69.                 e.printStackTrace();  
  70.             }finally{  
  71.                 try{  
  72.                     dataSource.free(conn);  
  73.                 }catch(SQLException e){  
  74.                     e.printStackTrace();  
  75.                 }  
  76.             }  
  77.         }  
  78.               
  79.     }  
  80.   
  81. }  


我们看到,在我们自定义的数据源中,主要有这么几个变量:

初始化连接数,最大连接数,当前的连接数,连接池(因为我们可能需要频繁的添加连接和删除连接所以使用LinkedList,因为这个list是链表结构的,增加和删除效率高)

主要流程是:初始化数据源的时候,初始化一定量的连接放到池子中,当用户使用getConnection()方法取出连接的时候,我们会判断这个连接池中还有没有连接了,有就直接取出第一个连接返回,没有的话,我们在判断当前的连接数有没有超过最大连接数,超过的话,就抛出一个异常(其实这里还可以选择等待其他连接的释放,这个具体实现是很麻烦的),没有超过的话,就创建连接,并且将其放入池子中。

我们自定义的数据源是实现了JDBC中的DataSource接口的,这个接口很重要的,后面我们会说到apache的数据源都是要实现这个接口的,这个接口统一了数据源的标准,这个接口中有很多实现的,所以看到我们的数据源类中有很多没必要的方法,但是那个方法都是要实现的,最重要的就是要实现getConnection方法,其他的实现都只需要调用super.XXX就可以了。


在JdbcUtils类中我们也是需要修改的,首先我们要在静态代码块中初始化我们的数据源,在getConnection方法中调用数据源的getConnection方法,在free方法中调用数据源的free方法即可。


看一下测试类:

[java] view plain copy
  1. package com.weijia.datasource;  
  2.   
  3. import java.sql.Connection;  
  4.   
  5. import com.weijia.firstdemo.JdbcUtils;  
  6.   
  7. public class Test {  
  8.       
  9.     public static void main(String[] args) throws Exception{  
  10.         for(int i=0;i<10;i++){  
  11.             Connection conn = JdbcUtils.getConnection();  
  12.             System.out.println(conn);  
  13.             JdbcUtils.free(nullnull, conn);  
  14.         }  
  15.     }  
  16.   
  17. }  


运行结果:


我们可以看到,我们在测试代码中申请了10个连接,从结果上可以看出前五个是不同的连接,后五个连接和前五个是一样的,这是因为我们在释放连接的时候就是free方法中,是将连接重新放到池子中的,上面显示的是五个,是因为我们初始化的连接数是5个,当第一个连接释放的时候这个连接其实已经放到了池子的第六个位置,以此类推。


下面我们继续来看下个问题,我们在上面的数据源中可以看到,我们定义了一个free方法来释放连接的,然后在JdbcUtils中调用这个方法即可,但是这个貌似不太符合我们的使用习惯,因为之前我们看到我们释放连接的时候都是使用close方法的,所以这里面我们在修改一下,至于怎么修改呢?

首先我们知道那个close方法是JDBC中的Connection接口中的,所有自定义的连接都是需要实现这个接口的,那么我们如果我们想让我们free中的逻辑放到close中的话,就需要实现这个接口了,我们可以看到

[java] view plain copy
  1. DriverManager.getConnection(url)  

通过这种方式获取到的Connection也是mysql中实现了Connection的接口的,那么现在我们可能需要自定一个我们自己的连接,然后实现Connection接口,将free方法中的逻辑搬到close方法中,同时我们还要在连接类中保持一个mysql中的连接对象,这里面的逻辑有点不好理解,先看代码:

[java] view plain copy
  1. package com.weijia.datasource;  
  2.   
  3. import java.sql.CallableStatement;  
  4. import java.sql.Connection;  
  5. import java.sql.DatabaseMetaData;  
  6. import java.sql.PreparedStatement;  
  7. import java.sql.SQLException;  
  8. import java.sql.SQLWarning;  
  9. import java.sql.Savepoint;  
  10. import java.sql.Statement;  
  11. import java.util.Map;  
  12.   
  13. public class MyConnection implements Connection{  
  14.       
  15.     //组合方式:静态代理  
  16.     private Connection realConnection;  
  17.     private MyDataSource2 dataSource;  
  18.     //当前连接的使用的次数  
  19.     private int maxUseCount = 5;  
  20.     private int currentUseCount = 0;  
  21.       
  22.     public MyConnection(Connection conn,MyDataSource2 dataSource){  
  23.         this.realConnection = conn;  
  24.         this.dataSource = dataSource;  
  25.     }  
  26.       
  27.     public void close() throws SQLException {  
  28.         this.currentUseCount++;  
  29.         if(this.currentUseCount < this.maxUseCount){  
  30.             this.dataSource.free(this);  
  31.         }else{  
  32.             dataSource.currentCount--;  
  33.             this.realConnection.close();  
  34.         }  
  35.     }  
  36.   
  37.     public void clearWarnings() throws SQLException {  
  38.         this.realConnection.clearWarnings();  
  39.     }  
  40.   
  41.     public void commit() throws SQLException {  
  42.         this.realConnection.commit();  
  43.     }  
  44.   
  45.     public Statement createStatement() throws SQLException {  
  46.         return this.realConnection.createStatement();  
  47.     }  
  48.   
  49.     public Statement createStatement(int resultSetType, int resultSetConcurrency)throws SQLException {  
  50.         return this.realConnection.createStatement(resultSetType, resultSetConcurrency);  
  51.     }  
  52.   
  53.     public Statement createStatement(int resultSetType,int resultSetConcurrency, int resultSetHoldability)throws SQLException {  
  54.         return this.realConnection.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability);  
  55.     }  
  56.   
  57.     public boolean getAutoCommit() throws SQLException {  
  58.         return this.realConnection.getAutoCommit();  
  59.     }  
  60.   
  61.     public String getCatalog() throws SQLException {  
  62.         return this.realConnection.getCatalog();  
  63.     }  
  64.   
  65.     public int getHoldability() throws SQLException {  
  66.         return this.realConnection.getHoldability();  
  67.     }  
  68.   
  69.     public DatabaseMetaData getMetaData() throws SQLException {  
  70.         // TODO Auto-generated method stub  
  71.         return null;  
  72.     }  
  73.   
  74.     public int getTransactionIsolation() throws SQLException {  
  75.         // TODO Auto-generated method stub  
  76.         return 0;  
  77.     }  
  78.   
  79.     public Map<String, Class<?>> getTypeMap() throws SQLException {  
  80.         // TODO Auto-generated method stub  
  81.         return null;  
  82.     }  
  83.   
  84.     public SQLWarning getWarnings() throws SQLException {  
  85.         // TODO Auto-generated method stub  
  86.         return null;  
  87.     }  
  88.   
  89.     public boolean isClosed() throws SQLException {  
  90.         // TODO Auto-generated method stub  
  91.         return false;  
  92.     }  
  93.   
  94.     public boolean isReadOnly() throws SQLException {  
  95.         // TODO Auto-generated method stub  
  96.         return false;  
  97.     }  
  98.   
  99.     public String nativeSQL(String sql) throws SQLException {  
  100.         // TODO Auto-generated method stub  
  101.         return null;  
  102.     }  
  103.   
  104.     public CallableStatement prepareCall(String sql) throws SQLException {  
  105.         // TODO Auto-generated method stub  
  106.         return null;  
  107.     }  
  108.   
  109.     public CallableStatement prepareCall(String sql, int resultSetType,  
  110.             int resultSetConcurrency) throws SQLException {  
  111.         // TODO Auto-generated method stub  
  112.         return null;  
  113.     }  
  114.   
  115.     public CallableStatement prepareCall(String sql, int resultSetType,  
  116.             int resultSetConcurrency, int resultSetHoldability)  
  117.             throws SQLException {  
  118.         // TODO Auto-generated method stub  
  119.         return null;  
  120.     }  
  121.   
  122.     public PreparedStatement prepareStatement(String sql) throws SQLException {  
  123.         // TODO Auto-generated method stub  
  124.         return null;  
  125.     }  
  126.   
  127.     public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)  
  128.             throws SQLException {  
  129.         // TODO Auto-generated method stub  
  130.         return null;  
  131.     }  
  132.   
  133.     public PreparedStatement prepareStatement(String sql, int[] columnIndexes)  
  134.             throws SQLException {  
  135.         // TODO Auto-generated method stub  
  136.         return null;  
  137.     }  
  138.   
  139.     public PreparedStatement prepareStatement(String sql, String[] columnNames)  
  140.             throws SQLException {  
  141.         // TODO Auto-generated method stub  
  142.         return null;  
  143.     }  
  144.   
  145.     public PreparedStatement prepareStatement(String sql, int resultSetType,  
  146.             int resultSetConcurrency) throws SQLException {  
  147.         // TODO Auto-generated method stub  
  148.         return null;  
  149.     }  
  150.   
  151.     public PreparedStatement prepareStatement(String sql, int resultSetType,  
  152.             int resultSetConcurrency, int resultSetHoldability)  
  153.             throws SQLException {  
  154.         // TODO Auto-generated method stub  
  155.         return null;  
  156.     }  
  157.   
  158.     public void releaseSavepoint(Savepoint savepoint) throws SQLException {  
  159.         // TODO Auto-generated method stub  
  160.           
  161.     }  
  162.   
  163.     public void rollback() throws SQLException {  
  164.         // TODO Auto-generated method stub  
  165.           
  166.     }  
  167.   
  168.     public void rollback(Savepoint savepoint) throws SQLException {  
  169.         // TODO Auto-generated method stub  
  170.           
  171.     }  
  172.   
  173.     public void setAutoCommit(boolean autoCommit) throws SQLException {  
  174.         // TODO Auto-generated method stub  
  175.           
  176.     }  
  177.   
  178.     public void setCatalog(String catalog) throws SQLException {  
  179.         // TODO Auto-generated method stub  
  180.           
  181.     }  
  182.   
  183.     public void setHoldability(int holdability) throws SQLException {  
  184.         // TODO Auto-generated method stub  
  185.           
  186.     }  
  187.   
  188.     public void setReadOnly(boolean readOnly) throws SQLException {  
  189.         // TODO Auto-generated method stub  
  190.           
  191.     }  
  192.   
  193.     public Savepoint setSavepoint() throws SQLException {  
  194.         // TODO Auto-generated method stub  
  195.         return null;  
  196.     }  
  197.   
  198.     public Savepoint setSavepoint(String name) throws SQLException {  
  199.         // TODO Auto-generated method stub  
  200.         return null;  
  201.     }  
  202.   
  203.     public void setTransactionIsolation(int level) throws SQLException {  
  204.         // TODO Auto-generated method stub  
  205.           
  206.     }  
  207.   
  208.     public void setTypeMap(Map<String, Class<?>> map) throws SQLException {  
  209.         // TODO Auto-generated method stub  
  210.           
  211.     }  
  212.   
  213. }  


首先看到了这个类中有很多恶心的代码,那些方法都是Connection接口中的,我们这里只需要实现close方法就可以了,其他的方法中可以添加:

[java] view plain copy
  1. return this.realConnection.XXX  

我们看到会在类中保留一个Connection对象,这个对象就是真实的连接对象,即我们使用

[java] view plain copy
  1. DriverManager.getConnection(url)  

这种方法获取到的,因为我们要在close方法中使用到这个真实的连接

我们看一下close方法吧:

[java] view plain copy
  1. public void close() throws SQLException {  
  2.         this.currentUseCount++;  
  3.         if(this.currentUseCount < this.maxUseCount){  
  4.             this.dataSource.free(this);  
  5.         }else{  
  6.             dataSource.currentCount--;  
  7.             this.realConnection.close();  
  8.         }  
  9.     }  

首先看到我们定义了一个类变量:currentUseCount用来表示当前连接的使用次数,同时还有一个类变量就是maxUseCount表示当前连接的最大使用次数,我们看一下close方法的逻辑:

首先当用户调用close方法的时候当前连接的使用数就加一,这里有些同学可能不能理解,我们想想上面还记得我们释放连接的时候是怎么做的,是将这个连接重新放到池子中,所以这个连接又被用了一次,所以这里面是加一,当这个连接的当前使用次数没有超过他的最大使用次数的话,就还把他放到池子中(就是数据源中的free方法,这个方法中传递的参数是我们自定义的连接对象,因为我们不是真的需要关闭这个连接的),如果使用次数超过了最大使用次数的话,我们就将这个连接真正的释放关闭了,同时需要将数据源中当前的连接数减去一,这里我们是调用真实连接的关闭方法的,所以我们需要在我们自定义的连接中保持一个真实连接的对象,其实我们采用的是组合的方法,在一个要想调用另外类中的方法,我们需要在本类中维持一个他的对象,然后进行调用他特定的方法,这种方式也是一种设计模式叫做:静态代理,相当于我们本类是另外一个类的代理。

同时我们需要在构造函数中传递一个数据源对象进来的,当然我们这时候需要在之前的数据源中修改一下,这里修改很简单的,只需要修改数据源中的createConnection方法就可以了:

[java] view plain copy
  1. private Connection createConnection() throws SQLException{  
  2.         Connection realConn = DriverManager.getConnection(url);  
  3.         MyConnection myConnection = new MyConnection(realConn,this);  
  4.         return myConnection;  
  5.     }  

我们返回的其实是我们自己的定义的连接,这个连接其实也是真实连接的一个代理对象。这样我们在JdbcUtils中的free方法中直接调用:

[java] view plain copy
  1. conn.close();  

而不需要调用:

[java] view plain copy
  1. dataSource.free(conn);  

这样的释放方式就和我们之前普通连接的释放方式是一样的。其实我们上面做的这么多的操作就是为了这个,想让用户能够还是直接调用conn.close方法就可以释放连接,我们还是运行一下之前的测试类:

[java] view plain copy
  1. package com.weijia.datasource;  
  2.   
  3. import java.sql.Connection;  
  4.   
  5. import com.weijia.firstdemo.JdbcUtils;  
  6.   
  7. public class Test {  
  8.       
  9.     public static void main(String[] args) throws Exception{  
  10.         for(int i=0;i<10;i++){  
  11.             Connection conn = JdbcUtils.getConnection();  
  12.             System.out.println(conn);  
  13.             JdbcUtils.free(nullnull, conn);  
  14.         }  
  15.     }  
  16.   
  17. }  

运行结果如下:


我们看到前五个用的是同一个连接对象,这个原因就是我们在我们自定义的连接MyConnection类中使用了当前连接的最大使用次数是5次

我们看到在定义我们自己的连接类的时候,需要实现Connection接口,这个接口中需要实现的方法很多,其实我们只需要一个close方法就可以了,这时候我们还可以将我们的代码在修改一下,下面是我们修改之后的自定义连接类:

[java] view plain copy
  1. package com.weijia.datasource;  
  2.   
  3. import java.lang.reflect.InvocationHandler;  
  4. import java.lang.reflect.Method;  
  5. import java.lang.reflect.Proxy;  
  6. import java.sql.Connection;  
  7.   
  8. public class MyConnectionHandler implements InvocationHandler{  
  9.       
  10.     private Connection realConnection = null;  
  11.     private Connection warpedConnection = null;  
  12.     private MyDataSource2 dataSource = null;  
  13.       
  14.     //当前连接的使用的次数  
  15.     private int maxUseCount = 5;  
  16.     private int currentUseCount = 0;  
  17.       
  18.     public MyConnectionHandler(MyDataSource2 dataSource){  
  19.         this.dataSource = dataSource;  
  20.     }  
  21.       
  22.     public Connection bind(Connection conn){  
  23.         this.realConnection = conn;  
  24.         warpedConnection = (Connection)Proxy.newProxyInstance(this.getClass().getClassLoader(),new Class[]{Connection.class},this);  
  25.         return warpedConnection;  
  26.     }  
  27.   
  28.     public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {  
  29.         if("close".equals(method.getName())){  
  30.             this.currentUseCount++;  
  31.             if(this.currentUseCount < this.maxUseCount){  
  32.                 this.dataSource.free(warpedConnection);  
  33.             }else{  
  34.                 dataSource.currentCount--;  
  35.                 this.realConnection.close();  
  36.             }  
  37.         }  
  38.         return method.invoke(this.realConnection, args);  
  39.     }  
  40.   
  41. }  

这里我们看到了并没有实现Connection接口了,而且代码也是很简洁的,其实这个就是动态代理的模式,我们通过bind方法传递进来一个真实的连接对象,然后使用Proxy类实例化一个代理对象,newProxyInstance方法的参数说明:

第一个:需要代理对象的类加载器

第二个:需要代理对象实现的接口

第三个:InvocationHandler回调接口,我们主要的工具都是实现这个接口中的invoke方法

然后我们在invoke方法中拦截close方法即可,将之前的close方法中的逻辑搬到这里就可以了。我们使用上面的测试代码运行如下:


这里我们就看到了使用动态代理很简单的,但是有一个限制,就是代理对象必须要实现一个接口,这里正好是Connection接口,他比静态代理优雅了很多的,后面我们在说到Spring的时候还会说到这个动态代理模式的


好了,上面我们就可以看到我们自己定义了一个数据源,连接,这样对我们后面的操作优化了很多。


下面我们在来看一下apache的数据源DataSource,其实这个数据源大体上和我们上面设计的以一样的,只是他做了更优化,更好。

首先我们导入需要的jar包:


然后我们定义一个dbcpconfig.properties文件,用于配置数据源的相关信息:

[java] view plain copy
  1. #连接设置  
  2. driverClassName=com.mysql.jdbc.Driver  
  3. url=jdbc:mysql://localhost:3306/test  
  4. username=root  
  5. password=123456  
  6.   
  7. #<!-- 初始化连接 -->  
  8. initialSize=10  
  9.   
  10. #最大连接数量  
  11. maxActive=50  
  12.   
  13. #<!-- 最大空闲连接 -->  
  14. maxIdle=20  
  15.   
  16. #<!-- 最小空闲连接 -->  
  17. minIdle=5  
  18.   
  19. #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 -->  
  20. maxWait=60000  
  21.   
  22.   
  23. #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;]   
  24. #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。  
  25. connectionProperties=useUnicode=true;characterEncoding=gbk;generateSimpleParameterMetadata=true  
  26.   
  27. #指定由连接池所创建的连接的自动提交(auto-commit)状态。  
  28. defaultAutoCommit=true  
  29.   
  30. #driver default 指定由连接池所创建的连接的只读(read-only)状态。  
  31. #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)  
  32. defaultReadOnly=  
  33.   
  34. #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。  
  35. #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE  
  36. defaultTransactionIsolation=READ_UNCOMMITTED  
  37.   
  38. 从这些配置上我们可以看到前面的几个参数的含义就是我们自定义数据源中的使用到的,这里还有一个参数是maxWait是超时,这个就是我们在获取连接的时候,当连接数超过最大连接数的时候,需要等待的时间,在前面我们自己定义的数据源中我们是采用抛出异常的问题来解决的,这里我们看到apache是采用线程等待的方式来解决的。  
  39. 我们在代码里面修改的东西也是很少的,在JdbcUtils中的静态代码块中使用apache的数据源即可:  
  40. //使用Apache的DBCP数据源  
  41. Properties prop = new Properties();  
  42. prop.load(JdbcUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"));  
  43. dataSource = BasicDataSourceFactory.createDataSource(prop);  

apache中的连接也是经过改装的,我们直接调用conn.close方法即可,和我们上面实现的是一样的。


JDBC中CRUD的模板模式

我们从前面的例子中可以看到,我们在操作CRUD的时候,返现有很多重复的代码,比如现在一个UserDao来操作查询操作,写了一段查询代码,然后有一个ProductDao也来操作查询操作,也写了一段查询代码,其实我们会发现这两个查询代码中有很多是重复的,这时候我们就想了,能不能够进行代码的优化,我们想到了模板模式,就是将相同的代码提取出来放到父类中做,不同的代码放到各自的子类中去做,这样重复的代码只会出现一次了,下面来看一下实例,首先我们看一下抽象出来的Dao代码:

[java] view plain copy
  1. package com.weijia.template;  
  2.   
  3. import java.sql.Connection;  
  4. import java.sql.PreparedStatement;  
  5. import java.sql.ResultSet;  
  6. import java.sql.SQLException;  
  7.   
  8. import com.weijia.domain.DaoException;  
  9. import com.weijia.firstdemo.JdbcUtils;  
  10.   
  11. public abstract class AbstractDao {  
  12.       
  13.     /** 
  14.      * 更新 
  15.      */  
  16.     protected int update(String sql,Object[] args) {  
  17.         //这里需要做判断的,可能args为null  
  18.         Connection conn = null;  
  19.         PreparedStatement st = null;  
  20.         try{  
  21.             conn = JdbcUtils.getConnection();  
  22.             st = conn.prepareStatement(sql);  
  23.             for(int i=0;i<args.length;i++){  
  24.                 st.setObject(i+1, args[i]);  
  25.             }  
  26.             int count = 0;  
  27.             count = st.executeUpdate();  
  28.             System.out.println("更新的记录数:"+count);  
  29.             return count;  
  30.         }catch(Exception e){  
  31.             throw new DaoException(e.getMessage(),e);  
  32.         }finally{  
  33.             JdbcUtils.free(null, st, conn);  
  34.         }  
  35.     }  
  36.       
  37.     /** 
  38.      * 查询 
  39.      * @param sql 
  40.      * @param args 
  41.      * @return 
  42.      */  
  43.     protected Object find(String sql,Object[] args){  
  44.         Connection conn = null;  
  45.         PreparedStatement st = null;  
  46.         ResultSet rs = null;  
  47.         try{  
  48.             conn = JdbcUtils.getConnection();  
  49.             st = conn.prepareStatement(sql);  
  50.             for(int i=0;i<args.length;i++){  
  51.                 st.setObject(i+1, args[i]);  
  52.             }  
  53.             rs = st.executeQuery();  
  54.             Object obj = null;  
  55.             while(rs.next()){  
  56.                 //不同的部分放到子类去做  
  57.                 obj = rowMapper(rs);  
  58.             }  
  59.             return obj;  
  60.         }catch(Exception e){  
  61.             throw new DaoException(e.getMessage(),e);  
  62.         }finally{  
  63.             JdbcUtils.free(null, st, conn);  
  64.         }  
  65.     }  
  66.       
  67.     //子类需要实现的结果集处理方法  
  68.     protected abstract Object rowMapper(ResultSet rs) throws SQLException;  
  69.   
  70. }  


看一下UserDaoImpl类:

[java] view plain copy
  1. package com.weijia.template;  
  2.   
  3. import java.sql.ResultSet;  
  4. import java.sql.SQLException;  
  5.   
  6. import com.weijia.domain.User;  
  7.   
  8. public class UserDaoImpl extends AbstractDao{  
  9.   
  10.     /** 
  11.      * 更新用户信息 
  12.      */  
  13.     public int update(User user) {  
  14.         String sql = "udpate user set name=?,birthday=?,money=?,where id=?";  
  15.         Object[] args = new Object[]{user.getName(),user.getBirthday(),user.getMoney(),user.getId()};  
  16.         return super.update(sql, args);//相同的代码调用父类的方法即可  
  17.     }  
  18.       
  19.     /** 
  20.      * 删除用户 
  21.      * @param user 
  22.      */  
  23.     public void delete(User user){  
  24.         String sql = "delete from user where id=?";  
  25.         Object[] args = new Object[]{user.getId()};  
  26.         super.update(sql, args);  
  27.     }  
  28.       
  29.     /** 
  30.      * 查找用户 
  31.      * @param loginName 
  32.      * @param password