首页 > 技术文章 > JDBC入门

youngchaolin 2019-09-05 12:06 原文

接下来记录一下,JDBC的相关知识点

为什么使用JDBC

不同的数据库有不同的驱动,为了使用这些数据库如果没有JDBC就需要每个驱动都需要了解,但是有了JDBC后,就只需要了解JDBC的API就可以了。JDBC是数据库驱动的上层,里面主要包含一些接口,而各个数据库厂商需遵循这个接口来开发驱动。

 

组成JDBC需要2个包的支持,其为java.sql包和javax.sql包,另外使用哪个数据库还需要对应数据库的驱动包,如mysql选择mysql的驱动包,可以理解为驱动包为具体接口实现,而JDBC可以理解为包含这些实现的接口。

如何实现JDBC

step1 注册数据库驱动

step2 获取数据库连接Connection

step3 创建传输器Statement或PreparedStatement

step4 传输sql并返回结果集ResultSet

step5 遍历结果集

step6 关闭资源

实现JDBC最原始方式

接下来使用最原始的方式实现以上步骤,参考如下代码。

 1 package com.boe.jdbc;
 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 com.mysql.jdbc.Driver;
10 
11 public class ConnectionDemo {
12 
13     public static void main(String[] args) throws SQLException {
14         //1 注册数据库驱动
15         DriverManager.registerDriver(new Driver());
16         //2 获取数据库连接
17         Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2", "root", "2688");
18         //3 创建传输器
19         Statement stmt=conn.createStatement();
20         //4 创建sql会返回结果
21         ResultSet rs=stmt.executeQuery("SELECT *FROM dept");
22         //5 遍历结果
23         while(rs.next()){//next()会移动行的光标,如果移动到的行有数据返回true,否则返回false,初始化位置在第一行前面
24             //获取表字段数据
25             int id=rs.getInt("id");
26             String name=rs.getString("name");
27             System.out.println("id="+id+",name="+name);
28         }
29         //6 关闭资源  后创建的先关闭
30         rs.close();//不关闭,数据库数据会留在内存
31         stmt.close();//这个关闭会自动关闭rs
32         conn.close();
33 
34     }
35 
36 }

控制台输出情况。

优化JDBC连接方式

以上为最原始的方式,但是存在问题,即DriverManager会注册两次,因为底层Driver有一个静态方法也会注册一次,因此这里采用反射加载驱动类来实现注册,具体参考代码。

底层静态代码块加载后会注册一次。

 1 package com.boe.jdbc;
 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 com.mysql.jdbc.Driver;
10 
11 public class ConnectionDemo1 {
12 
13     public static void main(String[] args) {
14         //1 注册数据库驱动
15         //问题1 手动注册了一次,底层代码也注册了一次,注册了两次驱动实际上,只需要注册一次即可
16         //问题2 包名和代码绑定死,如果更换数据库,需要修改包名
17         //DriverManager.registerDriver(new Driver());
18         //定义成员变量
19         Connection conn=null;
20         Statement stmt=null;
21         ResultSet rs=null;
22         try {
23             Class.forName("com.mysql.jdbc.Driver");
24             //利用反射API获取一次Driver类,会自动执行一次Driver类中的静态方法
25             //2 获取数据库连接
26             //Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2", "root", "2688");
27             //下面的连接方式写法也可以,参考文档26连接器
28             conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2?user=root&password=2688");
29             //3 创建传输器
30             stmt=conn.createStatement();
31             //4 创建sql会返回结果
32             rs=stmt.executeQuery("SELECT * FROM dept");
33             //5 遍历结果
34             while(rs.next()){//next()会移动行的光标,如果移动到的行有数据返回true,否则返回false,初始化位置在第一行前面
35                 //获取表字段数据
36                 int id=rs.getInt("id");
37                 String name=rs.getString("name");
38                 System.out.println("id="+id+",name="+name);
39             }
40         } catch (Exception e) {
41             e.printStackTrace();
42             throw new RuntimeException(e);
43         }finally{
44             //6 关闭资源  后创建的先关闭
45             try {
46                 if(rs!=null){
47                     rs.close();//不关闭,数据库数据会留在内存
48                 }
49             } catch (SQLException e) {
50                 e.printStackTrace();
51                 //可以选择抛出运行时异常,不会打断程序
52                 throw new RuntimeException(e);
53             }finally{
54                 rs=null;
55             }            
56             try {
57                 if(stmt!=null){
58                     stmt.close();//这个关闭会自动关闭rs                    
59                 }
60             } catch (SQLException e) {
61                 e.printStackTrace();
62                 throw new RuntimeException(e);
63             }finally{
64                 stmt=null;
65             }            
66             try {
67                 if(conn!=null){
68                     conn.close();                    
69                 }
70             } catch (SQLException e) {
71                 e.printStackTrace();
72                 throw new RuntimeException(e);
73             }finally{            
74                 conn=null;
75             }
76         }        
77     }
78 }

