JDBC和数据库连接池

1.JDBC介绍
 1.1 1.1 JDBC介绍

2.JDBC之API
 2.1 2.1 JDBC之API

3.JDBC例子
 3.1 3.1 前期准备DBUtil
 3.2 3.2 DBUtil实现对数据库的CURD

4.JDBC之事务管理
 4.1 4.1 事务基础概念
 4.2 4.2 事务实现转账功能

5.数据库连接池
 5.1 数据库连接池介绍
 5.2 JNDI访问数据源
 5.3 开源数据库连接池

一、JDBC开发步骤

二、JDBC事务管理

Connection.setAutoCommit(boolean autoCommit) 修改自动提交模式(默认为true)
Connection.commit() 提交事务
Connection.rollback() 事务回滚

1、什么是事务

事务(Transaction):是并发控制的单元,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,sql server 能将逻辑相关的一组操作绑定在一起,以便服务器 保持数据的完整性。事务通常是以begin transaction开始,以commit或rollback结束。Commint表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据的更新写回到磁盘上的物理数据库中去,事务正常结束。Rollback表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态。

事务的特性:
    1. 原子性(atomicity):事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。
    1. 一致性(consistency):事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。
    1. 隔离性(isolation):一个事务的执行不能被其他事务所影响。
    1. 持久性(durability):一个事务一旦提交,事物的操作便永久性的保存在DB中。即使此时再执行回滚操作也不能撤消所做的更改。
2、Java JDBC事务机制

JDBC对事务的支持体现在三个方面:

自动提交模式(Auto-commit mode)

Connection提供了一个auto-commit的属性来指定事务何时结束。

  • a. 当auto-commit为true时(默认为true),当每个独立SQL操作的执行完毕,事务立即自动提交,也就是说每个SQL操作都是一个事务。一个独立SQL操作什么时候算执行完毕,JDBC规范是这样规定的:

对数据操作语言(DML,如insert,update,delete)和数据定义语言(如create,drop),语句一执行完就视为执行完毕。

对select语句,当与它关联的ResultSet对象关闭时,视为执行完毕。

对存储过程或其他返回多个结果的语句,当与它关联的所有ResultSet对象全部关闭,所有update count(update,delete等语句操作影响的行数)和output parameter(存储过程的输出参数)都已经获取之后,视为执行完毕。

  • b. 当auto-commit为false时,每个事务都必须显示调用commit方法进行提交,或者显示调用rollback方法进行回滚。auto-commit默认为true。
例子
try {
        conn.setAutoCommit(false);  //将自动提交设置为false
        ps.executeUpdate("修改SQL"); //执行修改操作
        ps.executeQuery("查询SQL");  //执行查询操作
        conn.commit();      //当两个操作成功后手动提交
    } catch (Exception e) {
        conn.rollback();    //一旦其中一个操作出错都将回滚,使两个操作都不成功
        e.printStackTrace();
    }

三、数据库连接池

数据库连接池的实现方法:

  • JNDI
  • 开源框架:DBCP、C3P0等
  • 公司用:持久层框架Mybatis

从数据库连接池获取的Connection是不需要关闭的,连接池将根据情况自动关闭。

1、使用JNDI配置数据库连接池

参考:JNDI 是什么 | Tomcat配置JNDI

JNDI(Java Naming and Directory Inteface)即Java名称目录接口。JNDI的作用:就是将资源引入到服务器中。可以将JNDI当成一个仓库。将Java对象放入到JNDI中去。

JNDI提供了一种服务,这个服务可以将“名称”和“资源”进行绑定,然后程序员通过面向JNDI接口调用方法lookup可以查找到相关的资源。

基于JNDI的特性,用了JNDI之后建立数据库连接池的做法:首先,在J2EE容器中配置JNDI参数,定义一个数据源,也就是JDBC引用参数,给这个数据源设置一个名称;然后,在程序中,通过数据源名称引用数据源从而访问后台数据库。

2、使用开源框架:以DBCP为例

DBCP(DatabaseConnection Pool)是一个依赖Jakarta commons-pool对象池机制的数据库连接池,Tomcat的数据源使用的就是DBCP。

DBCP使用Demo
1、引入依赖
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>
2、配置文件(.properties)
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost/db_qunar_fresh2017
jdbc.username=root  
jdbc.password=root  

#初始化连接
dataSource.initialSize=10  

#最大空闲连接
dataSource.maxIdle=20  

#最小空闲连接
dataSource.minIdle=5  

#最大连接数量
dataSource.maxActive=50  

#是否在自动回收超时连接的时候打印连接的超时错误
dataSource.logAbandoned=true  

#是否自动回收超时连接
dataSource.removeAbandoned=true  

