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表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态。
事务的特性:
- 原子性(atomicity):事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。
- 一致性(consistency):事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。
- 隔离性(isolation):一个事务的执行不能被其他事务所影响。
- 持久性(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转账(尤其是跨数据库情况),如何保证各方的钱是正确的?