使用优化后的JDBC实现CRUB操作

以下简单的实现了CRUB操作,使用优化后注册方式。

  1 package com.boe.jdbc;
  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 org.junit.Test;
 10 
 11 import com.boe.utils.JDBCUtils;
 12 
 13 public class ConnectionDemo3 {
 14     //添加操作
 15     @Test
 16     public void add(){
 17         Connection conn=null;
 18         Statement stmt=null;
 19         try {
 20             //1 注册数据库驱动
 21             Class.forName("com.mysql.jdbc.Driver");
 22             //2 创建数据库连接
 23             conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2?user=root&password=2688");
 24             //3 获取传输器
 25             stmt=conn.createStatement();
 26             //4 输出sql 获取结果集
 27             int i=stmt.executeUpdate("INSERT INTO dept Values(null,'文娱部')");
 28             if(i>0){
 29                 System.out.println("插入成功,受到影响的行数为:"+i+"行");
 30             }
 31         } catch (Exception e) {
 32             e.printStackTrace();
 33         }finally{
 34             //后创建的先关闭
 35             try {
 36                 if(stmt!=null){
 37                     stmt.close();                
 38                 }
 39             } catch (SQLException e) {
 40                 e.printStackTrace();
 41             }finally{
 42                 stmt=null;
 43             }
 44             try {
 45                 if(conn!=null){
 46                     conn.close();    
 47                 }
 48             } catch (SQLException e) {
 49                 e.printStackTrace();
 50             }finally{
 51                 conn=null;
 52             }
 53         }
 54     }
 55     
 56     //更新数据的方法
 57     @Test
 58     public void update(){
 59         Connection conn=null;
 60         Statement stmt=null;
 61         try{
 62             Class.forName("com.mysql.jdbc.Driver");
 63             conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2?user=root&password=2688");
 64             stmt=conn.createStatement();
 65             int i=stmt.executeUpdate("UPDATE dept SET name='IT部门' WHERE id=6");
 66             if(i>0){
 67                 System.out.println("更新成功,受到影响的行数为:"+i+"行");                
 68             }
 69         }catch(Exception e){
 70             e.printStackTrace();
 71         }finally{
 72             //后创建的先关闭
 73             try {
 74                 if(stmt!=null){
 75                     stmt.close();                
 76                 }
 77             } catch (SQLException e) {
 78                 e.printStackTrace();
 79             }finally{
 80                 stmt=null;
 81             }
 82             try {
 83                 if(conn!=null){
 84                     conn.close();    
 85                 }
 86             } catch (SQLException e) {
 87                 e.printStackTrace();
 88             }finally{
 89                 conn=null;
 90             }            
 91         }
 92     }
 93     
 94     //删除数据
 95     @Test
 96     public void delete(){
 97         Connection conn=null;
 98         Statement stmt=null;
 99         try{
100             conn=JDBCUtils.getConnection();
101             stmt=conn.createStatement();
102             int i=stmt.executeUpdate("DELETE FROM dept WHERE id=8");
103             if(i>0){
104                 System.out.println("删除成功,受到影响的行数为:"+i+"行");                
105             }
106         }catch(Exception e){
107             e.printStackTrace();
108         }finally{
109             JDBCUtils.closeConnection(conn, stmt, null);
110         }
111     }
112 }

生成工具类DBUtils

在使用的过程中发现很多代码需要重复写,如类加载和获取连接,以及关闭资源等,这些反复书写的代码可以提取出来,写到一个工具类里面,这里将创建连接需要的驱动类名,url,用户名和密码都保存在了一个properties文件里面,这样如果需要更改数据库就只需要修改配置文件即可。

 1 package com.boe.utils;
 2 
 3 import java.io.File;
 4 import java.io.FileInputStream;
 5 import java.io.FileNotFoundException;
 6 import java.io.IOException;
 7 import java.sql.Connection;
 8 import java.sql.DriverManager;
 9 import java.sql.ResultSet;
