Java的JDBC原生态学习以及连接池的用法

转自:http://www.oseye.net/user/kevin/blog/269


JDBC是什么

JDBC(Java Data Base Connectivity)是Java访问数据库的桥梁,但它只是接口规范,具体实现是各数据库厂商提供的驱动程序(Driver)。

应用程序、JDBC、JDBC驱动、数据库之间的关系如上图:应用程序通过JDBC访问数据库,而JDBC驱动来具体实现JDBC访问数据库的动作。

JDBC版本

JDBC的版本和JDK版本是独立的,他们的对应关系:

JDK版本DB版本
JDK 1.1JDBC1
JDK 1.2, 1.3JDBC2
JDK 1.4, 1.5JDBC3
JDK 1.6JDBC4
JDK 1.7JDBC4.1

JDBC4.0新特性

JDBC 4.0中增加的主要特性包括:

  • JDBC驱动类的自动加载;
    不必再使用Class.forName()方法明确加载JDBC驱动。当调用getConnection方法 时,DriverManager会尝试从初始化时已经加载的JDBC驱动程序库中选择合适的驱动,以及他在当前应用的同一个类加载器中明确加载使用过的驱动。
  • 连接管理的增强
  • 在JDBC 4.0之前,我们依靠JDBC URL来定义一个数据源连接。现在我们只需为标准连接工厂机制提供一组参数,就能获取与任何数据源的连接。
  • 对RowId SQL类型的支持
    增加了RowID接口以支持ROWID数据类型。
  • SQL的DataSet实现使用了Annotations;
    JDBC 4.0对标注(Java SE 5新增)作了进一步规范和补充,他允许开发人员不必再写大量代码就能达到联系SQL查询和Java类的目的。
  • SQL异常处理的增强;
    JDBC 4.0在SQLException处理中有不少增强,在处理SQLException时提供了更好的开发体验,如新的SQLException子类、对因果关系的支持、对改进的for-each循环的支持。
  • 对SQL XML的支持;

JDBC链接步骤

  1. 链接数据库;
  2. 创建执行声明;
  3. 执行Sql语句;
  4. 处理结果;
  5. 释放资源;

以MySql为例,制作了一个DEMO(也可以了解下我早期入门的一个DEMO)。

pom.xml添加MySql驱动依赖:

 
 
  1. <dependency>
  2. <groupId>mysql</groupId>
  3. <artifactId>mysql-connector-java</artifactId>
  4. <version>5.1.30</version>
  5. </dependency>
完整的App.java代码

  
  
  1. package net.oseye.DbDemo;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.ResultSet;
  6. import java.sql.Statement;
  7.  
  8. public class App
  9. {
  10. public static void main( String[] args ) throws Exception
  11. {
  12. //链接数据库
  13. Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root","root");
  14. //创建执行声明
  15. Statement stmt=conn.createStatement();
  16. //执行Sql语句
  17. ResultSet rs=stmt.executeQuery("select user_id,user_name from user");
  18. //处理结果集
  19. while(rs.next()){
  20. System.out.println("用户ID"+rs.getInt(1)+"\t用户名:"+rs.getString(2));
  21. }
  22. //释放资源
  23. rs.close();
  24. stmt.close();
  25. conn.close();
  26. }
  27. }
相关API在此不做详细介绍,请查看JDK API文档。

JDBC预处理PreparedStatement

上面的示例有两个缺点:

  1. 使用DriverManager获得数据库连接。这种方式效率低,并且其性能、可靠性和稳定性随着用户访问量得增加逐渐下降,应该使用连接池取而代之(后文再讲);
  2. 应该使用PreparedStatement取代Statement,因为Statement的缺点也非常明显:
    • 执行时发送sql,影响效率.
    • 同样的sql,每次都要发送,不能进行有效的缓存,是一种资源的浪费.
    • 示例代码中演示可以看出,为了防止恶意数据我们还需要编写附加的程序(过滤器)带来不必要的开支.
    • 拼接sql字符串很容易出现错误.

PreparedStatement是Statement的子接口,它在执行sql之前首先准备好sql语句,将其中的条件通过?进行占位,还有一个好处就是,同样的sql会被PreparedStatement有效的缓存,也就是说,数据库会减少校验的过程,减少消耗,这就是我们常说的预处理命令方式。


  
  
  1. package net.oseye.DbDemo;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.PreparedStatement;
  6. import java.sql.ResultSet;
  7.  
  8. public class App
  9. {
  10. public static void main( String[] args ) throws Exception
  11. {
  12. //链接数据库
  13. Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root","root");
  14. //创建执行声明
  15. PreparedStatement pstmt=conn.prepareStatement("select user_id,user_name from user where user_id < ?");
  16. //填充占位符?
  17. pstmt.setInt(1, 3);
  18. //执行Sql语句
  19. ResultSet rs=pstmt.executeQuery();
  20. //处理结果集
  21. while(rs.next()){
  22. System.out.println("用户ID"+rs.getInt(1)+"\t用户名:"+rs.getString(2));
  23. }
  24. //释放资源
  25. rs.close();
  26. pstmt.close();
  27. conn.close();
  28. }
  29. }