#超时时间(以秒数为单位)
dataSource.removeAbandonedTimeout=180  

#超时等待时间以毫秒为单位 6000毫秒/1000等于60秒
dataSource.maxWait=1000  
3、创建数据库连接池、获取连接(封装工具类)
package com.qunar.fresh2017.db;

import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/**
 * Copyright(C),2005-2017,Qunar.com
 * version    date      author
 * ──────────────────────────────────
 * 1.0       17-3-9   wanlong.ma
 * Description:DBCP连接池工具类
 * Others:
 * Function List:
 * History:
 */
public class DataSourceUtil {
    private static Logger logger = LoggerFactory.getLogger(DataSourceUtil.class);
    private static Properties properties = new Properties();

    private static DataSource dataSource;
    static {
        // 读取配置文件
        try {
            properties.load(DataSourceUtil.class.getResourceAsStream("/dbcp.properties"));
        } catch (IOException e) {
            logger.error("读取数据库连接池配置文件失败:/dbcp.properties",e);
        }
        // 创建数据库连接池
        try {
            dataSource = BasicDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            logger.error("数据库连接池创建失败",e);
        }
    }

    /**
     * 获取连接
     * @return
     */
    public static Connection getConnection(){
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
        } catch (SQLException e) {
            logger.error("从数据库连接池中获取连接失败",e);
        }
        return connection;
    }
   
}
3、使用数据库连接池是否需要关闭Connection

我们在平时项目中用到了数据库连接池,比如C3P0,DBCP,JNDI...
在使用结束的时候我们也要关闭连接
为什么呢。具体解释如下:使用 C3P0 的话,也是 java.sql.Connection,只要是 JDBC 都是这个接口的对象!

使用完后必须 con.close() 掉 ,使用连接池的话,执行 con.close 并不会关闭与数据库的 TCP 连接,而是将连接还回到池中去,如果不 close 掉的话,这个连接将会一直被占用,直接连接池中的连接耗尽为止

也就是说:我们通过数据库连接池获取到的Connection是经过连接池重写后的Connection,同为 java.sql.Connection,只是在关闭连接时,它重写了colse方法,使得Connection并不是断开TCP连接,而是释放、还给连接池继续分配。

至于是如何做到 con.close 并不是真正意义上的关闭连接?而是直接将连接还回到池中去? 一般有两种方式:

  • (1)使用装饰器模式,在装饰器构造中传入一个真正的 Connection,这个装饰器实现 Connection,使用构造 传入 Connection 委托重写所有方法,并改写 close 方法:

    public class ConnectionDecorator implements Connection {
    
      private Connection con = null;
    
      public ConnectionDecorator(Connection con) {
          this.con = con;
      }
    
      public void close() throws SQLException {
          // 重写! 
      }
    
      public void commit() throws SQLException {
          this.con.commit();
      }
    
      public Statement createStatement() throws SQLException {
          return this.con.createStatement();
      }
    
      public Statement createStatement(int resultSetType, int resultSetConcurrency)
              throws SQLException {
          return this.con.createStatement(resultSetType, resultSetConcurrency);
      } 
     
       ......
    }
    

然后经过连接池控制的 Connection 对象都使用该装饰器进行包装

  • (2):动态代理: 使用动态代理重新实现 close 方法,每个获得 Connection 是一个代理后的对象。

一个完善的连接池,其架构设计非常复杂,Connection#close 问题就是连接池诸多设计难点当中的一个。

四、补充问题

1、JDBC批处理

当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。

2、线程并发更新问题

例如:A、B同时给C转账(尤其是跨数据库情况),如何保证各方的钱是正确的?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • 一、事务 事务就是一个事情,组成这个事情可能有多个单元,要求这些单元,要么全都成功,要么全都不成功。在开发中,有事...
    野狗子嗷嗷嗷阅读 2,795评论 0 6
  • 主要内容 定义Spring的数据访问支持 配置数据库资源 使用Spring提供的JDBC模板 写在前面:经过上一篇...
    程序熊大阅读 8,740评论 1 31
  • 本文包括传统JDBC的缺点连接池原理自定义连接池开源数据库连接池DBCP连接池C3P0连接池Tomcat内置连接池...
    廖少少阅读 16,723评论 0 37
  • 1.事务 1.1事务的四大特性(ACID) 原子性:事务中的所有操作要么全部执行成功,要么执行全部失败。 一致性:...
    joshul阅读 430评论 0 1
  • 最原始的数据库连接就是我们打开一个连接,使用过后再关闭该链接来释放资源。频繁的新建打开再关闭连接对jvm和数据库都...
    野柳阅读 6,325评论 1 11