10 import java.sql.SQLException;
11 import java.sql.Statement;
12 import java.util.Properties;
13 
14 //工厂类:不允许创建对象,方法为静态方法
15 public class JDBCUtils {
16     //不允许创建对象,构造方法私有化    
17     private JDBCUtils(){
18         
19     }
20     //prop变成静态,代表只创建一次
21     private static Properties prop=new Properties();
22     static{
23         try {
24             prop.load(new FileInputStream(new File(JDBCUtils.class.getClassLoader().getResource("conf.properties").getPath())));
25         } catch (FileNotFoundException e) {
26             e.printStackTrace();
27         } catch (IOException e) {
28             e.printStackTrace();
29         }
30     }
31     //写静态方法
32     public static Connection getConnection() throws Exception{
33         //将配置信息加载prop
34         //Properties prop=new Properties();
35         //prop.load(new FileInputStream(new File(JDBCUtils.class.getClassLoader().getResource("conf.properties").getPath())));
36         //获取prop里的内容
37         String driver=prop.getProperty("driver");
38         String url=prop.getProperty("url");
39         String user=prop.getProperty("user");
40         String pwd=prop.getProperty("password");
41         //Class.forName("com.mysql.jdbc.Driver");
42         Class.forName(driver);
43         //2 创建数据库连接
44         //return DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2?user=root&password=2688");
45         return DriverManager.getConnection(url,user,pwd);
46     }
47     public static void closeConnection(Connection conn,Statement stmt,ResultSet rs){
48         //后创建的先关闭
49         try {
50             if(rs!=null){
51                 rs.close();                
52             }
53         } catch (SQLException e) {
54             e.printStackTrace();
55         }finally{
56             rs=null;
57         }
58         try {
59             if(stmt!=null){
60                 stmt.close();                
61             }
62         } catch (SQLException e) {
63             e.printStackTrace();
64         }finally{
65             stmt=null;
66         }
67         try {
68             if(conn!=null){
69                 conn.close();    
70             }
71         } catch (SQLException e) {
72             e.printStackTrace();
73         }finally{
74             conn=null;
75         }
76     }
77 }

使用工具类完成登录模拟

在完成工具类的泛化后,接下来使用它可以完成一个简单的登录案例,其中引出PreparedStatement和SQL注入,

如果使用Statement,会有SQL注入的风险,即SQL后台拼接后如果参数里有SQL关键字,可能导致整条SQL语义的变化导致不需要输入密码也可以登录的情况,如用户名后加上' #,或者密码里加上'OR ' 1=1。这样的情况下,PreparedStatement就应用而生,它是Statement的一个子接口,使用它将先将SQL进行预编译,参数只是用?占位符来代替,后面传入参数也只是将其作为文本来处理,不会导致语义的变化。

 1 package com.boe.jdbc;
 2 
 3 import java.sql.Connection;
 4 import java.sql.PreparedStatement;
 5 import java.sql.ResultSet;
 6 import java.sql.Statement;
 7 import java.util.Scanner;
 8 
 9 import com.boe.utils.JDBCUtils;
10 
11 //登录
12 public class Login {
13     //用户输入用户名和密码,和数据库中的进行比较,如果匹配则提示登录成功,否则提示失败
14     public static void main(String[] args) {
15         //获取控制台输入的用户名和密码
16         Scanner scan=new Scanner(System.in);
17         System.out.println("请输入用户名:");
18         String user=scan.nextLine();
19         System.out.println("请输入密码:");
20         String pwd=scan.nextLine();
21         //测试是否正确
22         testLogin( user,pwd);        
23         //testPreparedStatementLogin(user,pwd);
24         
25     }
26     /**
27      * 访问数据库,使用Preparedstatement根据用户名和密码进行访问
28      * @param user
29      * @param pwd
30      */
31     private static void testPreparedStatementLogin(String user, String pwd) {
32         Connection conn=null;
33         PreparedStatement ps=null;
34         ResultSet rs=null;
35         try {
36             conn=JDBCUtils.getConnection();
37             String sql="SELECT * FROM user WHERE username=? AND password=? ";
38             ps=conn.prepareStatement(sql);
39             ps.setString(1, user);
40             ps.setString(2, pwd);
41             rs=ps.executeQuery();
42             if(rs.next()){
43                 System.out.println("登录成功");
44             }else{
45                 System.out.println("登录失败");
46             }
47         } catch (Exception e) {
48             e.printStackTrace();
49         }finally{
50             //PreparedStatement继承自Statement接口,所以可以直接用
51             JDBCUtils.closeConnection(conn, ps, rs);
52         }
53             
54     }
55     /**
56      * 访问数据库,根据用户名和密码进行访问
57      * @param user
58      * @param pwd
59      */
60     private static void testLogin(String user, String pwd) {
61         Connection conn=null;
62         Statement stmt=null;
63         ResultSet rs=null;
64         try {
65             conn=JDBCUtils.getConnection();
66             stmt=conn.createStatement();
67             String sql="SELECT * FROM user WHERE username='"+user+"' AND password='"+pwd+"'";
68             rs=stmt.executeQuery(sql);
69             //打印sql,查看sql注入攻击
70             System.out.println(sql);
71             if(rs.next()){
72                 System.out.println("登录成功");
73             }else{
74                 System.out.println("登录失败");
75             }
76         } catch (Exception e) {
77             e.printStackTrace();
78         }finally{
79             JDBCUtils.closeConnection(conn, stmt, rs);
80         }
81     }
82     
83 }