JDBC调用存储过程请使用“CallableStatement”接口。

JDBC事务

数据库的事务是保证数据完整性的一种机制,简而言之,就是怎样确保数据的执行过程要么都成功,要么都失败

  • 原子性(atomicity):组成事务处理的语句形成了一个逻辑单元,不能只执行其中的一部分。 
  • 一致性(consistency):在事务处理执行前后,数据库是一致的(两个账户要么都变,或者都不变)。 
  • 隔离性(isolcation):一个事务处理对另一个事务处理没有影响。 
  • 持续性(durability):事务处理的效果能够被永久保存下来 。
Jdbc的事务默认是打开的,也就是说执行每一步操作的话,事务都会隐式的进行提交,可以取消默认事务:
 
 
  1. connection.setAutoCommit(false);
示例代码( 也可以了解下我早期的一个DEMO)

  
  
  1. package net.oseye.DbDemo;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.DriverManager;
  5. import java.sql.PreparedStatement;
  6. import java.sql.ResultSet;
  7.  
  8. public class App
  9. {
  10. public static void main( String[] args ) throws Exception
  11. {
  12. Connection conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root","root");
  13. //取消默认事务
  14. conn.setAutoCommit(false);
  15. PreparedStatement pstmt=conn.prepareStatement("update user set user_name=? where user_id = ?");
  16. pstmt.setString(1, "kevin");
  17. pstmt.setInt(2, 1);
  18. pstmt.execute();
  19. pstmt=conn.prepareStatement("select user_name from user where user_id=?");
  20. pstmt.setInt(1,1);
  21. ResultSet rs=pstmt.executeQuery();
  22. //提交事务
  23. conn.commit();
  24. if(rs.next()){
  25. System.out.println("用户名:"+rs.getString(1));
  26. }
  27. rs.close();
  28. pstmt.close();
  29. conn.close();
  30. }
  31. }
这里没有对事务做深入讨论,看到这儿容易让你产生误会,以为事务如此简单,请正式生产前一定找相关专业书籍学习,切记切记。

JDBC的DataSource

前文提到使用DriverManager获取数据库连接的低效,是因为数据库连接是一种关键的有限而昂贵的资源。所以JDK为我们提供了一个DataSource接口。DataSource作为 DriverManager 工具的替代项,DataSource 对象是获取连接的首选方法。DataSource接口:


  
  
  1. //尝试建立与此 DataSource 对象所表示的数据源的连接。
  2. Connection getConnection()
  3.  
  4. //尝试建立与此 DataSource 对象所表示的数据源的连接。
  5. Connection getConnection(String username, String password)
有很多容器如Tomcat都实现DataSource接口而提供了连接池功能,现在我们来简单实现一个连接池:

  
  
  1. /**
  2. * 实现DataSource接口的简单连接池
  3. */
  4. public class ConnectPool implements DataSource {
  5. private static final String url = "jdbc:mysql://127.0.0.1:3306/test";
  6. private static final String user = "root";
  7. private static final String pswd = "root";
  8.  
  9. // 连接池队列
  10. private static LinkedList<Connection> pool = new LinkedList<Connection>();
  11. private static ConnectPool instance = new ConnectPool();
  12.  
  13. /**
  14. * 获取数据源单例
  15. */
  16. public static ConnectPool instance() {
  17. if (instance == null)
  18. instance = new ConnectPool();
  19. return instance;
  20. }
  21.  
  22. /**
  23. * 获取一个数据库连接
  24. */
  25. public Connection getConnection() throws SQLException {
  26. synchronized (pool) {
  27. if (pool.size() > 0) {
  28. return pool.removeFirst();
  29. } else {
  30. return DriverManager.getConnection(url, user, pswd);
  31. }
  32. }
  33. }
  34.  
  35. /**
  36. * 连接归池,这里的实现思想是使用过的线程入池以备下次使用
  37. */
  38. public static void freeConnection(Connection conn) {
  39. pool.addLast(conn);
  40. }
  41.  
  42. public Connection getConnection(String username, String password)
  43. throws SQLException {
  44. // TODO Auto-generated method stub
  45. return null;
  46. }
  47.  
  48. public PrintWriter getLogWriter() throws SQLException {
  49. // TODO Auto-generated method stub
  50. return null;
  51. }
  52.  
  53. public void setLogWriter(PrintWriter out) throws SQLException {
  54. // TODO Auto-generated method stub
  55.  
  56. }
  57.  
  58. public void setLoginTimeout(int seconds) throws SQLException {
  59. // TODO Auto-generated method stub
  60.  
  61. }
  62.  
  63. public int getLoginTimeout() throws SQLException {
  64. // TODO Auto-generated method stub
  65. return 0;
  66. }
  67.  
  68. public Logger getParentLogger() throws SQLFeatureNotSupportedException {
  69. // TODO Auto-generated method stub
  70. return null;
  71. }
  72.  
  73. public <T> T unwrap(Class<T> iface) throws SQLException {
  74. // TODO Auto-generated method stub
  75. return null;
  76. }
  77.  
  78. public boolean isWrapperFor(Class<?> iface) throws SQLException {
  79. // TODO Auto-generated method stub
  80. return false;
  81. }
  82. }
