传统JDBC数据库连接,使用DriverManager来获取;
每次数据库连接,使用完后都得断开;若程序出现异常而未能关闭,将导致数据库内存泄露,最终将导致重启数据库;
传统获取连接的方式,不能控制创建的连接数量;若连接过多,也会导致内存泄露,MYSQL崩溃;
解决传统开发中的数据库连接问题,可采用数据库连接池技术(connection pool);
JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方【服务器(Weblogic, WebSphere, Tomcat)】提供实现【提供.jar】。
通常被称为数据源,它包含连接池和连接池管理两个部分;
DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度;
Apache Commons DBCP:
HikariCP:
C3P0:
Tomcat JDBC Pool:
H2 Database:
Druid(德鲁伊):
Bitronix Transaction Manager:
c3p0jar包下载地址
读取相关的属性值和JDBC中的方式一样:使用properties
配置文件。
@Test
public void testC3P0_01() throws Exception {
//1. 创建一个数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//2. 通过配置文件mysql.properties 获取相关连接的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//读取相关的属性值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//给数据源 comboPooledDataSource 设置相关的参数
//注意:连接管理是由 comboPooledDataSource 来管理
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
//设置初始化连接数
comboPooledDataSource.setInitialPoolSize(10);
//最大连接数
comboPooledDataSource.setMaxPoolSize(50);
Connection connection = comboPooledDataSource.getConnection(); //这个方法就是从 DataSource 接口实现的
connection.close();
}
//配置文件mysql.properties
user=root
password=lhyroot123
url=jdbc:mysql://localhost:13306/hsp_db02?rewriteBatchedStatements=true
driver=com.mysql.jdbc.Driver
使用XML
文件来配置相关信息
//1. 将c3p0 提供的 c3p0.config.xml 拷贝到 src目录下
//2. 该文件指定了连接数据库和连接池的相关参数
@Test
public void testC3P0_02() throws SQLException {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("jk");
Connection connection = comboPooledDataSource.getConnection();
connection.close();
}
c3p0-config.xml配置文件
com.mysql.jdbc.Driver
jdbc:mysql://127.0.0.1:13306/db_01
root
root123
5
10
5
50
5
2
德鲁伊jar包下载地址
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
// 工具类,完成 Druid的连接和关闭资源
public class JDBCDUtilsByDruid {
private static DataSource ds;
// 在static代码块去初始化
static {
try {
// 1.创建Properties对象
Properties properties = new Properties();
// 2.创建并加载流到Properties中
properties.load(new FileInputStream("src\\druid.properties"));
// 3.创建一个指定参数的数据库连接池, Druid 连接池
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
// 在实际开发中,我们可以这样处理
// 1. 将编译异常转成 运行异常
// 2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
}
}
// 连接数据库, 返回Connection对象
public static Connection getConnection() {
try {
return ds.getConnection();
} catch (SQLException e) {
// 1. 将编译异常转成 运行异常
// 2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
}
}
//关闭连接.
//注意:在数据库连接池技术中, close 不是真的断掉连接,而是把使用的 Connection 对象放回连接池
/*
1. ResultSet 结果集
2. Statement 或者 PreparedStatement
3. Connection
4. 如果需要关闭资源,就传入对象,否则传入 null
*/
public static void close(Connection connection, Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close(Connection connection, Statement statement, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(connection, statement);
}
}
不足
该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理使用QueryRunner类实现查询。
//源码:
/**
* 分析 queryRunner.query方法:
* public T query(Connection conn, String sql, ResultSetHandler rsh, Object... params) throws SQLException {
* PreparedStatement stmt = null;//定义PreparedStatement
* ResultSet rs = null;//接收返回的 ResultSet
* Object result = null;//返回ArrayList
*
* try {
* stmt = this.prepareStatement(conn, sql);//创建PreparedStatement
* this.fillStatement(stmt, params);//对sql 进行 ? 赋值
* rs = this.wrap(stmt.executeQuery());//执行sql,返回resultset
* result = rsh.handle(rs);//返回的resultset --> arrayList[result] [使用到反射,对传入class对象处理]
* } catch (SQLException var33) {
* this.rethrow(var33, sql, params);
* } finally {
* try {
* this.close(rs);//关闭resultset
* } finally {
* this.close((Statement)stmt);//关闭preparedstatement对象
* }
* }
* return result;
* }
*/
该接口用于处理java.sql.ResultSet
,将数据按要求转换为另一种形式。
package com.lhy.jdbc;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.jupiter.api.Test;
import java.sql.*;
import java.util.List;
@SuppressWarnings({"all"})
public class DBUtils_USE {
//使用apache-DBUtils 工具类 + druid 完成对表的crud操作
@Test
public void testQueryMany() throws SQLException { //返回结果是多行的情况
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. 就可以执行相关的方法,返回ArrayList 结果集
//String sql = "select * from actor where id >= ?";
// 注意: sql 语句也可以查询部分列
String sql = "select id, name from actor where id >= ?";
// 解读
//(1) query 方法就是执行sql 语句,得到resultset ---封装到 --> ArrayList 集合中
//(2) 返回集合
//(3) connection: 连接
//(4) sql : 执行的sql语句
//(5) new BeanListHandler<>(Actor.class): 在将resultset -> Actor 对象 -> 封装到 ArrayList
// 底层使用反射机制 去获取Actor 类的属性,然后进行封装
//(6) 1 就是给 sql 语句中的? 赋值,可以有多个值,因为是可变参数Object... params
//(7) 底层得到的resultset ,会在query中关闭Resultset, 关闭PreparedStatment
List list =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
System.out.println("输出集合的信息");
for (Actor actor : list) {
System.out.print(actor);
}
//释放资源
JDBCUtilsByDruid.close(null, null,connection);
}
//演示 apache-dbutils + druid 完成 返回的结果是单行记录(单个对象)
@Test
public void testQuerySingle() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. 就可以执行相关的方法,返回单个对象
String sql = "select * from actor where id = ?";
// 因为我们返回的单行记录<--->单个对象 , 使用的Hander 是 BeanHandler
Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 10);
System.out.println(actor);
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
//演示apache-dbutils + druid 完成查询结果是单行单列-返回的就是object
@Test
public void testScalar() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. 就可以执行相关的方法,返回单行单列 , 返回的就是Object
String sql = "select name from actor where id = ?";
//老师解读: 因为返回的是一个对象, 使用的handler 就是 ScalarHandler
Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 4);
System.out.println(obj);
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
//演示apache-dbutils + druid 完成 dml (update, insert ,delete)
@Test
public void testDML() throws SQLException {
//1. 得到 连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 使用 DBUtils 类和接口 , 先引入DBUtils 相关的jar , 加入到本Project
//3. 创建 QueryRunner
QueryRunner queryRunner = new QueryRunner();
//4. 这里组织sql 完成 update, insert delete
//String sql = "update actor set name = ? where id = ?";
//String sql = "insert into actor values(null, ?, ?, ?, ?)";
String sql = "delete from actor where id = ?";
//老韩解读
//(1) 执行dml 操作是 queryRunner.update()
//(2) 返回的值是受影响的行数 (affected: 受影响)
//int affectedRow = queryRunner.update(connection, sql, "林青霞", "女", "1966-10-10", "116");
int affectedRow = queryRunner.update(connection, sql, 1000 );
System.out.println(affectedRow > 0 ? "执行成功" : "执行没有影响到表");
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
}
package com.lhy.jdbc;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
// 工具类,完成 Druid的连接和关闭资源
public class JDBCUtilsByDruid {
private static DataSource ds;
// 在static代码块去初始化
static {
try {
// 1.创建Properties对象
Properties properties = new Properties();
// 2.创建并加载流到Properties中
properties.load(new FileInputStream("src\\druid.properties"));
// 3.创建一个指定参数的数据库连接池, Druid 连接池
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
// 在实际开发中,我们可以这样处理
// 1. 将编译异常转成 运行异常
// 2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
}
}
// 连接数据库, 返回Connection对象
public static Connection getConnection() {
try {
return ds.getConnection();
} catch (SQLException e) {
// 1. 将编译异常转成 运行异常
// 2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e);
}
}
//关闭相关资源
/*
1. ResultSet 结果集
2. Statement 或者 PreparedStatement
3. Connection
4. 如果需要关闭资源,就传入对象,否则传入 null
*/
public static void close(Statement statement,Connection connection) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void close( ResultSet rs, Statement statement,Connection connection) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
close(statement,connection);
}
}
(1)三层架构
三层架构是指:视图层 View、服务层 Service,与持久层 Dao。它们分别完成不同的功能。
(2)面向接口 (抽象) 编程
面向接口编程:程序设计时,考虑易修改、易扩展,为Service层和DAO层设计接口,便于未来更换实现类;
在三层架构程序设计中,采用面向接口(抽象)编程。
实现方式
特点:
(1)DAO封装 — (DAO Data Access Object 数据访问对象)
将对数据库中同⼀张数据表的JDBC操作⽅法封装到同⼀个Java类中,这个类就是访问此数据表的 数据访问对象
(2)DTO封装 — ( Data Transfer Object 数据传输对象(实体类))
在Java程序中创建⼀个属性与数据库表匹配的类,通过此类的对象封装查询到的数据,我们把⽤于传递JDBC增删查改操作的数据的对象称之为 数据传输对象
(1)事务的概念
事务是指是程序中一系列严密的逻辑操作,而且所有操作必须全部成功完成,否则在每个操作中所作的所有更改都会被撤消。
(2)事务的四大特性
(3)事务的隔离级别
第一种隔离级别:Read uncommitted(读未提交)
第二种隔离级别:Read committed(读提交)
第三种隔离级别:Repeatable read(可重复读取)
第四种隔离级别:Serializable(序列化)
脏读
幻读
不可重复读
MySQL事务管理:
(4)JDBC事务管理
(5)Service层简介
DAO负责特定的数据库操作,业务由service层进⾏管理
(6)Service层事务管理
事务管理要满⾜以下条件:
1.需要解决的问题
Servcie层事务可能涉及多个DAO层,其中多个数据库的DML操作是相互独⽴的,如何保证所有DML要么同时成功,要么同时失败呢?
解决办法:让Service事务中的多个DML使⽤同⼀个数据库连接
方式一:在Service获取连接对象,将连接对象传递到DAO中
分析: DAO类中的Connection对象需要通过Service传递给进来,这种对象传递本来也⽆可厚⾮,但是当我们通过⾯向接⼝开发时(⾯向接⼝,是为了能够灵活的定义实现类),容易造成接⼝的冗余(接⼝污染)
接口污染典型示例: 不同的数据库的数据库连接对象是不同的,MySQL的连接对象是Connection 但Oracle数据库则不是
方式二:使⽤ThreadLocal容器,实现多个DML操作使⽤相同的连接
不使用自定义List集合的原因:
存储Connection的容器可以使⽤List集合,使⽤List集合做容器,在多线程并发编程中会出现资源竞争问题,多个并发的线程使⽤的是同⼀个数据库连接对象(我们的要求是同⼀个事务中使⽤同⼀个连接,⽽并⾮多个线程共享同一个连接)
为了解决并发编程的连接对象共享问题,我们可以 使⽤ThreadLocal作为数据库连接对象的容器;