不使用预编译的结果,可以看到可以sql注入。

批量处理

在介绍完上面的基本操作后,JDBC还提供批量处理的API,可以实现SQL批量处理,其有两种选择,一个是使用Statement的批处理,一个是使用PreparedStatement的批处理,两者各有优缺点。前者的话就是可以放入不同结构的SQL,但是没有预编译效率低,后者是有预编译效率高,但是更适合有相同结构但是参数不同的SQL。

Statement实现批处理

 1 package com.boe.batch;
 2 
 3 import java.sql.Connection;
 4 import java.sql.Statement;
 5 
 6 import com.boe.utils.JDBCUtils;
 7 
 8 //批处理
 9 /*
10  *   create table t1(id int primary key auto_increment,name varchar(20));
11  *   insert into t1 values(null,'鸣人');
12  *   insert into t1 values(null,'向日');
13  *   insert into t1 values(null,'卡卡西');
14  *   insert into t1 values(null,'大蛇');
15  *   
16  * */
17 public class StatementBatch {
18 
19     public static void main(String[] args) {
20         Connection conn=null;
21         Statement stmt=null;
22         try{
23             conn=JDBCUtils.getConnection();
24             stmt=conn.createStatement();
25             stmt.addBatch("create table t1(id int primary key auto_increment,name varchar(20))");
26             stmt.addBatch(" insert into t1 values(null,'鸣人')");
27             stmt.addBatch(" insert into t1 values(null,'向日')");
28             stmt.addBatch(" insert into t1 values(null,'卡卡西')");
29             stmt.addBatch(" insert into t1 values(null,'大蛇')");
30             int[] arr=stmt.executeBatch();
31             System.out.println(arr.toString());
32             System.out.println("批处理执行完成");
33         }catch(Exception e){
34             e.printStackTrace();
35         }finally{
36             JDBCUtils.closeConnection(conn, stmt, null);
37         }
38     }
39 
40 }

PreparedStatement实现批处理

 1 package com.boe.batch;
 2 
 3 import java.sql.Connection;
 4 import java.sql.PreparedStatement;
 5 
 6 import com.boe.utils.JDBCUtils;
 7 
 8 /*
 9  *   create table t1(id int primary key auto_increment,name varchar(20));
10  *   insert into t1 values(null,'鸣人');
11  *   insert into t1 values(null,'向日');
12  *   insert into t1 values(null,'卡卡西');
13  *   insert into t1 values(null,'大蛇');
14  *   
15  * */
16 public class PrepareBatch {
17 
18     public static void main(String[] args) {
19         //PrepareStatement比较适合sql主干一致的
20         Connection conn=null;
21         PreparedStatement ps=null;
22         try{
23             conn=JDBCUtils.getConnection();
24             ps=conn.prepareStatement("INSERT INTO t1 values(null,?)");
25             //添加批处理
26             for(int i=1;i<1000;i++){
27                 ps.setString(1, "小明"+i);
28                 ps.addBatch();
29                 if(i%100==0){
30                     //每隔1000条执行一次
31                     ps.executeBatch();
32                     //执行完的批处理可以清空
33                     ps.clearBatch();
34                     System.out.println("第"+i/100+"批处理完成");
35                 }
36             }
37             //批量处理,收尾
38             ps.executeBatch();
39             System.out.println("全部处理完成");
40         }catch(Exception e){
41             e.printStackTrace();
42         }finally{
43             JDBCUtils.closeConnection(conn, ps, null);
44         }
45     }
46 
47 }

 

数据库连接池

在大量数据库连接的情况下,显然普通的连接会给数据库造成巨大的压力,因为会频繁的创建连接删除连接,这种操作特别的消耗资源,在这样的情况下数据库连接池就应用而生,它会将获取到的数据库连接保存到一个池子中,需要连接就取不需要了就归还连接。下面实现一个简单的连接池,只包含获取归还连接,并且还将连接包装了close方法,这样实现调用连接的close方法只是将连接归还到连接池,具体参考代码。