使用连接池

  
  
  1. public class App {
  2. public static void main(String[] args) throws Exception {
  3. //没有使用连接池
  4. long start=System.currentTimeMillis();
  5. for (int i = 0; i < 100; i++) {
  6. Connection conn = DriverManager.getConnection(
  7. "jdbc:mysql://127.0.0.1:3306/test",
  8. "root", "root");
  9.  
  10. PreparedStatement pstmt = conn
  11. .prepareStatement("insert into user(user_name) values(?)");
  12. pstmt.setString(1, String.valueOf(System.currentTimeMillis()));
  13. pstmt.execute();
  14.  
  15. pstmt.close();
  16. conn.close();
  17. }
  18. System.out.println("没有使用连接池花费时间:"+(System.currentTimeMillis()-start));
  19. //使用连接池
  20. start=System.currentTimeMillis();
  21. for (int i = 0; i < 100; i++) {
  22. Connection conn=ConnectPool.instance().getConnection();
  23. PreparedStatement pstmt = conn
  24. .prepareStatement("insert into user(user_name) values(?)");
  25. pstmt.setString(1, String.valueOf(System.currentTimeMillis()));
  26. pstmt.execute();
  27.  
  28. pstmt.close();
  29. ConnectPool.freeConnection(conn);
  30. }
  31. System.out.println("使用连接池花费时间:"+(System.currentTimeMillis()-start));
  32. }
  33. }
输出

没有使用连接池花费时间:4625
使用连接池花费时间:2782

示例是使用的单线程就能足以展现出差距了,如果使用多线程会更明显,但多线程就要对连接池进行同步操作,实现起来会更复杂。实例中的连接池实现虽然使用了同步(synchronized ),但是向当地弱,此实现只是简单展示连接池的实现以及使用,禁止把如此简单实现投入生产,切记切记。

开源连接池

有很多产品都实现了自己的连接池,如Jboss、Tomcat等容器,业界也有很多著名的开源连接池供大家使用,如:C3P0、BoneCP、DBCP 、Proxool等。我们以BoneCP连接池为例来展示。

pom.xml增加BoneCP的依赖配置(BoneCP使用了SLF4J,这里暂时没做配置)

 
 
  1. <dependency>
  2. <groupId>com.jolbox</groupId>
  3. <artifactId>bonecp</artifactId>
  4. <version>0.8.0.RELEASE</version>
  5. </dependency>
App.java代码

  
  
  1. package net.oseye.DbDemo;
  2.  
  3. import java.sql.Connection;
  4. import java.sql.PreparedStatement;
  5. import java.sql.SQLException;
  6.  
  7. import com.jolbox.bonecp.BoneCP;
  8. import com.jolbox.bonecp.BoneCPConfig;
  9.  
  10. public class App {
  11. public static void main(String[] args) {
  12. long start=System.currentTimeMillis();
  13. BoneCP connectionPool = null;
  14. Connection connection = null;
  15. try {
  16. //连接池配置
  17. BoneCPConfig config = new BoneCPConfig();
  18. config.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/test");
  19. config.setUsername("root");
  20. config.setPassword("root");
  21. config.setMinConnectionsPerPartition(5);
  22. config.setMaxConnectionsPerPartition(10);
  23. config.setPartitionCount(1);
  24.  
  25. connectionPool = new BoneCP(config);
  26. for (int i = 0; i < 100; i++) {
  27. //获取链接
  28. connection = connectionPool.getConnection();
  29. if (connection != null) {
  30. PreparedStatement pstmt = connection
  31. .prepareStatement("insert into user(user_name) values(?)");
  32. pstmt.setString(1,
  33. String.valueOf(System.currentTimeMillis()));
  34. pstmt.execute();
  35. pstmt.close();
  36. connection.close();
  37. }
  38. }
  39. connectionPool.shutdown();
  40. System.out.println("使用BoneCP连接池花费时间:"+(System.currentTimeMillis()-start));
  41. } catch (SQLException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. }
输出

使用BoneCP连接池花费时间:3531


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值