JDBC:从原理到应用
一、是为何物
1、概念
JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。简言之,JDBC就是Java用于执行SQL语句实现数据库操作的API。
如果不好理解我们可以先看一下驱动程序的概念:
驱动程序:指的是设备驱动程序(Device Driver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作,假如某设备的驱动程序未能正确安装,便不能正常工作。简言之就是操作系统通过一段代码来操作设备,而这段代码就称为该设备的驱动程序,是操作系统与硬件之间的桥梁。
同理,数据库驱动程序就是提供用于操作数据库的一段代码。我们现在想要使用纯Java来操作数据库,肯定不能直接调用这段代码,为了更便捷和灵活地使用高级语言直接对数据库进行管理,JDBC因此而产生,JDBC是一套协议,是JAVA开发人员和数据库厂商达成的协议,也就是由Sun定义一组接口,由数据库厂商来实现,并规定了JAVA开发人员访问数据库所使用的方法的调用规范。
2、分类
据访问数据库的技术不同, JDBC 驱动程序相应地分为四种类型。不同类型的驱动程序有着不一样的特性和使用方法。
- JDBC-ODBC桥驱动程序(JDBC-ODBC Bridge Driver)
此类驱动程序由JDBC-ODBC桥和一个ODBC驱动程序组成。其工作原理是,通过一段本地C代码将JDBC调用转化成ODBC调用。这一类型必须在本地计算机上先安装好ODBC驱动程序,然后通过JDBC-ODBCBridge的转换,将Java程序中使用的JDBCAPI访问指令转化成ODBCAPI指令,进而通过ODBC驱动程序调用本地数据库驱动代码完成对数据库的访问。 - 部分Java的本地JDBCAPI驱动程序
JDBC API驱动程序(Anative API partly Java technology-enabled Driver)此类驱动程序也必须在本地计算机上先安装好特定的驱动程序(类似ODBC),然后通过PartialJavaJDBCDriver的转换,把Java程序中使用的JDBC API转换成NativeAPI,进而存取数据库。
- 纯Java的数据库中间件驱动程序(目前主流驱动)
纯Java的数据库中间件驱动程序(Pure Java Driver for Database Middleware)使用这类驱动程序时,不需要在本地计算机上安装任何附加软件,但是必须在安装数据库管理系统的服务器端加装中间件(Middleware),这个中间件负责所有存取数据库时必要的转换。其工作原理是:驱动程序将JDBC访问转换成与数据库无关的标准网络协议(通常是HTTP或HTTPS)送出,然后再由中间件服务器将其转换成数据库专用的访问指令,完成对数据库的操作。中间件服务器能支持对多种数据库的访问。
- 纯Java的JDBC驱动程序
纯Java的JDBC驱动程序(Direct-to-DatabasePureJavaDriver)这类驱动程序是直接面向数据库的纯Java驱动程序,即所谓的"瘦"驱动程序。使用这类驱动程序时无需安装任何附加的软件(无论是本地计算机或是数据库服务器端),所有存取数据库的操作都直接由JDBC驱动程序来完成,此类驱动程序能将JDBC调用转换成DBMS专用的网络协议,能够自动识别网络协议下的特殊数据库并能直接创建数据连接。
第一类最节省投资。但执行效率比较低,不适合对大数据量存取的应用;第二种方式具有开放性,但不够兼容使用较麻烦;
第三类驱动程序是由纯Java语言开发而成的,并且中间件也仅需要在服务器上安装,不再需要客户端的本机代码,这类驱动程序的体积最小,效率较高,具有最大的灵活性。而且,此类驱动采用标准的网络协议,可以被防火墙支持,是开发Applet程序理想的选择(其实这些驱动是为Applet特别编写的),是Internet应用理想的解决方案。另外,开发者还可以利用单一的驱动程序连接到多种数据库。由于此种JDBC驱动程序提供了许多企业级的特征,因而非常适合用户的特殊用途,如:SSL安全、分布式事务处理等。如果用户未来扩张应用需要多个数据库,则选择这种方式是最理想的。由于它具有较好的性能和安全性,广泛应用于基于Web的应用系统的开发。其不足之处是:需要在服务器端安装中间件,这适当影响了驱动程序的效率。
由于我们主流选择的JDBC驱动为第三类,下面给出第三类的JDBC驱动程序结构图:
从图可见,JDBC驱动程序为两层结构,它由驱动程序客户端程序和驱动程序服务器端程序组成。客户端直接与用户交互,它为用户提供符合JDBC规范的数据库统一编程接口,客户端将数据库请求通过特定的网络协议传送给服务器。服务器充当中间件的角色,它负责接收和处理用户的请求以及支持对多种数据库的操作。JDBC驱动程序自身并不直接与数据库交互,而是借助于其他已实现的数据库驱动程序,成为“雇主”。可以想象,它“雇佣”的数据库驱动程序越多,则可支持的数据库就越多,“雇佣”的数据库驱动程序越强大,则类型3JDBC驱动程序的功能也越强大。除此之外,“雇佣”的数据库驱动程序的数量和成员可以动态改变,以满足系统扩展的需求。第三类 JDBC驱动程序的这一特性正是它强大的根源所在。
二、动手测一测(以MySQL为例)
了解了JDBC的大致原理,我们学会使用才是关键。
1、环境
既然要使用API,我们就需要导入JDBC运行环境所需要的jar包,而不同的数据库jar包也是不同的,下面以MySQL数据库为例,导入下列jar包(百度一堆或者去官网下载):
2、一般步骤
- 注册驱动
- 获取连接对象(url、username、password)
- 编写SQL语句
- 获取语句执行对象
- (给占位符设置参数)
- 执行SQL语句
- 获得执行后的结果集
- 处理结果集
3、Java代码
下面看具体实现代码:
-
1 public void JDBCDemo(String pname,int price) throws ClassNotFoundException, Exception{ 2 //1、注册驱动 3 Class.forName("com.mysql.jdbc.Driver"); 4 //2、获取连接对象 5 Connection conn = DriverManager.getConnection( 6 "jdbc:mysql://localhost:3306/web08?useUnicode=true&characterEncoding=utf-8&useSSL=false", 7 "root", "password"); 8 //sql语句 9 String sql = "select * from product where pname=? and price=?"; 10 //3、获得sql语句执行对象 11 PreparedStatement ps = conn.prepareStatement(sql); 12 //4、设置参数给占位符 13 ps.setString(1, pname); 14 ps.setInt(2, price); 15 //5、执行并保存结果集 16 ResultSet rs = ps.executeQuery(); 17 //6、处理结果集 18 if(rs.next()){ 19 System.out.println(rs.getString(2)+":"+rs.getInt(3)); 20 }else 21 System.out.println("查询失败"); 22 if(rs!=null){ 23 rs.close(); 24 } 25 if(ps!=null){ 26 ps.close(); 27 } 28 if(conn!=null){ 29 conn.close(); 30 } 31 } 32 }
-
程序员在使用Java操作数据库时只需要按照给定的规则进行编程即可操作数据库。下面是常用api的详细讲解。
三、API详解
1、注册驱动程序
Class.forName(),是最常见的注册JDBC驱动程序的方法,注册某数据库就将该数据库驱动的名称以字符串的形式作为参数:
例如我们需要注册MySQL的数据库驱动,则使用代码:
-
1 Class.froName("com.mysql.jdbc.Driver");
另外还可以使用DriverManage的静态方法:DriverManager.registerDriver()
来注册驱动,参数为对应的数据库驱动对象。
2、获得连接对象Connection
可以使用 DriverManager.getConnection()
方法建立连接。根据传入参数的不同,有三种重载的DriverManager.getConnection()
方法:
getConnection(String url)
getConnection(String url, Properties prop)
getConnection(String url, String user, String password)
这里每个格式都需要一个数据库URL。 数据库URL是指向数据库的地址。制定数据库URL是建立连接相关联的大多数错误问题发生的地方。各数据库对应的URL如下所示:
假设我们现在需要连接MySQL数据库,格式为:jdbc:mysql://hostname:port/datebaseName。我们需要的信息是hostname主机名和端口号,一般默认为localHost:3306;还需要datebaseName数据库名,假设为freeDB;当然还有URL格式未包含的也是必须的信息:连接数据库的用户名和密码,假设为Leslie和030401。那么就有URL:
-
1 String url = "jdbc:mysql//localhost:3306/freeDB";
下面分别使用三种方法来实现:
- 使用一个URL作为参数的方式:需要将username+password以参数的形式放到URL中,但是每种数据库的放置都不太相同
//连接mysql的纯URL String url = "jdbc:mysql//localhost:3306/freeDB?username=leslie&password=030401";
Connection conn = DriverManager.getConnection(url,p);
//连接Oracle的纯URL
String url = "jdbc:oracle:thin:leslie/030401@192.0.0.10:1521:freeDB";
Connection conn = DriverManager.getConnection(url); - 使用URL、properties作为参数的方式:即需要将username和password以键值对形式存放在properties对象中作为参数
1 //MySql 2 String url = "jdbc:mysql//localhost:3306/freeDB"; 3 Properties p = new Properties(); 4 p.put("username","leslie"); 5 p.put("password","030401"); 6 Connection conn = DriverManager.getConnection(url,p);
- 使用URL、username、password三个参数分开的方式(推荐)
1 String url = "jdbc:mysql//localhost:3306/freeDB"; 2 String username = "leslie"; 3 String password = "030401"; 4 Connection conn = DriverManager.getConnection(url,username,password);
3、获得SQL语句执行对象
SQL语句的执行对象按理说有Statement和PreparedStatement两个,但我们一般都不会去使用Statement,先看下两者的基本描述:
- Statement 是 Java 执行数据库操作的一个重要接口,用于在已经建立数据库连接的基础上,向数据库发送要执行的SQL语句。Statement对象,用于执行不带参数的简单SQL语句,即静态SQL语句。
- PreparedStatement 继承于Statement。实例包含已编译的 SQL 语句,这就是使语句“准备好”。包含于 PreparedStatement 对象中的 SQL 语句可具有一个或多个 IN 参数。IN参数的值在 SQL 语句创建时未被指定。相反的,该语句为每个 IN 参数保留一个问号(“?”)作为占位符。每个问号的值必须在该语句执行之前,通过适当的setXXX() 方法来提供。
简言之,Statement执行静态SQL语句,而它的子类PreparedStatement执行预编译SQL,即可传入参数。两者相比之下,PreparedStatement有以下优势:
- 预编译处理,可动态执行SQL语句。很明显,SQL语句的预编译,使用占位符?去代替未知数据,因而一个句子可以执行多种不同的SQL,而Statement需要重新书写SQL语句,笨重。
- 速度快,执行效率高。SQL语句会预编译在数据库系统中。执行计划同样会被缓存起来,它允许数据库做参数化查询。使用预处理语句比普通的查询更快,因为它做的工作更少(数据库对SQL语句的分析,编译,优化已经在第一次查询前完成了)。我们要利用预编译的特性,比如第一种查询和第二种查询,第二种才是预编译形式,而第一种其实是恢复了父类Statement的做法:
1 //第一种方式,追加字符串:没有进行预编译,所以效率低 2 String loanType = getLoanType(); 3 PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=" + loanType); 4 5 //第二种方式,使用占位符,进行预编译,效率高(推荐) 6 PreparedStatement prestmt = conn.prepareStatement("select banks from loan where loan_type=?"); 7 prestmt.setString(1,loanType);
- 可防止SQL注入式攻击。静态SQL语句说到底还是字符串,所以存在拼字符串的而带来的注入式SQL攻击风险。比如某个网站的登录验证SQL查询代码为:
1 String sql = "SELECT * FROM users WHERE name = '" + userName + "' and pw = '"+ passWord +"';"
验证需要用户输入用户名和密码,正确则执行查询语句(登录),但如果这样输入:
userName = "1' OR '1'='1"; passWord = "1' OR '1'='1";
那么执行语句就变成了:
1 String sql = "SELECT * FROM users WHERE name = '1' OR '1'='1' and pw = '1' OR '1'='1';"
这样,where语句恒为真,就能实现无账号登录。此外便可能被恶意修改甚至删除数据表。然而使用PreparedStatement的参数化的查询可以阻止大部分的SQL注入。在使用参数化查询的情况下,数据库系统(eg:MySQL)不会将参数的内容视为SQL指令的一部分来处理,而是在数据库完成SQL指令的编译后,才套用参数运行,且占位符?不允许多值,只能填入一个值,因此就算参数中含有破坏性的指令,也不会被数据库所运行。
说了那么多,PreparedStatement如何获取呢?PreparedStatement是和连接对象Connection关联的,所以我们需要使用Connection对象的PreparedStatement(sql)方法进行获取:
-
String sql = "select * from user where name=? and ange=?";//预处理,需要我们先写好sql语句 PreparedStatement ps = conn.preparedStatement(sql);//conn是连接对象,参数为sql语句
4、给占位符设置参数
根据需要的参数类型,使用setXXX()的方法即可。(注:从第一个问好起是从1开始编号)
-
1 ps.setString(1,"tom"); 2 ps.setInt(2,18);
5、执行SQL语句
sql语句有增删查改等几种类型,所以执行方法有以下三种:
execute():执行SQL语句,可以是任何种类的 SQL 语句。返回值是boolean类型。
executeQuery()
:至少SQL语句查询,查询结果返回为ResultSet
对象。executeUpdate()
:执行更新语句。该语句必须是一个 SQL 数据操作语言(Data Manipulation Language,DML)语句,比如INSERT
、UPDATE
或DELETE
语句;或者是无返回内容的 SQL 语句,比如 DDL 语句。返回值是int。
例如本例中的语句是查询语句,所以执行代码为:
-
1 ResultSet rs = ps.executeQuery();
6、处理结果或结果集
如果返回值是boolean或者int很好处理,但如果是查询结果集ResultSet对象,一般使用while循环来处理:
ResultSet
对象具有指向其当前数据行的光标。最初,光标被置于第一行之前。next()
方法将光标移动到下一行;因为该方法在 ResultSet
对象没有下一行时返回 false
,所以可以在 while
循环中使用它来迭代结果集。另外,可以使用ResultSet对象的getXXX(int columnIndex)获得游标所在行指定列的值。原理如下图所示:
所以,本例的结果集处理如下:
-
1 while(rs.next()){ 2 system.out.println(rs.getString(1)); 3 system.out.println(rs.getInt(2)); 4 }
7、关闭连接
在JDBC程序结束之后,显式地需要关闭与数据库的所有连接以结束每个数据库会话。 但是,如果在编写程序中忘记了关闭也没有关系,Java的垃圾收集器将在清除过时的对象时也会关闭这些连接。
依靠垃圾收集,特别是数据库编程,是一个非常差的编程实践。所以应该要使用与连接对象关联的close()
方法关闭连接。要确保连接已关闭,可以将关闭连接的代码中编写在“finally
”块中。 一个finally
块总是会被执行,不管是否发生异常。
-
conn.close();
四、工具类JDBCUtils的使用
在使用JDBC连接数据库的过程中,我们发现大多数步骤都是一样的,而我们平常使用数据库的频率如果很高所以我们需要对JDBC的通用代码进行提炼,提高代码复用率,提炼出来的工具类我们一般称为JDBCUtils,工具类中包含了我们常用的很多方法,比如连接数据库和断开连接就是常用的方法,我们只要掌握了JDBC原理,就可以自己设计满足需求工具类或参考以下工具类(后面我们会说到DBUtils工具类,这是Apache组织提供的JDBC工具类,比较全面,基本能够满足我们的需求,本文最后一节会进行详解):
-
1 package cn.jdbc; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.sql.Connection; 6 import java.sql.DriverManager; 7 import java.sql.PreparedStatement; 8 import java.sql.ResultSet; 9 import java.sql.SQLException; 10 import java.util.Properties; 11 12 /** 13 * @author Fzz 14 * @date 2017年11月12日 15 * @Description TODO:提供获取连接和释放资源的方法 16 */ 17 public class jdbcUtil{ 18 /* 19 * 获取Properties有两种方法: 20 * 一是使用io流获取文件内容, 21 * 二是使用jdk提供的专门获取Properties配置文件的工具类:ResourceBundle 22 */ 23 private static String driver; 24 private static String url; 25 private static String username; 26 private static String password; 27 static{ 28 //1、通过本类获取类加载器 29 ClassLoader classLoader = jdbcUtil_v3.class.getClassLoader(); 30 //2、使用类加载器获得输入流 31 InputStream is = classLoader.getResourceAsStream("jdbc.properties"); 32 //3、Properties工具类 33 Properties prop = new Properties(); 34 //4、加载流 35 try { 36 prop.load(is); 37 } catch (IOException e) { 38 e.printStackTrace(); 39 } 40 //5、 41 driver = prop.getProperty("driver"); 42 url = prop.getProperty("url"); 43 username = prop.getProperty("username"); 44 password = prop.getProperty("password"); 45 } 46 /** 47 * @Title: getConnection 48 * @Description: TODO(获取mySql连接 ) 49 * @return: Connection 50 */ 51 public static Connection getConnection(){ 52 Connection conn = null; 53 try { 54 //注册驱动 55 Class.forName(driver); 56 //获取连接 57 conn = DriverManager.getConnection(url,username,password); 58 } catch (Exception e) { 59 e.printStackTrace(); 60 } 61 return conn; 62 } 63 64 /** 65 * @Title: release 66 * @Description: TODO:释放连接mySql的资源 67 * @return: void 68 */ 69 public static void release(Connection conn,PreparedStatement ps,ResultSet rs){ 70 try{ 71 if(rs != null){ 72 rs.close(); 73 } 74 if(ps != null){ 75 ps.close(); 76 } 77 if(conn != null){ 78 conn.close(); 79 } 80 }catch(SQLException e){ 81 e.printStackTrace(); 82 } 83 } 84 }
五、JDBC共享连接池的使用
对于频繁使用数据库或使用人数较多的项目,连接对象Connection的开开关关也是不好的吖,我们就可以设置JDBC连接池,连接池就相当于一个存放若干连接对象的池子,由于用户人数多,每次访问数据库需要一个连接对象,那么就从池子里取出一个,用完放回即可,而不是销毁。这样,就可以实现连接池共享的目的,减少系统资源消耗。
Java提供了一个公共接口:Javax.sql.DataSource。此接口提供了 DataSource
对象所表示的物理数据源的连接。作为 DriverManager
工具的替代项,DataSource
对象是获取连接的首选方法。
简单来说,就是DateSource接口是Drivermanager的替代项,提供了getConnection()方法并生产标准的Connection对象,那么要实现连接池,就需要实现该接口和该方法。(所以我们常说的数据源也就是连接池)连接池处理自定义的方式,目前主要的连接池工具有C3P0(主流)、DBCP。
1、自定义共享连接池
一般步骤:
- 实现数据源DataSource,即实现Javax.sql.DataSource接口;由于只是简单的演示,我们只实现其中的getConnection()方法即可。
- 创建一个LinkList容器。既然是“池子”,就需要保存东西,即存储连接池对象,而连接池涉及移除/添加连接池对象,优先考虑使用LinkList来存储。
- 使用静态代码块初始化若干个连接池对象。由于只是测试,我们初始化3个就行。
- 实现getConnection()方法。注意,为了保证连接对象只提供给一个线程(一个用户)使用,我们需要先将连接对象从池子中取出来。
- 用完的连接对象不需要执行close()而是放回池子去。
-
1 package cn.jdbcUtils; 2 3 import java.io.PrintWriter; 4 import java.sql.Connection; 5 import java.sql.SQLException; 6 import java.sql.SQLFeatureNotSupportedException; 7 import java.util.LinkedList; 8 import java.util.logging.Logger; 9 10 import javax.sql.DataSource; 11 12 import cn.day09.jdbc.jdbcUtil; 13 14 /** 15 * @author Fzz 16 * @date 2017年11月12日 17 * @Description TODO:自定义JDBC连接池 18 */ 19 20 public class MyDataSource implements DataSource{//[1]实现接口 21 //[2]创建一个容器存储连接池里的Connection对象。 22 private static LinkedList<Connection> pool = new LinkedList<Connection>(); 23 24 //[3]初始化3个Connection对象放进池子。 25 static{ 26 Connection conn = null; 27 for (int i = 0; i < 3; i++) { 28 conn = jdbcUtil.getConnection();//这里我们使用上面创建的jdbcUtils来获取连接 29 pool.add(conn); 30 } 31 } 32 33 @Override 34 /** 35 * [4]从池子里取连接对象 36 */ 37 public Connection getConnection() throws SQLException { 38 //使用前先判断连接池是否有连接对象,没有则添加 39 Connection conn = null; 40 if(pool.size() == 0){ 41 for (int i = 0; i < 3; i++) { 42 conn = jdbcUtil.getConnection(); 43 pool.add(conn); 44 } 45 } 46 conn = pool.removeFirst();//取出来 47 return conn; 48 } 49 50 /** 51 * [5]用完归还连接到连接池 52 */ 53 public boolean returnConnToPool(Connection conn){ 54 return pool.add(conn); 55 } 56 57 //下面是未实现的方法。 58 @Override 59 public PrintWriter getLogWriter() throws SQLException { 60 // TODO Auto-generated method stub 61 return null; 62 } 63 @Override 64 public void setLogWriter(PrintWriter out) throws SQLException { 65 // TODO Auto-generated method stub 66 67 } 68 @Override 69 public void setLoginTimeout(int seconds) throws SQLException { 70 // TODO Auto-generated method stub 71 72 } 73 @Override 74 public int getLoginTimeout() throws SQLException { 75 // TODO Auto-generated method stub 76 return 0; 77 } 78 @Override 79 public Logger getParentLogger() throws SQLFeatureNotSupportedException { 80 // TODO Auto-generated method stub 81 return null; 82 } 83 @Override 84 public <T> T unwrap(Class<T> iface) throws SQLException { 85 // TODO Auto-generated method stub 86 return null; 87 } 88 @Override 89 public boolean isWrapperFor(Class<?> iface) throws SQLException { 90 // TODO Auto-generated method stub 91 return false; 92 } 93 @Override 94 public Connection getConnection(String username, String password) throws SQLException { 95 // TODO Auto-generated method stub 96 return null; 97 } 98 }
【扩展:方法的增强】
自定义共享连接池到了这里基本已经完成,但美中不足的是:之前我们使用连接对象是这样的,用getConnection()方法获取,用完再用close()关闭。现在我们通过共享连接池来使用连接对象,是用getConnection()取出连接对象,使用returnConnToPool()归还连接对象。而美中不足的就是,我们希望通过共享连接池使用连接对象是仍然使用getConnection()和close()这两个方法,以保持前后一致的美感(呵呵),但我们是实现DataSource接口,接口中没有close()方法。可能你会说这是多余,就直接使用returnConnToPool()不挺好的吗,还能加深理解。
其实,这里我只是作为另一个知识点的引申——方法的增强,以此找了个小小的借口。(碧池)
目前有四种方法可以进行某个方法的增强,但各有优缺点:
- 使用继承增强方法。
使用前提是需要实现继承关系,子类通过继承父类方法,进而改进方法。 - 使用装饰者设计模式增强方法。
装饰者设计模式是专门用来增强方法的设计模式,使用前提是需要装饰者实现被装饰对象(即被增强对象)相同接口,以实现交互。装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。装饰者模式比继承灵活,但程序也相对复杂。 - 使用动态代理增强方法。(难点)
动态代理与装饰者模式类似,且需要使用反射技术。 - 使用字节码增强方法。(难点)
Java字节码增强指的是在Java字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修改。Java字节码增强的应用场景主要是减少冗余代码,对开发人员屏蔽底层的实现细节。字节码增强技术主要有两种实现机制:一种是通过创建原始类的一个子类;另一种是直接去修改原先的class字节码。
目前有cglib、Javassist等框架提供使用。
上面只是简单的介绍,有意者可以自行搜索。此处不是本文重点故不再赘述,其实是笔者自己也没学。(只想对笔者说一句:“多捞啊”)
2、C3P0连接池的使用
C3P0是开源的共享连接池,比如Sring、Hibernate等开源项目都有使用C3P0。C3P0是第三方工具,除了需要导出相应jar包,还需要编写配置文件 c3p0-config.xml。
- 导包
我们首先去下载一个C3P0压缩包(自行百度)。然后解压找到lib目录,导入第一个包即可: - 编写配置文件:c3p0-config.xml
其实不一定非要配置文件,但相关设置比较麻烦建议编写。在压缩包的doc目录下有 index.html是c3p0的帮助文档(英文版):
帮助文档中就有配置文件相关格式的说明,这里总结如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <c3p0-config> 3 4 <!-- 默认配置1--> 5 <default-config> 6 <连接数据库的一些基本参数> 7 <property name="driverClass">com.mysql.jdbc.Driver</property> 8 <property name="jdbcUrl">jdbc:mysql:///FreeDB</property> 9 <property name="user">leslie</property> 10 <property name="password">030401</property> 11 <property name="initialPoolSize">5</property> 12 <property name="maxPoolSize">20</property> 13 </default-config> 14 15 <!-- 配置2命名 --> 16 <named-config name="mysql_web09"> 17 <property name="driverClass">数据库驱动</property> 18 <property name="jdbcUrl">数据库url</property> 19 <property name="user">用户名</property> 20 <property name="password">密码</property> 21 <property name="initialPoolSize">最小连接数</property> 22 <property name="maxPoolSize">最大连接数</property> 23 </named-config> 24 25 </c3p0-config>
- C3P0Utils
c3p0提供了一个工具类可以方便我们的使用,免去了多余类和方法的学习和直接使用:
1 package cn.c3p0Utils; 2 3 import java.sql.Connection; 4 import java.sql.SQLException; 5 import com.mchange.v2.c3p0.ComboPooledDataSource; 6 7 /** 8 * C3P0工具类 9 * @author Fzz 10 * @date 2017年11月12日12 */ 13 public class C3P0Utils { 14 //创建c3p0的连接池对象(注:ComboPooledDataSource空参数时使用默认配置,否则传入需要使用的配置名字即可) 15 private static ComboPooledDataSource datasource = new ComboPooledDataSource(); 16 /** 17 * 返回DataSource 18 * @Title: getDataSource 19 * @Description: TODO:获得连接池对象 20 * @return: ComboPooledDataSource 21 */ 22 public static ComboPooledDataSource getDataSource(){ 23 return datasource;//将连接池对象返回即可 24 } 25 /** 26 * 返回Connection 27 * @Title: getConnection 28 * @Description: TODO:获得连接对象(从池子中取) 29 * @return: Connection 30 */ 31 public static Connection getConnection(){ 32 try { 33 return datasource.getConnection(); 34 } catch (SQLException e) { 35 throw new RuntimeException(e); 36 } 37 } 38 }
注:这里C3P0Utils提供没有进行连接对象的归还方法,这是因为一我们在使用完连接对象conn后直接使用conn.close()即可归还。(c3p0使用代理已经增强了close()方法不是关闭是归还连接池,和上文拓展说的一个意思),二是后面我们要使用DBUtils工具类进行数据库操作,而DBUtils底层会自动维护连接对象,固此处不再提供归还方法。
六、使用DBUtils工具类进行数据库测CRUD(增查改删)
上文我们也说到JDBC工具类对提高代码复用率的重要性,DBUtils是Apache大佬提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时又不会影响程序的性能。
1、准备工作
- 导包:既然是第三方工具,就需要导包,包括数据库驱动包、DBUtils包和连接池包(这里我们使用C3P0连接池):
- 准备连接池:比如编写c3p0工具类和配置文件。
- JavaBean类的编写。JavaBean是一种JAVA语言规范写成可重用组件的类。写成JavaBean的类,类必须是具体的和公共的,并且具有无参数的构造器。JavaBean 通过提供符合一致性设计模式的公共方法将内部域暴露成员属性,set和get方法获取。众所周知,属性名称符合这种模式,其他Java 类可以通过自省机制(反射机制)发现和操作这些JavaBean 的属性。比如我的数据库中有一个category表,那么我就可以设计一个JavaBean类。
1 package cn.domain; 2 3 public class category { 4 private String cid; 5 private String cname; 6 public category(){ 7 8 } 9 public String getCid() { 10 return cid; 11 } 12 public void setCid(String cid) { 13 this.cid = cid; 14 } 15 public String getCname() { 16 return cname; 17 } 18 public void setCname(String cname) { 19 this.cname = cname; 20 } 21 22 }
2、三个核心类
在使用之前我们先学习DBUtils的三个核心类:提供SQL语句操作API的QueryRunner类、用于执行select语句后进行结果集封装的ResultSetHandler接口、提供资源关闭、处理事务等方法的DbUtils工具类。
- QueryRunner类:
new QueryRunner(DateSource ds):提供连接数据源(连接池)的方式。
update(String sql,Object...params):执行更新操作。
query(String sql,ResultSetHandler<T> rs,Object...paramas):执行查询操作。 - ResultSetHandler接口:(重点常用的三个已标红)
- DbUtils工具类:
closeQuietly(Connection conn):关闭连接,如果有异常try后不抛。
commitAndCloseQuietly(Connection conn):提交事务并关闭连接。
rollbackAndCloseQuietly(Connection conn):回滚事务并关闭连接。
3、实现CRUD操作
这里举一个例子作为参考,其他步骤基本一致:
1 //使用DBUtils查询数据库 2 @Test 3 public void queryAll(){ 4 try { 5 //1、使用QueryRunner获得数据源(连接池) 6 QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); 7 //2、编写sql语句 8 String sql = "select * from category"; 9 //3、执行sql语句 10 List<category> query = qr.query(sql,new BeanListHandler<category>(category.class)); 11 //4、处理结果 12 for(category c:query){ 13 System.out.println(c.getCid()+":"+c.getCname()); 14 } 15 } catch (SQLException e) { 16 throw new RuntimeException(e); 17 } 18 }