手写数据库简单连接池

(1)连接池类需要实现DataSource接口,并写一个包装类包装Connection。

  1 package com.boe.pool;
  2 
  3 import java.io.PrintWriter;
  4 import java.sql.Connection;
  5 import java.sql.DriverManager;
  6 import java.sql.SQLException;
  7 import java.sql.SQLFeatureNotSupportedException;
  8 import java.util.LinkedList;
  9 import java.util.List;
 10 import java.util.logging.Logger;
 11 
 12 import javax.sql.DataSource;
 13 
 14 public class MyConnectionPool implements DataSource{
 15     //在MyConnectionPool加载的时候初始化一批连接
 16     public static List<Connection> pool=new LinkedList<Connection>();
 17     static{
 18         try {
 19             Class.forName("com.mysql.jdbc.Driver");
 20             for(int i=0;i<5;i++){
 21                 Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2", "root", "2688");
 22                 pool.add(conn);
 23             }
 24         } catch (Exception e) {
 25             e.printStackTrace();
 26         }
 27     }
 28 
 29     @Override
 30     public PrintWriter getLogWriter() throws SQLException {
 31         // TODO Auto-generated method stub
 32         return null;
 33     }
 34 
 35     @Override
 36     public void setLogWriter(PrintWriter out) throws SQLException {
 37         // TODO Auto-generated method stub
 38         
 39     }
 40 
 41     @Override
 42     public void setLoginTimeout(int seconds) throws SQLException {
 43         // TODO Auto-generated method stub
 44         
 45     }
 46 
 47     @Override
 48     public int getLoginTimeout() throws SQLException {
 49         // TODO Auto-generated method stub
 50         return 0;
 51     }
 52 
 53     @Override
 54     public Logger getParentLogger() throws SQLFeatureNotSupportedException {
 55         // TODO Auto-generated method stub
 56         return null;
 57     }
 58 
 59     @Override
 60     public <T> T unwrap(Class<T> iface) throws SQLException {
 61         // TODO Auto-generated method stub
 62         return null;
 63     }
 64 
 65     @Override
 66     public boolean isWrapperFor(Class<?> iface) throws SQLException {
 67         // TODO Auto-generated method stub
 68         return false;
 69     }
 70 
 71     //取出连接
 72     @Override
 73     public Connection getConnection() throws SQLException {
 74         //取出连接前先判断
 75         if(pool.size()>0){
 76             Connection conn=pool.remove(0);//移除一个连接,并将它返回
 77             System.out.println("还有"+pool.size()+"条连接");
 78             //返回之前先包装连接
 79             DecorateConnection connDeco=new DecorateConnection(conn, this);
 80             return connDeco;
 81         }else{
 82             //如果没有就初始化一批连接
 83             for(int i=0;i<5;i++){
 84                 Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb2", "root", "2688");
 85                 pool.add(conn);
 86             }
 87             Connection conn=pool.remove(0);//移除一个连接,并将它返回
 88             System.out.println("还有"+pool.size()+"条连接");
 89             //返回之前先包装连接
 90             DecorateConnection connDeco=new DecorateConnection(conn, this);
 91             return conn;
 92         }    
 93     }
 94     //归还连接
 95     public void returnConnection(Connection conn) throws Exception{
 96         //判断连接还在,并且没有关闭,再归还连接
 97         if(conn!=null&&!conn.isClosed()){
 98             pool.add(conn);
 99             System.out.println("还有"+pool.size()+"条连接");
100         }
101     }
102 
103     @Override
104     public Connection getConnection(String username, String password)
105             throws SQLException {
106         // TODO Auto-generated method stub
107         return null;
108     }
109 
110 }

包装类,实现部分方法。

 1 package com.boe.pool;
 2 
 3 import java.sql.Array;
 4 import java.sql.Blob;
 5 import java.sql.CallableStatement;
 6 import java.sql.Clob;
 7 import java.sql.Connection;
 8 import java.sql.DatabaseMetaData;
 9 import java.sql.NClob;
