S01-25 JavaSE-数据库-JDBC
[TOC]
JDBC概述
基本介绍
- JDBC(Java Database Connectivity):Java访问数据库的统一接口
- 作用:屏蔽不同数据库的实现细节,Java程序通过JDBC驱动访问任意数据库
- 核心:Java提供接口,数据库厂商提供驱动(jar包)实现
模拟JDBC实现
// JDBC接口(Java制定规范)
public interface JdbcInterface {
Object getConnection();
void crud();
void close();
}
// MySQL驱动实现
public class MysqlJdbcImpl implements JdbcInterface {
@Override
public Object getConnection() {
System.out.println("得到mysql的连接");
return null;
}
@Override
public void crud() {
System.out.println("完成mysql增删改查");
}
@Override
public void close() {
System.out.println("关闭mysql的连接");
}
}
// Oracle驱动实现
public class OracleJdbcImpl implements JdbcInterface {
@Override
public Object getConnection() {
System.out.println("得到oracle的连接");
return null;
}
@Override
public void crud() {
System.out.println("完成oracle增删改查");
}
@Override
public void close() {
System.out.println("关闭oracle的连接");
}
}
// 测试类
public class TestJDBC {
public static void main(String[] args) {
// 访问MySQL
JdbcInterface jdbc = new MysqlJdbcImpl();
jdbc.getConnection();
jdbc.crud();
jdbc.close();
// 访问Oracle
jdbc = new OracleJdbcImpl();
jdbc.getConnection();
jdbc.crud();
jdbc.close();
}
}JDBC快速入门
JDBC程序编写步骤
- 注册驱动(加载Driver类)
- 获取连接(Connection)
- 执行SQL(Statement/PreparedStatement)
- 释放资源(关闭连接、语句、结果集)
第一个JDBC程序(操作actor表)
import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.Statement;
import java.util.Properties;
public class Jdbc01 {
public static void main(String[] args) throws Exception {
// 1. 注册驱动
Driver driver = new Driver();
// 2. 获取连接
String url = "jdbc:mysql://localhost:3306/hsp_db02";
Properties props = new Properties();
props.setProperty("user", "root");
props.setProperty("password", "hsp");
Connection conn = driver.connect(url, props);
// 3. 执行SQL(增删改)
String sql = "insert into actor values(null, '刘德华', '男', '1970-11-11', '110')";
// String sql = "update actor set name='周星驰' where id = 1";
// String sql = "delete from actor where id = 1";
Statement stmt = conn.createStatement();
int rows = stmt.executeUpdate(sql); // DML返回影响行数
System.out.println(rows > 0 ? "成功" : "失败");
// 4. 释放资源
stmt.close();
conn.close();
}
}获取数据库连接5种方式
方式1:直接创建Driver对象
@Test
public void connect01() throws Exception {
Driver driver = new com.mysql.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/hsp_db02";
Properties props = new Properties();
props.setProperty("user", "root");
props.setProperty("password", "hsp");
Connection conn = driver.connect(url, props);
System.out.println(conn);
}方式2:反射加载Driver(动态加载)
@Test
public void connect02() throws Exception {
Class<?> clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
String url = "jdbc:mysql://localhost:3306/hsp_db02";
Properties props = new Properties();
props.setProperty("user", "root");
props.setProperty("password", "hsp");
Connection conn = driver.connect(url, props);
System.out.println("方式2=" + conn);
}方式3:使用DriverManager管理驱动
@Test
public void connect03() throws Exception {
Class<?> clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String pwd = "hsp";
DriverManager.registerDriver(driver);
Connection conn = DriverManager.getConnection(url, user, pwd);
System.out.println("方式3=" + conn);
}方式4:Class.forName自动注册驱动(推荐)
@Test
public void connect04() throws Exception {
// 加载Driver类时,静态代码块自动注册驱动(DriverManager.registerDriver)
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String pwd = "hsp";
Connection conn = DriverManager.getConnection(url, user, pwd);
System.out.println("方式4=" + conn);
}方式5:配置文件+反射(最灵活)
- 创建
src/mysql.properties配置文件:
user=root
password=hsp
url=jdbc:mysql://localhost:3306/hsp_db02
driver=com.mysql.jdbc.Driver- Java代码:
@Test
public void connect05() throws Exception {
// 加载配置文件
Properties props = new Properties();
props.load(new FileInputStream("src/mysql.properties"));
String user = props.getProperty("user");
String pwd = props.getProperty("password");
String url = props.getProperty("url");
String driver = props.getProperty("driver");
Class.forName(driver); // 推荐写上
Connection conn = DriverManager.getConnection(url, user, pwd);
System.out.println("方式5=" + conn);
}ResultSet(结果集)
基本介绍
- 存储查询结果的数据集,光标初始在第一行之前
next():光标下移一行,返回false表示无更多行- 支持通过列索引或列名获取值
应用实例
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import java.io.FileInputStream;
public class ResultSet_ {
public static void main(String[] args) throws Exception {
// 加载配置
Properties props = new Properties();
props.load(new FileInputStream("src/mysql.properties"));
String user = props.getProperty("user");
String pwd = props.getProperty("password");
String url = props.getProperty("url");
String driver = props.getProperty("driver");
// 1. 注册驱动
Class.forName(driver);
// 2. 获取连接
Connection conn = DriverManager.getConnection(url, user, pwd);
// 3. 创建Statement
Statement stmt = conn.createStatement();
// 4. 执行查询
String sql = "select id, name, sex, borndate from actor";
ResultSet rs = stmt.executeQuery(sql);
// 5. 遍历结果集
while (rs.next()) {
int id = rs.getInt("id"); // 列名(推荐)
String name = rs.getString("name");
String sex = rs.getString("sex");
java.sql.Date borndate = rs.getDate("borndate");
System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate);
}
// 6. 释放资源
rs.close();
stmt.close();
conn.close();
}
}Statement(SQL注入问题)
问题说明
- Statement执行静态SQL,通过字符串拼接生成SQL,存在SQL注入风险
- SQL注入:恶意用户输入非法SQL片段,改变原SQL逻辑
演示SQL注入
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import java.util.Scanner;
import java.io.FileInputStream;
public class Statement_ {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入管理员名字:");
String name = scanner.nextLine();
System.out.print("请输入密码:");
String pwd = scanner.nextLine();
// 加载配置
Properties props = new Properties();
props.load(new FileInputStream("src/mysql.properties"));
String user = props.getProperty("user");
String password = props.getProperty("password");
String url = props.getProperty("url");
String driver = props.getProperty("driver");
// 连接数据库
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
// 拼接SQL(存在注入风险)
String sql = "select name, pwd from admin where name = '" + name + "' and pwd = '" + pwd + "'";
ResultSet rs = stmt.executeQuery(sql);
// 判断登录结果
if (rs.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
// 释放资源
rs.close();
stmt.close();
conn.close();
}
}注入测试
- 用户名输入:
1' or - 密码输入:
or '1'='1 - 拼接后的SQL:
select name, pwd from admin where name = '1' or' and pwd = 'or '1'='1' - 结果:无需正确密码即可登录
PreparedStatement(预处理,解决SQL注入)
基本介绍
- 预编译SQL,参数用
?占位符 - 通过
setXxx(索引, 值)设置参数(索引从1开始) - 优点:避免SQL注入、减少编译次数、语法简洁
应用实例(登录功能)
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Properties;
import java.util.Scanner;
import java.io.FileInputStream;
public class PreparedStatement_ {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入管理员名字:");
String name = scanner.nextLine();
System.out.print("请输入密码:");
String pwd = scanner.nextLine();
// 加载配置
Properties props = new Properties();
props.load(new FileInputStream("src/mysql.properties"));
String user = props.getProperty("user");
String password = props.getProperty("password");
String url = props.getProperty("url");
String driver = props.getProperty("driver");
// 连接数据库
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, user, password);
// 预处理SQL(?占位符)
String sql = "select name, pwd from admin where name = ? and pwd = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置参数(索引1对应第一个?,索引2对应第二个?)
pstmt.setString(1, name);
pstmt.setString(2, pwd);
// 执行查询(无需传入SQL)
ResultSet rs = pstmt.executeQuery();
// 判断登录结果
if (rs.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
// 释放资源
rs.close();
pstmt.close();
conn.close();
}
}PreparedStatement执行DML(增删改)
public class PreparedStatementDML_ {
public static void main(String[] args) throws Exception {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入要删除的管理员名字:");
String name = scanner.nextLine();
// 加载配置
Properties props = new Properties();
props.load(new FileInputStream("src/mysql.properties"));
String user = props.getProperty("user");
String password = props.getProperty("password");
String url = props.getProperty("url");
String driver = props.getProperty("driver");
// 连接数据库
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, user, password);
// 预处理SQL(删除)
String sql = "delete from admin where name = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
// 执行DML(返回影响行数)
int rows = pstmt.executeUpdate();
System.out.println(rows > 0 ? "执行成功" : "执行失败");
// 释放资源
pstmt.close();
conn.close();
}
}JDBC API小结
| 组件 | 核心方法 |
|---|---|
| DriverManager | getConnection(url, user, pwd):获取连接 |
| Connection | createStatement():创建StatementprepareStatement(sql):创建PreparedStatement |
| Statement | executeUpdate(sql):执行DMLexecuteQuery(sql):执行查询execute(sql):执行任意SQL |
| PreparedStatement | setXxx(索引, 值):设置参数executeUpdate():执行DMLexecuteQuery():执行查询 |
| ResultSet | next():光标下移getXxx(列名/索引):获取值getObject(列名/索引):获取任意类型值 |
封装JDBCUtils工具类
工具类代码
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Properties;
import java.io.FileInputStream;
import java.io.IOException;
public class JDBCUtils {
// 静态属性(只加载一次)
private static String user;
private static String password;
private static String url;
private static String driver;
// 静态代码块初始化配置
static {
try {
Properties props = new Properties();
props.load(new FileInputStream("src/mysql.properties"));
user = props.getProperty("user");
password = props.getProperty("password");
url = props.getProperty("url");
driver = props.getProperty("driver");
// 注册驱动
Class.forName(driver);
} catch (IOException | ClassNotFoundException e) {
// 编译异常转运行异常
throw new RuntimeException("初始化JDBC工具类失败", e);
}
}
// 获取连接
public static Connection getConnection() throws Exception {
return DriverManager.getConnection(url, user, password);
}
// 释放资源
public static void close(ResultSet rs, Statement stmt, Connection conn) {
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (Exception e) {
throw new RuntimeException("释放资源失败", e);
}
}
// 重载(无ResultSet时)
public static void close(Statement stmt, Connection conn) {
close(null, stmt, conn);
}
}工具类使用示例
public class TestJDBCUtils {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 获取连接
conn = JDBCUtils.getConnection();
// 预处理SQL
String sql = "select * from actor where id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 1);
// 执行查询
rs = pstmt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("name") + "\t" + rs.getString("phone"));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放资源
JDBCUtils.close(rs, pstmt, conn);
}
}
}第25章 JDBC进阶
事务
基本介绍
- JDBC程序中当一个
Connection对象创建时,默认情况下是自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚。 - JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务。
- 调用
Connection的setAutoCommit(false)可以取消自动提交事务。 - 在所有的SQL语句都成功执行后,调用
Connection的commit();方法提交事务。 - 在其中某个操作失败或出现异常时,调用
Connection的rollback();方法回滚事务。
应用实例
模拟经典的转账业务
create table account(
id int primary key auto_increment,
name varchar(32) not null default '',
balance double not null default 0
) character set utf8;
insert into account values(null,'马化腾',10000);
insert into account values(null,'马云',3000);不使用事务可能出现的问题模拟
模拟经典的转账业务(转账过程中出现异常导致数据不一致)
使用事务解决上述问题-模拟经典的转账业务
package com.hspedu.jdbc.transaction_;
import com.hspedu.jdbc.utils.JDBCUtils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author 韩顺平
* @version 1.0
* 演示jdbc 中如何使用事务
*/
public class Transaction_ {
//没有使用事务
@Test
public void noTransaction() {
//操作转账的业务
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection(); // 在默认情况下,connection 是默认自动提交
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate(); // 执行第1 条sql
int i = 1 / 0; //抛出异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第2 条sql
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
//使用事务来解决
@Test
public void useTransaction() {
//操作转账的业务
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection(); // 在默认情况下,connection 是默认自动提交
//将connection 设置为不自动提交
connection.setAutoCommit(false); //开启了事务
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate(); // 执行第1 条sql
int i = 1 / 0; //抛出异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第2 条sql
//这里提交事务
connection.commit();
} catch (SQLException e) {
//这里我们可以进行回滚,即撤销执行的SQL
//默认回滚到事务开始的状态
System.out.println("执行发生了异常,撤销执行的sql");
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
}课后练习
参考老师代码:
- 创建
account表 - 在表中先添加两条记录:tom余额100,king余额200
- 使用事务完成,tom给king转10元钱。
批处理
基本介绍
- 当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理,通常情况下比单独提交处理更有效率。
- JDBC的批量处理语句包括下面方法:
addBatch():添加需要批量处理的SQL语句或参数executeBatch():执行批量处理语句clearBatch():清空批处理包的语句
- JDBC连接MySQL时,如果要使用批处理功能,请在url中加参数
?rewriteBatchedStatements=true - 批处理往往和
PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高。
应用实例
Batch_.java:演示向admin2表中添加5000条数据,对比传统方式和批量方式的耗时
注意:需要修改配置文件jdbc.properties
url=jdbc:mysql://localhost:3306/数据库?rewriteBatchedStatements=true创建表:
create table admin2(
id int primary key auto_increment,
username varchar(32) not null,
password varchar(32) not null
);Java代码:
package com.hspedu.jdbc.batch_;
import com.hspedu.jdbc.utils.JDBCUtils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author 韩顺平
* @version 1.0
* 演示java 的批处理
*/
public class Batch_ {
//传统方法,添加5000 条数据到admin2
@Test
public void noBatch() throws Exception {
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values(null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {//5000 次执行
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666");
preparedStatement.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("传统的方式耗时=" + (end - start));//传统的方式耗时=10702
//关闭连接
JDBCUtils.close(null, preparedStatement, connection);
}
//使用批量方式添加数据
@Test
public void batch() throws Exception {
Connection connection = JDBCUtils.getConnection();
String sql = "insert into admin2 values(null, ?, ?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
System.out.println("开始执行");
long start = System.currentTimeMillis();//开始时间
for (int i = 0; i < 5000; i++) {//5000 次执行
preparedStatement.setString(1, "jack" + i);
preparedStatement.setString(2, "666");
//将sql 语句加入到批处理包中-> 看源码
/*
//1. 第一次就创建ArrayList - elementData => Object[]
//2. elementData => Object[] 就会存放我们预处理的sql 语句
//3. 当elementData 满后,就按照1.5 扩容
//4. 当添加到指定的值后,就executeBatch
//5. 批量处理会减少我们发送sql 语句的网络开销,而且减少编译次数,因此效率提高
public void addBatch() throws SQLException {
synchronized(this.checkClosed().getConnectionMutex()) {
if (this.batchedArgs == null) {
this.batchedArgs = new ArrayList();
}
for(int i = 0; i < this.parameterValues.length; ++i) {
this.checkAllParametersSet(this.parameterValues[i], this.parameterStreams[i], i);
}
this.batchedArgs.add(new PreparedStatement.BatchParams(this.parameterValues, this.parameterStreams, this.isStream, this.streamLengths, this.isNull));
}
}
*/
preparedStatement.addBatch();
//当有1000 条记录时,批量执行一次
if((i + 1) % 1000 == 0) {//满1000 条sql
preparedStatement.executeBatch();
//清空批处理包
preparedStatement.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("批量方式耗时=" + (end - start));//批量方式耗时=108
//关闭连接
JDBCUtils.close(null, preparedStatement, connection);
}
}数据库连接池
5k次连接数据库问题
ConQuestion.java:编写程序完成连接MySQL5000次的操作,观察耗时和问题
package com.hspedu.jdbc.datasource;
import com.hspedu.jdbc.utils.JDBCUtils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
/**
* @author 韩顺平
* @version 1.0
*/
public class ConQuestion {
//代码连接mysql 5000 次
@Test
public void testCon() {
//看看连接-关闭connection 会耗用多久
long start = System.currentTimeMillis();
System.out.println("开始连接.....");
for (int i = 0; i < 5000; i++) {
//使用传统的jdbc 方式,得到连接
Connection connection = JDBCUtils.getConnection();
//做一些工作,比如得到PreparedStatement,发送sql
//关闭连接
JDBCUtils.close(null, null, connection);
}
long end = System.currentTimeMillis();
System.out.println("传统方式5000 次耗时=" + (end - start));//传统方式5000 次耗时=7099
}
}传统获取Connection问题分析
- 传统的JDBC数据库连接使用
DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证IP地址、用户名和密码(0.05s~1s时间)。 - 需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
- 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
- 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃。
解决方式:采用数据库连接池技术(connection pool)
数据库连接池种类
JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现(需导入对应.jar包):
- C3P0数据库连接池:速度相对较慢,稳定性不错(hibernate、spring中常用)
- DBCP数据库连接池:速度相对c3p0较快,但不稳定
- Proxool数据库连接池:有监控连接池状态的功能,稳定性较c3p0差一点
- BoneCP数据库连接池:速度快
- Druid(德鲁伊):阿里提供的数据库连接池,集DBCP、C3P0、Proxool优点于一身
C3P0应用实例
使用代码实现c3p0数据库连接池,配置文件放src目录下
package com.hspedu.jdbc.datasource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author 韩顺平
* @version 1.0
* 演示c3p0 的使用
*/
public class C3P0_ {
//方式1: 相关参数在程序中指定
@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); //最大连接数
//测试连接池的效率, 测试对mysql 5000 次操作
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection(); //从连接池获取连接
//System.out.println("连接OK");
connection.close(); //关闭连接(实际是放回连接池)
}
long end = System.currentTimeMillis();
System.out.println("c3p0 5000 连接mysql 耗时=" + (end - start));//c3p0 5000 连接mysql 耗时=391
}
//第二种方式: 使用配置文件模板来完成
@Test
public void testC3P0_02() throws SQLException {
//1. 将c3p0 提供的c3p0-config.xml 拷贝到src 目录下
//2. 该文件指定了连接数据库和连接池的相关参数
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("hsp_edu");
//测试500000次连接mysql
long start = System.currentTimeMillis();
System.out.println("开始执行....");
for (int i = 0; i < 500000; i++) {
Connection connection = comboPooledDataSource.getConnection();
//System.out.println("连接OK~");
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("c3p0 的第二种方式(500000次) 耗时=" + (end - start));//约1917ms
}
}Druid(德鲁伊)应用实例
使用代码实现Druid数据库连接池
package com.hspedu.jdbc.datasource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.junit.jupiter.api.Test;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* @author 韩顺平
* @version 1.0
* 测试druid的使用
*/
public class Druid_ {
@Test
public void testDruid() throws Exception {
//1. 加入Druid jar 包
//2. 加入配置文件druid.properties , 将该文件拷贝到项目的src 目录
//3. 创建Properties 对象, 读取配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//4. 创建一个指定参数的数据库连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//测试连接池效率(500000次连接)
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
Connection connection = dataSource.getConnection();
//System.out.println("连接成功!");
connection.close(); //放回连接池
}
long end = System.currentTimeMillis();
System.out.println("druid 连接池操作500000次耗时=" + (end - start));//约539ms
}
}将JDBCUtils工具类改成Druid(德鲁伊)实现
基于Druid数据库连接池的工具类
package com.hspedu.jdbc.datasource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @author 韩顺平
* @version 1.0
* 基于druid 数据库连接池的工具类
*/
public class JDBCUtilsByDruid {
private static DataSource ds;
//在静态代码块完成ds 初始化
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//编写getConnection 方法
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//关闭连接, 注意: 在数据库连接池技术中,close 不是真的断掉连接
//而是把使用的Connection 对象放回连接池
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}工具类使用示例:
package com.hspedu.jdbc.datasource;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
/**
* @author 韩顺平
* @version 1.0
*/
@SuppressWarnings({"all"})
public class JDBCUtilsByDruid_USE {
@Test
public void testSelect() {
System.out.println("使用druid 方式完成查询");
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "select * from actor where id >= ?";
PreparedStatement preparedStatement = null;
ResultSet set = null;
try {
connection = JDBCUtilsByDruid.getConnection();
System.out.println(connection.getClass());//运行类型com.alibaba.druid.pool.DruidPooledConnection
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 1);//给?号赋值
//执行, 得到结果集
set = preparedStatement.executeQuery();
//遍历该结果集
while (set.next()) {
int id = set.getInt("id");
String name = set.getString("name");
String sex = set.getString("sex");
Date borndate = set.getDate("borndate");
String phone = set.getString("phone");
System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate + "\t" + phone);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtilsByDruid.close(set, preparedStatement, connection);
}
}
//将ResultSet 封装到ArrayList<Actor>中
@Test
public ArrayList<Actor> testSelectToArrayList() {
System.out.println("使用druid 方式完成查询并封装");
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "select * from actor where id >= ?";
PreparedStatement preparedStatement = null;
ResultSet set = null;
ArrayList<Actor> list = new ArrayList<>();//创建ArrayList 对象,存放actor 对象
try {
connection = JDBCUtilsByDruid.getConnection();
System.out.println(connection.getClass());//运行类型com.alibaba.druid.pool.DruidPooledConnection
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 1);//给?号赋值
//执行, 得到结果集
set = preparedStatement.executeQuery();
//遍历该结果集
while (set.next()) {
int id = set.getInt("id");
String name = set.getString("name");
String sex = set.getString("sex");
Date borndate = set.getDate("borndate");
String phone = set.getString("phone");
//把得到的resultset 的记录,封装到Actor 对象,放入到list 集合
list.add(new Actor(id, name, sex, borndate, phone));
}
System.out.println("list 集合数据=" + list);
for(Actor actor : list) {
System.out.println("id=" + actor.getId() + "\t" + actor.getName());
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtilsByDruid.close(set, preparedStatement, connection);
}
//因为ArrayList 和connection 没有任何关联,所以该集合可以复用
return list;
}
}Apache-DBUtils
问题分析
- 关闭
connection后,resultSet结果集无法使用。 resultSet不利于数据的管理(只能用一次,数据结构不直观)。- 数据复用性差,无法直接将结果集转换为业务对象。
解决方案:将结果集记录封装到ArrayList<Actor>中,一个Actor对象对应一条actor表记录。
自定义封装方式(土方法)
参考JDBCUtilsByDruid_USE中的testSelectToArrayList方法。
基本介绍
commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutils能极大简化jdbc编码的工作量。
核心组件:
DbUtils类:提供数据库连接相关的工具方法QueryRunner类:封装了SQL的执行,是线程安全的,可实现增、删、改、查、批处理ResultSetHandler接口:用于处理java.sql.ResultSet,将数据按要求转换为另一种形式
常用ResultSetHandler实现类:
| 实现类 | 功能描述 |
|---|---|
ArrayHandler | 把结果集中的第一行数据转成对象数组 |
ArrayListHandler | 把结果集中的每一行数据都转成一个数组,再存放到List中 |
BeanHandler | 将结果集中的第一行数据封装到一个对应的JavaBean实例中 |
BeanListHandler | 将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里 |
ColumnListHandler | 将结果集中某一列的数据存放到List中 |
KeyedHandler(name) | 将结果集中的每行数据都封装到Map里,再把这些map存到一个map里,其key为指定的列名 |
MapHandler | 将结果集中的第一行数据封装到一个Map里,key是列名,value是对应的值 |
MapListHandler | 将结果集中的每一行数据都封装到一个Map里,然后再存放到List |
应用实例
使用DBUtils + Druid方式,完成对actor表的CRUD操作
package com.hspedu.jdbc.datasource;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.util.List;
/**
* @author 韩顺平
* @version 1.0
* 使用apache-DBUtils 工具类+ druid 完成对表的crud 操作
*/
@SuppressWarnings({"all"})
public class DBUtils_USE {
//查询多行记录
@Test
public void testQueryMany() throws SQLException {
//1. 得到连接(druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 组织sql
String sql = "select id, name from actor where id >= ?";
//4. 执行查询,得到结果集(封装为Actor列表)
/*
老韩解读:
(1) query 方法执行sql 语句,得到resultset 并封装到ArrayList 集合中
(2) connection: 数据库连接
(3) sql: 执行的sql 语句
(4) new BeanListHandler<>(Actor.class): 将resultset -> Actor 对象-> 封装到ArrayList
底层使用反射机制获取Actor 类的属性,然后进行封装
(5) 1: 给sql 语句中的? 赋值(可变参数)
(6) 底层会自动关闭resultset 和PreparedStatement
*/
List<Actor> list = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
System.out.println("===查询结果===");
for (Actor actor : list) {
System.out.println(actor);
}
//释放资源(只需要关闭连接,因为DBUtils已关闭resultset和statement)
JDBCUtilsByDruid.close(null, null, connection);
}
//查询单行记录(单个对象)
@Test
public void testQuerySingle() throws SQLException {
//1. 得到连接(druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 组织sql
String sql = "select * from actor where id = ?";
//4. 执行查询(BeanHandler用于封装单行记录)
Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 6);
System.out.println("====查询单行结果====");
System.out.println(actor);
//释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
//查询单行单列(返回单个值)
@Test
public void testScalar() throws SQLException {
//1. 得到连接(druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 组织sql
String sql = "select name from actor where id = ?";
//4. 执行查询(ScalarHandler用于封装单行单列值)
Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 4);
System.out.println("====查询单行单列值===");
System.out.println(obj);
//释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
//执行dml操作(insert, update, delete)
@Test
public void testDML() throws SQLException {
//1. 得到连接(druid)
Connection connection = JDBCUtilsByDruid.getConnection();
//2. 创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
//3. 组织sql(可替换为insert、update、delete)
//String sql = "update actor set name = ? where id = ?";
//String sql = "insert into actor values(null, ?, ?, ?, ?)";
String sql = "delete from actor where id = ?";
//4. 执行dml操作(update方法返回受影响的行数)
//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);
}
}表和JavaBean的类型映射关系
| 数据库类型 | Java类型 | 说明 |
|---|---|---|
| int(11) | Integer | 包装类可接收null值 |
| varchar(20) | String | 字符串类型通用 |
| char(11) | String | 字符类型映射为String |
| double | Double | 包装类可接收null值 |
| date | Date/String | 可根据需求选择Date或String类型 |
JavaBean示例:
public class Employee {
private Integer eid;
private String ename;
private String tel;
private String gender; //mysql中char类型映射为String
private Double salary;
private Double commissionPct;
private Date birthday; //可使用Date或String
private Date hiredate;
private Integer jobId;
private String email;
private Integer mid;
private String address;
private String nativePlace;
private Integer did;
//必须提供无参构造器(反射需要)
public Employee() {}
//getter和setter方法
//toString方法
}DAO 和增删改查通用方法-BasicDAO
问题分析
apache-dbutils + Druid简化了JDBC开发,但仍有不足:
- SQL语句固定,不能通过参数传入,通用性不好。
- 对于select操作,返回类型固定,需要使用泛型。
- 表数量多、业务复杂时,单个Java类无法满足需求。
解决方案:设计BasicDAO通用类,作为所有DAO的父类,封装通用的CRUD操作。
基本说明
- DAO:Data Access Object(数据访问对象),专门负责数据库(表)的CRUD操作。
BasicDAO:通用DAO类,封装所有DAO的共同操作,子类(如ActorDAO、GoodsDAO)可继承并实现特有业务。- 设计规范:一张表对应一个JavaBean(domain/pojo)和一个DAO子类。
BasicDAO应用实例
项目结构
com.hspedu.dao_
├── utils // 工具类(JDBCUtilsByDruid)
├── domain // JavaBean(Actor、Goods等)
├── dao // DAO类(BasicDAO、ActorDAO等)
└── test // 测试类(TestDAO)JavaBean(Actor.java)
package com.hspedu.dao_.domain;
import java.util.Date;
/**
* @author 韩顺平
* @version 1.0
* Actor 对象和actor 表的记录对应(JavaBean/POJO/Domain)
*/
public class Actor {
private Integer id;
private String name;
private String sex;
private Date borndate;
private String phone;
//无参构造器(必须,反射需要)
public Actor() {}
//有参构造器
public Actor(Integer id, String name, String sex, Date borndate, String phone) {
this.id = id;
this.name = name;
this.sex = sex;
this.borndate = borndate;
this.phone = phone;
}
//getter和setter方法
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getSex() { return sex; }
public void setSex(String sex) { this.sex = sex; }
public Date getBorndate() { return borndate; }
public void setBorndate(Date borndate) { this.borndate = borndate; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
//toString方法
@Override
public String toString() {
return "\nActor{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", borndate=" + borndate +
", phone='" + phone + '\'' +
'}';
}
}工具类(JDBCUtilsByDruid.java)
同25.11.6中的工具类,略。
通用DAO(BasicDAO.java)
package com.hspedu.dao_.dao;
import com.hspedu.dao_.utils.JDBCUtilsByDruid;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* @author 韩顺平
* @version 1.0
* 开发BasicDAO,是其他DAO的父类,使用apache-dbutils
* @param <T> 泛型,指定DAO操作的JavaBean类型
*/
public class BasicDAO<T> {
//QueryRunner线程安全,可复用
private QueryRunner qr = new QueryRunner();
//通用的dml方法(insert、update、delete),针对任意表
public int update(String sql, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
return qr.update(connection, sql, parameters);
} catch (SQLException e) {
//将编译异常转为运行异常抛出
throw new RuntimeException(e);
} finally {
//关闭连接(放回连接池)
JDBCUtilsByDruid.close(null, null, connection);
}
}
//查询多行记录,返回List<T>,针对任意表
public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
//BeanListHandler将resultset封装为List<T>
return qr.query(connection, sql, new BeanListHandler<>(clazz), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
//查询单行记录,返回T对象,针对任意表
public T querySingle(String sql, Class<T> clazz, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
//BeanHandler将resultset封装为单个T对象
return qr.query(connection, sql, new BeanHandler<>(clazz), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
//查询单行单列值(如count、sum等),返回Object
public Object queryScalar(String sql, Object... parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
//ScalarHandler处理单行单列结果
return qr.query(connection, sql, new ScalarHandler(), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null, null, connection);
}
}
}具体DAO(ActorDAO.java)
package com.hspedu.dao_.dao;
import com.hspedu.dao_.domain.Actor;
/**
* @author 韩顺平
* @version 1.0
* ActorDAO:专门操作actor表,继承BasicDAO
*/
public class ActorDAO extends BasicDAO<Actor> {
//继承BasicDAO的所有通用方法
//可根据业务需求添加特有方法
}测试类(TestDAO.java)
package com.hspedu.dao_.test;
import com.hspedu.dao_.dao.ActorDAO;
import com.hspedu.dao_.domain.Actor;
import org.junit.jupiter.api.Test;
import java.util.List;
/**
* @author 韩顺平
* @version 1.0
* 测试ActorDAO对actor表的CRUD操作
*/
public class TestDAO {
@Test
public void testActorDAO() {
ActorDAO actorDAO = new ActorDAO();
//1. 查询多行记录
List<Actor> actors = actorDAO.queryMulti("select * from actor where id >= ?", Actor.class, 1);
System.out.println("===查询结果===");
for (Actor actor : actors) {
System.out.println(actor);
}
//2. 查询单行记录
Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 6);
System.out.println("====查询单行结果====");
System.out.println(actor);
//3. 查询单行单列值
Object o = actorDAO.queryScalar("select name from actor where id = ?", 6);
System.out.println("====查询单行单列值===");
System.out.println(o);
//4. 执行dml操作(insert)
int update = actorDAO.update("insert into actor values(null, ?, ?, ?, ?)", "张无忌", "男", "2000-11-11", "999");
System.out.println(update > 0 ? "执行成功" : "执行没有影响表");
}
}课后练习
开发GoodsDao和Goods类,完成对goods表的CRUD操作。
goods表结构:
| 字段名 | 类型 | 约束 |
|---|---|---|
| id | int(11) | 主键,自增 |
| goods_name | varchar(10) | 非空 |
| price | double | 可空 |
测试数据:
insert into goods values
(null, '华为手机', 2000),
(null, '苹果手机', 3000),
(null, '小米手机', 2000),
(null, 'vivo手机', 2300),
(null, '三星手机', null),
(null, 'IBM手机', 5000),
(null, '格力手机', 1800),
(null, '海尔手机', null);