10 import java.sql.PreparedStatement;
11 import java.sql.SQLClientInfoException;
12 import java.sql.SQLException;
13 import java.sql.SQLWarning;
14 import java.sql.SQLXML;
15 import java.sql.Savepoint;
16 import java.sql.Statement;
17 import java.sql.Struct;
18 import java.util.Map;
19 import java.util.Properties;
20 import java.util.concurrent.Executor;
21 
22 //Connection的包装类,重写close方法
23 public class DecorateConnection implements Connection{
24     //属性
25     private Connection conn;
26     private MyConnectionPool pool;
27     //构造方法将自己写的连接池导入,可以调用里面的方法,进行装饰
28     public DecorateConnection(Connection conn,MyConnectionPool pool) {
29         this.conn=conn;
30         this.pool=pool;
31     }    
32     //重写close方法,这里实现将连接归还到连接池
33     @Override
34     public void close() throws SQLException {
35         try {
36             pool.returnConnection(conn);
37         } catch (Exception e) {
38             e.printStackTrace();
39         }finally{
40             conn=null;
41         }        
42     }
43     //重写下,因为包装类测试暂时用的方法不多,先重写它
44     @Override
45     public Statement createStatement() throws SQLException {
46         System.out.println("包装类的createStatement方法执行了");
47         return conn.createStatement();
48     }
49     
50 ......省略其他重写方法
51 
52 }

测试一下连接池,是否销毁连接后有归还连接。

 1 package com.boe.pool;
 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 org.junit.Test;
10 
11 import com.boe.utils.JDBCUtils;
12 //测试连接池
13 public class TestPool {
14     //添加操作
15     @Test
16     public void add(){
17         Connection conn=null;
18         Statement stmt=null;
19         MyConnectionPool pool=new MyConnectionPool();
20         try {
21             //此时得到的连接是包装好的
22             conn=pool.getConnection();
23             //3 获取传输器
24             stmt=conn.createStatement();
25             //4 输出sql 获取结果集
26             int i=stmt.executeUpdate("INSERT INTO dept VALUES(null,'文娱部')");
27             if(i>0){
28                 System.out.println("插入成功,受到影响的行数为:"+i+"行");
29             }
30         } catch (Exception e) {
31             e.printStackTrace();
32         }finally{
33             //后创建的先关闭
34             try {
35                 if(stmt!=null){
36                     stmt.close();                
37                 }
38             } catch (SQLException e) {
39                 e.printStackTrace();
40             }finally{
41                 stmt=null;
42             }
43             try {
44                 if(conn!=null){
45                     //归还连接
46                     //pool.returnConnection(conn);
47                     //使用了包装类后,直接关闭连接
48                     conn.close();
49                 }
50             } catch (Exception e) {
51                 e.printStackTrace();
52             }finally{
53                 conn=null;
54             }
55         }
56     }
57 }

这是一个简单的实现,可以看到刚开始使用一条后还有4条连接,执行完插入数据库操作后,使用close方法后连接池又变回了5条连接,并没有销毁连接。但是实际上一般不使用自己写的,而使用厂商提供的连接池,比较常见的就是DBCP和C3P0,其中后者性能上要好一些。

DHCP连接池的使用

DHCP是Apache下的,使用需要导两个包,包commons-dbcp.jar是用于连接池实现,commons-pool.jar是提供连接池资源库,其使用有如下两种常见方法,但是建议使用配置文件的方式来初始化连接池,如下是直接在代码中初始化连接池,使用DataSource的实现类BasicDataSource。

 1 package com.boe.pool;
 2 
 3 import java.sql.Connection;
 4 import java.sql.PreparedStatement;
 5 import java.sql.ResultSet;
 6 import java.sql.SQLException;
 7 
 8 import org.apache.commons.dbcp.BasicDataSource;
 9 
10 //测试DBCP连接池
11 public class DPCPDemo1 {
12 
13     public static void main(String[] args) {
14         Connection conn=null;
15         PreparedStatement ps=null;
16         ResultSet rs=null;
17         //使用dbcp连接池获取连接
18         BasicDataSource bs=new BasicDataSource();
19         //设置连接池属性,driver,url,name,password
20         bs.setDriverClassName("com.mysql.jdbc.Driver");
21         bs.setUrl("jdbc:mysql://localhost:3306/mydb2");//也可以不写localhost:3306
22         bs.setUsername("root");
23         bs.setPassword("2688");
24         //获取连接
25         try {
26             conn=bs.getConnection();
27             ps=conn.prepareStatement("SELECT * FROM exam");
28             rs=ps.executeQuery();
29             while(rs.next()){
30                 int id=rs.getInt("id");
31                 String name=rs.getString("name");
32                 System.out.println(id+":"+name);
33             }
34         } catch (SQLException e) {
35             e.printStackTrace();
36         }finally{
37             //不是正的关闭,而是归还连接
38             try {
39                 conn.close();
40             } catch (SQLException e) {
41                 e.printStackTrace();
42             }
43         }
44     }
45 }

如果使用配置文件,需要注意用户名需要修改为username,普通mysql连接写的是user,写好配置文件后,加载资源,然后使用工厂类来创建BasicDataSource。

 1 package com.boe.pool;
 2 
 3 import java.io.File;
 4 import java.io.FileInputStream;
 5 import java.sql.Connection;
 6 import java.sql.PreparedStatement;
 7 import java.sql.ResultSet;
 8 import java.sql.SQLException;
 9 import java.util.Properties;
10 
11 import javax.sql.DataSource;
12 
13 import org.apache.commons.dbcp.BasicDataSourceFactory;
14 
15 //测试DBCP连接池
16 public class DPCPDemo2 {
17 
18     public static void main(String[] args) {
19         Connection conn=null;
20         PreparedStatement ps=null;
21         ResultSet rs=null;
22         //获取连接
23         try {
24             //使用工厂类
25             BasicDataSourceFactory factory=new BasicDataSourceFactory();
26             //需要传入properties类型参数
27             Properties prop=new Properties();
28             prop.load(new FileInputStream(new File(DPCPDemo2.class.getClassLoader().getResource("dbcp.properties").getPath())));
29             //使用工厂方法,将有数据库连接信息的prop作为参数传入,得到连接池,这里使用接口引用指向
30             DataSource ds=factory.createDataSource(prop);
31             conn=ds.getConnection();
32             ps=conn.prepareStatement("SELECT * FROM exam");
33             rs=ps.executeQuery();
34             while(rs.next()){
35                 int id=rs.getInt("id");
36                 String name=rs.getString("name");
37                 System.out.println(id+":"+name);
38             }
39         } catch (Exception e) {
40             e.printStackTrace();
41         }finally{
42             //不是正的关闭,而是归还连接
43             try {
44                 conn.close();
45             } catch (SQLException e) {
46                 e.printStackTrace();
47             }
48         }
49     }
50 }

properties文件配置如下,可以参考。

1 # 设置mysql驱动,DBCP下的配置
2 driverClassName=com.mysql.jdbc.Driver
3 # 设置url
4 url=jdbc:mysql://localhost:3306/数据库名
5 # user
6 username=数据库用户名
7 # password
8 password=数据库密码

C3P0连接池的使用

C3P0是另外一款数据库连接池,使用也需要导包,这里使用版本为c3p0-0.9.1.2.jar,性能有博客说上要优于DBCP,它也可以直接代码传入连接参数使用,也可以使用配置文件,建议后者。

 1 package com.boe.pool;
 2 
 3 import java.beans.PropertyVetoException;
 4 import java.sql.Connection;
 5 import java.sql.PreparedStatement;
 6 import java.sql.ResultSet;
 7 
 8 import com.boe.utils.JDBCUtils;
 9 import com.mchange.v2.c3p0.ComboPooledDataSource;
10 //c3p0连接池的普通使用
11 public class C3P0Demo {
12 
13     public static void main(String[] args) {
14         Connection conn=null;
15         PreparedStatement ps=null;
16         ResultSet rs=null;
17         ComboPooledDataSource source=new ComboPooledDataSource();
18         try {
19             source.setDriverClass("com.mysql.jdbc.Driver");
20             source.setJdbcUrl("jdbc:mysql:///mydb2");
21             source.setUser("root");
22             source.setPassword("2688");
23             conn=source.getConnection();
24             ps=conn.prepareStatement("SELECT * FROM exam");
25             rs=ps.executeQuery();
26             while(rs.next()){
27                 int id=rs.getInt("id");
28                 String name=rs.getString("name");
29                 System.out.println(id+":"+name);
30             }        
31         } catch (Exception e) {
32             e.printStackTrace();
33         }finally{
34             JDBCUtils.closeConnection(conn, ps, rs);
35         }
36     }
37 
38 }

如果使用配置文件,可以参考C3P0的文档来配置,本文是将配置文件放到src目录下使用。以下为文档内容,可以用来参考。

Named and Per-User configuration: Overriding c3p0 defaults via c3p0-config.xml

As of c3p0-0.9.1, you can define multiple configurations in an XML configuration file, and specify in your code which configuration to use. For any configurations (including the unnamed default configuration), you can define overrides for a particular database user. For example, if several applications access your database under different authentication credentials, you might define maxPoolSizeto be 100 for user highVolumeApp, but only 10 for user lowLoadApp. (Recall that Connections associated with different authentication credentials are of necessity separated into separate pools, so it makes sense that these could be configured separately.)

You can use the XML config file for all c3p0 configuration, including configuration of defaults. However, for users who don't want or need the extra complexity, the c3p0.properties file will continue to be supported.

By default, c3p0 will look for an XML configuration file in its classloader's resource path under the name "/c3p0-config.xml". That means the XML file should be placed in a directly or jar file directly named in your applications CLASSPATH, in WEB-INF/classes, or some similar location.

If you prefer not to bundle your configuration with your code, you can specify an ordinary filesystem location for c3p0's configuration file via the system property com.mchange.v2.c3p0.cfg.xml.

Here is an example c3p0-config.xml file:

 1 <c3p0-config>
 2   <default-config>
 3     <property name="automaticTestTable">con_test</property>
 4     <property name="checkoutTimeout">30000</property>
 5     <property name="idleConnectionTestPeriod">30</property>
 6     <property name="initialPoolSize">10</property>
 7     <property name="maxIdleTime">30</property>
 8     <property name="maxPoolSize">100</property>
 9     <property name="minPoolSize">10</property>
10     <property name="maxStatements">200</property>
11 
12     <user-overrides user="test-user">
13       <property name="maxPoolSize">10</property>
14       <property name="minPoolSize">1</property>
15       <property name="maxStatements">0</property>
16     </user-overrides>
17 
18   </default-config>
19 
20   <!-- This app is massive! -->
21   <named-config name="intergalactoApp"> 
22     <property name="acquireIncrement">50</property>
23     <property name="initialPoolSize">100</property>
24     <property name="minPoolSize">50</property>
25     <property name="maxPoolSize">1000</property>
26 
27     <!-- intergalactoApp adopts a different approach to configuring statement caching -->
28     <property name="maxStatements">0</property> 
29     <property name="maxStatementsPerConnection">5</property>
30 
31     <!-- he's important, but there's only one of him -->
32     <user-overrides user="master-of-the-universe"> 
33       <property name="acquireIncrement">1</property>
34       <property name="initialPoolSize">1</property>
35       <property name="minPoolSize">1</property>
36       <property name="maxPoolSize">5</property>
37       <property name="maxStatementsPerConnection">50</property>
38     </user-overrides>
39   </named-config>
40 </c3p0-config>
View Code
 1 package com.boe.pool;
 2 
 3 import java.beans.PropertyVetoException;
 4 import java.sql.Connection;
 5 import java.sql.PreparedStatement;
 6 import java.sql.ResultSet;
 7 
 8 import com.boe.utils.JDBCUtils;
 9 import com.mchange.v2.c3p0.ComboPooledDataSource;
10 //c3p0连接池的使用xml配置文件
11 public class C3P0Demo1 {
12 
13     public static void main(String[] args) {
14         Connection conn=null;
15         PreparedStatement ps=null;
16         ResultSet rs=null;
17         ComboPooledDataSource source=new ComboPooledDataSource();
18         try {
19             /*source.setDriverClass("com.mysql.jdbc.Driver");
20             source.setJdbcUrl("jdbc:mysql:///mydb2");
21             source.setUser("root");
22             source.setPassword("2688");*/
23             conn=source.getConnection();
24             ps=conn.prepareStatement("SELECT * FROM exam");
25             rs=ps.executeQuery();
26             while(rs.next()){
27                 int id=rs.getInt("id");
28                 String name=rs.getString("name");
29                 System.out.println(id+":"+name);
30             }        
31         } catch (Exception e) {
32             e.printStackTrace();
33         }finally{
34             JDBCUtils.closeConnection(conn, ps, rs);
35         }
36     }
37 
38 }

c3p0的配置文件名字使用c3p0-config.xml,如果上面ComboPooled里没有传入参数,读取的是配置在default-config里的信息,如果传入参数则读取named-config里的配置信息。

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <c3p0-config>
 3   <default-config>
 4     <property name="driverClass">com.mysql.jdbc.Driver</property>
 5     <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb2</property>
 6     <property name="user">root</property>
 7     <property name="password">2688</property>
 8   </default-config>
 9 
10   <!-- 可以配置很多,name中自己写名字,根据名字可以切换数据源,在ComboPooledDataSource()作为参数传入 -->
11   <named-config name="intergalactoApp"> 
12 
13   </named-config>
14 </c3p0-config>

以上即为JDBC的基础知识。

参考博文:

(1)https://www.jianshu.com/p/1564631ed0d4 C3P0

推荐阅读