JDBC
JDBC概述
- JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题·
- Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
-
说明:JDBC是Java提供一套用于数据库操作的接口API,Java程序员只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同实现。
-
JDBC API
JDBCAPI是一系列的接口,它统一和规范了应用程序与数据库的连接、执行SQL语句,并到得到返回结果等各类操作,相关类和接口在java.sql与javax.sgl包中
JDBC快速入门
JDBC程序编写步骤
- 注册驱动 ——加载Driver类
- 获取连接——得到Connection
- 执行增删改查——发送SQL给MySQL执行
- 释放资源——关闭相关连接
JDBC第一个程序
public class Jdbc01 {
public static void main(String[] args) throws SQLException {
// 前置工作,在项目下创建文件夹libs
// 将mysql.jar复制到libs下,点击 add to library 加入到项目
// 1. 注册驱动
Driver driver = new Driver(); // 创建driver对象
// 2. 得到连接
// (1) jdbc:mysql:// 规定好的表示协议,通过jdbc的当时连接mysql
// (2) localhost:主机,可以是ip地址
// (3) 3306:端口号,表示mysql监听的端口
// (4) db02:数据库名,来年街道mysql dbms的哪个数据库
// (5) mysql的连接本质就是前面学过的socket连接
String url = "jdbc:mysql://localhost:3306/db02";
// 将用户名和密码放入到Properties对象
Properties properties = new Properties();
// 说明:user和password是固定的,不能修改,后面的值根据实际情况写
properties.setProperty("user", "root"); // 用户
properties.setProperty("password", "root"); // 密码
Connection connect = driver.connect(url, properties);
// 3. 执行sql
String sql = "insert into actor values(null, '刘德华', '男', '1970-01-01', '1500543')";
// statement用于执行静态SQL语句并返回其生成的结果对象
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql); // 如果是dml语句,返回的就是影响的行数
System.out.println(rows > 0 ? "成功" : "失败");
// 4. 关闭连接
statement.close();
connect.close();
}
}
连接JDBC的方式
-
获取Driver实现类对象
//方式1 @Test public void connect01() throws SQLException { Driver driver = new Driver(); //创建driver对象 String url = "jdbc:mysql://localhost:3306/db02"; //将 用户名和密码放入到Properties 对象 Properties properties = new Properties(); //说明 user 和 password 是规定好,后面的值根据实际情况写 properties.setProperty("user", "root");// 用户 properties.setProperty("password", "root"); //密码 Connection connect = driver.connect(url, properties); System.out.println(connect); }
属于静态加载,依赖性比较强,灵活性不够高。
-
使用反射机制,动态加载
// 方式2 @Test public void connect02() throws Exception { // 使用反射加载 Driver 类, 动态加载,更加的灵活,减少依赖性 Class<?> aClass = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver) aClass.getConstructor().newInstance(); String url = "jdbc:mysql://localhost:3306/db02"; //将 用户名和密码放入到Properties 对象 Properties properties = new Properties(); //说明 user 和 password 是规定好,后面的值根据实际情况写 properties.setProperty("user", "root");// 用户 properties.setProperty("password", "root"); //密码 Connection connect = driver.connect(url, properties); System.out.println("方式二:" + connect); }
-
使用 DriverManager 替换Driver,进行统一管理,具有更好的扩展性
// 方式3 @Test public void connect03() throws Exception { // 使用反射加载 Driver Class<?> aClass = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver) aClass.getConstructor().newInstance(); // 创建 url 和 user 和 password String url = "jdbc:mysql://localhost:3306/db02"; String user = "root"; String password = "root"; DriverManager.registerDriver(driver); // 注册Driver驱动 Connection connect = DriverManager.getConnection(url, user, password); System.out.println("方式三:" + connect); }
-
使用
Class.forName
自动完成注册驱动,简化代码 【这种方式使用最多】// 方式4 @Test public void connect04() throws Exception { // 使用反射加载 Driver // 在加载 Driver 类是,完成注册 /* 源码: 1. 静态代码块,在类加载的时候会执行一次 2. DriverManager.registerDriver(new Driver()); 3. 因此注册driver的工作已经完成 static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } */ Class.forName("com.mysql.jdbc.Driver"); // 创建 url 和 user 和 password String url = "jdbc:mysql://localhost:3306/db02"; String user = "root"; String password = "root"; Connection connect = DriverManager.getConnection(url, user, password); System.out.println("方式四:" + connect); }
注意:
- mysqL驱动5.1.6可以无需CLass.forName(“com.mysql.jdbc.Driver”);
- 从jdk1.5以后使用了jdbc4,不再需要显示调用class.forName()注册驱动而是自动调用驱动jar包下META-INF\services\java.sql.Driver文本中的类名称去注册
- 建议还是写上CLass.forName(“com.mysql.jdbc.Driver”);更加明确
-
使用配置文件,连接数据库更灵活(在方式4的基础上进行改进)
// 方式5 @Test public void connect05() throws Exception { // 通过 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"); Class.forName(driver); Connection connect = DriverManager.getConnection(url, user, password); System.out.println("方式五:" + connect); }
配置文件mysql.properties:
user=root password=root url=jdbc:mysql://localhost:3306/db02 driver=com.mysql.jdbc.Driver
ReultSet
-
基本介绍
- 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
- ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
- next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集
-
代码示例
public class ResultSet_ { public static void main(String[] args) throws Exception { // 通过 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"); // 1. 注册驱动 Class.forName(driver); // 2. 得到连接 Connection connect = DriverManager.getConnection(url, user, password); // 3. 得到 Statement Statement statement = connect.createStatement(); // 组织 sql String sql = "select id, name, sex, borndate from actor"; // 执行给定的SQL语句,该语句返回单个 ResultSet 对象 ResultSet resultSet = statement.executeQuery(sql); // 5. 使用while循环取出数据 System.out.println("id\t" + "name\t" + "sex \t" + "borndate"); while (resultSet.next()) { // 让光标向后移动,如果没有更多行,则返回false int id = resultSet.getInt(1); // 获取该行第1列 String name = resultSet.getString(2); // 获取该行第2列 String sex = resultSet.getString(3); Date borndate = resultSet.getDate(4); System.out.println(id + "\t" + name + "\t" + sex + "\t" + borndate); } // 6. 关闭连接 resultSet.close(); statement.close(); } }
Statement
Statement
-
基本介绍
- Statement对象用于执行静态SQL语句并返回其生成的结果的对象
- 在连接建立后,需要对数据库进行访问,执行命名或是SQL语句,可以通过
- Statement[存在SQL注入]
- PreparedStatement[预处理]
- CallableStatement[存储过程]
- Statement对象执行SQL语句,存在SQL注入风险
- SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库。
- 要防范SQL注入,只要用PreparedStatement(从Statement扩展而来)取代Statement就可以了
-
SQL 注入示例:
-- 创建admin表,存储用户名和密码 CREATE TABLE admin ( `name` VARCHAR(32) NOT NULL UNIQUE, pwd VARCHAR(32) NOT NULL DEFAULT ''); INSERT INTO admin VALUES('tom', '123'); -- 正常查询,只有当`name` = 'tom', `pwd` = '123'都正确的时候才能查询到 SELECT * FROM admin WHERE `name` = 'tom' AND `pwd` = '123'; -- 使用sql注入,按照如下方式输入也可以查询到 -- 输入用户名:1' or -- 输入密码:or '1' = '1 SELECT * FROM admin WHERE `name` = '1' OR' AND `pwd` = 'OR '1' = '1';
-
statement 注入:
@SuppressWarnings("all") public class Statement_ { public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); // 让用户输入管理员名和密码 System.out.println("请输入管理员名:"); String admin_name = scanner.nextLine(); System.out.println("请输入管理员密码:"); String admin_pwd = scanner.nextLine(); // 通过 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"); // 1. 注册驱动 Class.forName(driver); // 2. 得到连接 Connection connect = DriverManager.getConnection(url, user, password); // 3. 得到 Statement Statement statement = connect.createStatement(); // 组织 sql String sql = "select name, pwd from admin where name = '" + admin_name + "' and pwd = '" + admin_pwd + "'"; ResultSet resultSet = statement.executeQuery(sql); if (resultSet.next()) { // 如果查询到一条记录说明该管理员存在 System.out.println("管理员存在"); } else { System.out.println("管理员不存在"); } // 关闭连接 resultSet.close(); statement.close(); connect.close(); } }
PreparedStatement
String sql = "select name, pwd from admin where name = ? and pwd = ?";
-
基本介绍
- PreparedStatement 执行的SQL语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数。
- setXxx() 方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值
- 调用executeQuery(),返回ResultSet对象
- 调用executeUpdate():执行更新,包括增、删、修改
-
预处理的好处
- 不再使用+拼接sgl语句,减少语法错误
- 有效的解决了sgl注入问题!
- 大大减少了编译次数,效率较高
-
select语句示例
@SuppressWarnings("all") public class PreparedStatement_ { public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); // 让用户输入管理员名和密码 System.out.println("请输入管理员名:"); String admin_name = scanner.nextLine(); System.out.println("请输入管理员密码:"); String admin_pwd = scanner.nextLine(); // 通过 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"); // 1. 注册驱动 Class.forName(driver); // 2. 得到连接 Connection connect = DriverManager.getConnection(url, user, password); // 3. 得到 PreparedStatement // 3.1 组织 sql,sql语句的参数使用 ? 占位符 // select 语句 String sql = "select name, pwd from admin where name = ? and pwd = ?"; // 3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象 PreparedStatement preparedStatement = connect.prepareStatement(sql); // 3.3 给 ? 赋值 preparedStatement.setString(1, admin_name); // 第一个? preparedStatement.setString(2, admin_pwd); // 第二个? // 4. 执行 select 语句使用 executeQuery // 如果执行的是 dml 语句,则使用 executeUpdate // (sql)中的sql可以不用再写sql(),如果填写了sql,则会将上面的sql语句放入()中执行,name = ? and pwd = ?,会报错 ResultSet resultSet = preparedStatement.executeQuery(); if (resultSet.next()) { // 如果查询到一条记录说明该管理员存在 System.out.println("管理员存在"); } else { System.out.println("管理员不存在"); } // 关闭连接 resultSet.close(); preparedStatement.close(); connect.close(); } }
-
dml语句示例
@SuppressWarnings("all") public class PreparedStatementDML { public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); // 让用户输入管理员名和密码 System.out.println("请输入管理员名:"); String admin_name = scanner.nextLine(); // System.out.println("请输入管理员密码:"); // String admin_pwd = scanner.nextLine(); // 通过 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"); // 1. 注册驱动 Class.forName(driver); // 2. 得到连接 Connection connect = DriverManager.getConnection(url, user, password); // 3. 得到 PreparedStatement // 3.1 组织 sql,sql语句的参数使用 ? 占位符 // dml 语句 // 添加记录 // String sql = "insert into admin(name, pwd) values(?, ?)"; // 修改记录 // String sql = "update admin set pwd = ? where name = ?"; // 删除记录 String sql = "delete from admin where name = ?"; // 3.2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象 PreparedStatement preparedStatement = connect.prepareStatement(sql); // 3.3 给 ? 赋值 preparedStatement.setString(1, admin_name); // 第一个? // preparedStatement.setString(2, admin_pwd); // 第二个? // 4. 执行 dml 语句使用 executeUpdate int rows = preparedStatement.executeUpdate(); System.out.println(rows > 0 ? "执行成功" : "执行失败"); // 关闭连接 preparedStatement.close(); connect.close(); } }
JDBC API
JDBCUtils
说明:在jdbc操作中,获取连接和释放资源是经常使用到的(连接和关闭)可以将其封装JDBC连接的工具类JDBCUtils。
public class JDBCUtils {
// 定义相关的属性(4个),因为只需要一份,设置为static
private static String url; // url
private static String user; // 用户名
private static String password; // 密码
private static String driver; // 驱动名
// 在static代码块中初始化
static {
try {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
// 读取相关的属性
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
driver = properties.getProperty("driver");
} catch (IOException e) {
// 在实际开发中,这样处理
// 1. 将编译异常转成运行异常
// 2. 调用者可以选择捕获异常,也可以选择默认处理该异常,比较方便
throw new RuntimeException(e);
}
}
// 连接数据库,返回Connection
public static Connection getConnection() {
try {
return DriverManager.getConnection(url,user, password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// 关闭相关资源
/*
1. ResultSet 结果集
2. Statement 或者 PreparedStatement
3. Connection
4. 如果需要关闭资源,就传入对象,否则传入 null
*/
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);
}
}
}
-
JDBCUtils 测试案例——dml
@Test public class JDBCUtils_Use { @Test public void testDML() { // insert, update, delete // 1. 得到连接 Connection connection = null; // 2. 组织一个sql String sql = "update actor set name = ? where id = ?"; PreparedStatement preparedStatement = null; // 3, 创建 PreparedStatement 对象 try { connection = JDBCUtils.getConnection(); preparedStatement = connection.prepareStatement(sql); // 给占位符赋值 preparedStatement.setString(1, "周星驰"); preparedStatement.setInt(2, 3); // 执行sql int rows = preparedStatement.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } finally { // 关闭资源 JDBCUtils.close(null, preparedStatement, connection); } } }
-
JDBCUtils 测试案例——select
@Test public void testSelect() { // 1. 得到连接 Connection connection = null; // 2. 组织一个sql String sql = "select * from actor"; PreparedStatement preparedStatement = null; ResultSet resultSet = null; // 3, 创建 PreparedStatement 对象 try { connection = JDBCUtils.getConnection(); preparedStatement = connection.prepareStatement(sql); // 执行sql,得到 resultSet = preparedStatement.executeQuery(); // 遍历结果集 while (resultSet.next()) { System.out.println( resultSet.getString("id") + "\t" + resultSet.getString("name") + "\t" + resultSet.getString("sex") + "\t" + resultSet.getDate("borndate") + "\t" + resultSet.getString("phone") ); } } catch (SQLException e) { throw new RuntimeException(e); } finally { // 关闭资源 JDBCUtils.close(resultSet, preparedStatement, connection); } }
事务
-
基本介绍
- JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
- JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务
- 调用 Connection 的 setAutoCommit(false),可以取消自动提交事务
- 在所有的SQL语句都成功执行后,调用Connection 的 commit():方法提交事务
- 在其中某个操作失败或出现异常时,调用Connection 的 rollback():方法回滚事务
-
不适用事务和使用事务的两个案例
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; // 3, 创建 PreparedStatement 对象 try { connection = JDBCUtils.getConnection(); // 默认情况下,connection是默认自动提交的 preparedStatement = connection.prepareStatement(sql); preparedStatement.executeUpdate(); // 执行第一条sql语句 int i = 1 / 0; // 抛出异常 preparedStatement = connection.prepareStatement(sql2); preparedStatement.executeUpdate(); // 执行第二条sql语句 } catch (SQLException e) { throw new RuntimeException(e); } 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; // 3, 创建 PreparedStatement 对象 try { connection = JDBCUtils.getConnection(); // 默认情况下,connection是默认自动提交的 // 将 connection 设置为不自动提交 connection.setAutoCommit(false); // 表示开始了一个事务(事务开始点) preparedStatement = connection.prepareStatement(sql); preparedStatement.executeUpdate(); // 执行第一条sql语句 // int i = 1 / 0; // 抛出异常 preparedStatement = connection.prepareStatement(sql2); preparedStatement.executeUpdate(); // 执行第二条sql语句 // 这里提交事务 connection.commit(); } catch (SQLException e) { // 这里可以进行回滚,即撤销执行的sql // 默认回滚到事务开始的状态 System.out.println("事务回滚"); try { connection.rollback(); } catch (SQLException ex) { throw new RuntimeException(ex); } throw new RuntimeException(e); } finally { // 关闭资源 JDBCUtils.close(null, preparedStatement, connection); } } }
批处理
-
基本介绍
-
当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
-
JDBC的批量处理语句包括下面方法:
- addBatch():添加需要批量处理的SQL语句或参数
- executeBatch():执行批量处理语句
- clearBatch():清空批处理包的语句
-
JDBC连接MySQL时,如果要使用批处理功能,请在mysql.properties配置文件url中加参数:
?rewriteBatchedStatements=true
==> ``url=jdbc:mysql://localhost:3306/db02?rewriteBatchedStatements=true` -
批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,效率大大提高
-
-
不适用批处理和使用批处理使用案例
public class Batch_ { // 传统方法,添加5000条数据到admin2 @Test public void noBatch() throws SQLException { 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++) { preparedStatement.setString(1, "jack" + i); preparedStatement.setString(2, "666"); preparedStatement.executeUpdate(); } long end = System.currentTimeMillis(); // 结束时间 System.out.println("传统方法耗时:" + (end - start)); // 传统方法耗时:136786 JDBCUtils.close(null, preparedStatement, connection); } // 批量方法,添加5000条数据到admin2 @Test public void useBatch() throws SQLException { 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++) { preparedStatement.setString(1, "jack" + i); preparedStatement.setString(2, "666"); // preparedStatement.executeUpdate(); // 将sql语句加入到批处理包中 preparedStatement.addBatch(); // 当有1000条记录时,再批量执行 if ((i + 1) % 1000 == 0) { preparedStatement.executeBatch(); // 清空批处理队列 preparedStatement.clearBatch(); } } long end = System.currentTimeMillis(); // 结束时间 System.out.println("批量方法耗时:" + (end - start)); // 批量方法耗时:435 JDBCUtils.close(null, preparedStatement, connection); } }
-
批处理指令
preparedStatement.addBatch();
:将sql语句加入到批处理包中preparedStatement.executeBatch();
:批量执行preparedStatement.clearBatch();
:清空批处理队列
-
批处理会减少发送sql语句的网络开销,而且减少编译次数,因此效率提高
数据库连接池
-
传统获取Connection问题分析
- 传统的JDBC数据库连接使用DriverManager来获取,每次向数据库建立连接的时候都要将Connection加载到内存中,再验证IP地址,用户名和密码(0.05s~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
- 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
- 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃。
- 解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)。
-
数据库连接池基本介绍
- 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
- 2数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
-
画图理解
-
数据库连接池种类
- JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现
- C3P0数据库连接池,速度相对较慢,稳定性不错(hibernate,spring)
- DBCP数据库连接池,速度相对c3p0较快,但不稳定
- Proxool数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
- BoneCP数据库连接池,速度快
- Druid(德鲁伊)是阿里提供的数据库连接池,集DBCP、C3PO、Proxool优点于一身的数据库连接池
C3P0数据库连接池
两种连接方式【优选方式2】
public class C3P0_ {
// 方式1:相关参数在程序中指定user, url, password
@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 url = properties.getProperty("url");
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
// 给数据源 comboPooledDataSource 设置相关的参数
// 注意:连接管理是由 comboPooledDataSource 来管理
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
comboPooledDataSource.setDriverClass(driver);
// 设置初始连接数、最大连接数
comboPooledDataSource.setInitialPoolSize(10);
comboPooledDataSource.setMaxPoolSize(50);
// 连接 测试对mysql 5000次操作
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection(); // 这个方法就是从 DataSource 接口实现的
// System.out.println("连接成功");
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("c3p0 5000次连接数据库花费时间:" + (end - start) + "ms"); // c3p0 5000次连接数据库花费时间:383ms
}
// 方式2:使用配置文件模板来完成
// 1. 将c3p0提供的c3p0-config.xml拷贝到src目录下
// 2. 该文件制定了连接数据库和连接池的相关参数
@Test
public void testC3P0_02() throws Exception {
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("hsp_java"); // 填入c3p0-config.xml文件中配置的named-config
// 连接 测试对mysql 5000次操作
long start = System.currentTimeMillis();
for (int i = 0; i < 5000; i++) {
Connection connection = comboPooledDataSource.getConnection(); // 这个方法就是从 DataSource 接口实现的
// System.out.println("连接成功");
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("c3p0方式2配置文件 5000次连接数据库花费时间:" + (end - start) + "ms"); // c3p0方式2配置文件 5000次连接数据库花费时间:374ms
}
}
c3p0-config.xml文件:
<c3p0-config>
<!-- 数据源名称代表连接池 -->
<named-config name="hsp_java">
<!-- 驱动类 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<!-- url-->
<property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/db02</property>
<!-- 用户名 -->
<property name="user">root</property>
<!-- 密码 -->
<property name="password">root</property>
<!-- 每次增长的连接数-->
<property name="acquireIncrement">5</property>
<!-- 初始的连接数 -->
<property name="initialPoolSize">10</property>
<!-- 最小连接数 -->
<property name="minPoolSize">5</property>
<!-- 最大连接数 -->
<property name="maxPoolSize">50</property>
<!-- 可连接的最多的命令对象数 -->
<property name="maxStatements">5</property>
<!-- 每个连接对象可连接的最多的命令对象数 -->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
Druid数据库连接池
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);
// 连接 测试对mysql 500000次操作
long start = System.currentTimeMillis();
for (int i = 0; i < 500000; i++) {
Connection connection = dataSource.getConnection(); // 这个方法就是从 DataSource 接口实现的
// System.out.println("连接成功");
connection.close();
}
long end = System.currentTimeMillis();
System.out.println("druid 500000次连接数据库花费时间:" + (end - start) + "ms"); // druid 500000次连接数据库花费时间:660ms
}
}
druid.properties配置文件
#key=value
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db02?rewriteBatchedStatements=true
username=root
password=root
#initial connection Size
initialSize=10
#min idle connecton size
minIdle=5
#max active connection size
maxActive=50
#max wait time (5000 mil seconds)
maxWait=5000
-
使用druid工具类JDBCUtilsByDruid:
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) { throw new RuntimeException(e); } } // 编写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); } } }
-
测试
@Test public void testSelect() { System.out.println("使用druid方式完成"); // 1. 得到连接 Connection connection = null; // 2. 组织一个sql String sql = "select * from actor"; PreparedStatement preparedStatement = null; ResultSet resultSet = null; // 3, 创建 PreparedStatement 对象 try { connection = JDBCUtilsByDruid.getConnection(); System.out.println(connection.getClass()); //运行类型 class com.alibaba.druid.pool.DruidPooledConnection preparedStatement = connection.prepareStatement(sql); // 执行sql,得到 resultSet = preparedStatement.executeQuery(); // 遍历结果集 while (resultSet.next()) { System.out.println( resultSet.getString("id") + "\t" + resultSet.getString("name") + "\t" + resultSet.getString("sex") + "\t" + resultSet.getDate("borndate") + "\t" + resultSet.getString("phone") ); } } catch (SQLException e) { throw new RuntimeException(e); } finally { // 关闭资源 JDBCUtilsByDruid.close(resultSet, preparedStatement, connection); } }
Apache——DBUtils
问题引出
- 关闭connection后,resultSet结果集无法使用
- resultSet不利于数据的管理,只能用一次
- 使用返回信息不方便(getString不明确),不知道返回的是name还是sex,若可以 getName或者getSex就好了
使用土方法实现封装:
// 使用土方法解决 ResultSet封装到 ArrayList
@Test
public ArrayList<Actor> testSelectToArrayList() {
System.out.println("使用druid方式完成");
// 1. 得到连接
Connection connection = null;
// 2. 组织一个sql
String sql = "select * from actor";
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
ArrayList<Actor> list = new ArrayList<>(); // 创建ArrayList对象,存放actor对象
// 3, 创建 PreparedStatement 对象
try {
connection = JDBCUtilsByDruid.getConnection();
System.out.println(connection.getClass()); //运行类型 class com.alibaba.druid.pool.DruidPooledConnection
preparedStatement = connection.prepareStatement(sql);
// 执行sql,得到
resultSet = preparedStatement.executeQuery();
// 遍历结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String sex = resultSet.getString("sex");
Date borndate = resultSet.getDate("borndate");
String phone = resultSet.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("演员名字:" + actor.getName());
}
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// 关闭资源
JDBCUtilsByDruid.close(resultSet, preparedStatement, connection);
}
// 因为Arraylist和 connection 没有任何关联,所以该集合可以复用
return list;
}
基本介绍
commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的封装使用 dbutils 能极大简化jdbc编码的工作量。
DbUtils类
- QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理
- 使用QueryRunner类实现查询
- ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler(name):将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List。
DbUtils类查询
-
应用案例:使用DbUtils+数据库连接池(德鲁伊)方式完成对表actor的crud
-
返回多行:
new BeanListHandler<>(Actor.class)
List<Actor> list = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
// 使用Apache-DbUtils+数据库连接池(德鲁伊)方式完成对表actor的crud @Test public void testQueryMany() throws Exception { // 返回结果是多行的情况 // 1. 得到连接对象 Connection connection = JDBCUtilsByDruid.getConnection(); // 2. 使用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关闭,同时关闭PreparedStatement */ List<Actor> list = queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1); System.out.println("输出集合信息"); list.forEach(System.out::print); // 释放资源 JDBCUtilsByDruid.close(null, null, connection); }
-
返回一行:
new BeanHandler<>(Actor.class)
Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 2);
// 使用Apache-DbUtils+数据库连接池(德鲁伊)方式 返回单行记录 @Test public void testQuerySingle() throws Exception { // 1. 得到连接对象 Connection connection = JDBCUtilsByDruid.getConnection(); // 2. 使用DbUtils类和接口,先引入相关的jar文件,再加入到本project // 3. 创建QueryRunner QueryRunner queryRunner = new QueryRunner(); // 4. 就可以执行相关的方法,返回ArrayList结果集 String sql = "select * from actor where id = ?"; // 因为我们返回的是单行记录<--->单个对象,使用的 Handler是 BeanHandler Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 10); System.out.println(actor); // Actor{id=2, name='刘德华', sex='男', borndate=1970-01-01 00:00:00.0, phone='1500543'} // 若id不存在,返回null // 释放资源 JDBCUtilsByDruid.close(null, null, connection); }
-
返回单行单列:
new ScalarHandler()
Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 2);
// 使用Apache-DbUtils+数据库连接池(德鲁伊)方式 完成查询结果是单行单列的情况,返回的就是object @Test public void testQueryScalar() throws Exception { // 1. 得到连接对象 Connection connection = JDBCUtilsByDruid.getConnection(); // 2. 使用DbUtils类和接口,先引入相关的jar文件,再加入到本project // 3. 创建QueryRunner QueryRunner queryRunner = new QueryRunner(); // 4. 就可以执行相关的方法,返回ArrayList结果集 String sql = "select name from actor where id = ?"; // 因为返回的是一个对象,使用的 Handler 是 ScalarHandler Object obj = queryRunner.query(connection, sql, new ScalarHandler(), 2); System.out.println(obj); // 刘德华 // 若id不存在,返回null // 释放资源 JDBCUtilsByDruid.close(null, null, connection); }
-
DbUtils类DML操作
queryRunner.update()
// 使用Apache-DbUtils+数据库连接池(德鲁伊)方式 完成dml语句
@Test
public void testDML() throws Exception {
// 1. 得到连接对象
Connection connection = JDBCUtilsByDruid.getConnection();
// 2. 使用DbUtils类和接口,先引入相关的jar文件,再加入到本project
// 3. 创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
// 4. 就可以执行相关的方法,返回ArrayList结果集
// 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) 返回的是受影响的行数,若为0,则抛出异常
// int affectedRows = queryRunner.update(connection, sql, "周华健", 4);
// int affectedRows = queryRunner.update(connection, sql, "章子怡", "女", "1990-01-01", "12345678901");
int affectedRows = queryRunner.update(connection, sql, 5);
System.out.println(affectedRows > 0 ? "执行成功" : "数据库没有变化");
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
BasicDao
DAO和增删改查通用方法-BasicDao
问题引出
apache-dbutils+Druid 简化了JDBC开发,但还有不足:
- SQL语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行增删改查
- 对于select操作,如果有返回值,返回类型不能固定,需要使用泛型
- 将来的表很多,业务需求复杂,不可能只靠一个Java类完成
- 引出=》BasicDAO画出示意图实际开发中如何处理
基本说明
- DAO:Data Access Object 数据访问对象
- 这样的通用类,称为BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作
- 在BaiscDao的基础上,实现一张表对应一个Dao,更好的完成功能,比如Customer表-Customer.java类(javabean)-CustomerDao.java
代码示例
以actor为例:
-
utils
package com.honvin.dao_.utils; 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; /** * @ClassName: JDBCUtilsByDruid * @Date: 2023/12/17 17:29 * @Author: Honvin * @Software: IntelliJ IDEA * @Description: 基于Druid的JDBC工具类 **/ 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) { throw new RuntimeException(e); } } // 编写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); } } }
-
javabean/domain
package com.honvin.dao_.domain; import java.util.Date; /** * @ClassName: Actor * @Date: 2023/12/17 17:52 * @Author: Honvin * @Software: IntelliJ IDEA * @Description: Actor对象和actor表的记录对应 **/ public class Actor { // Javabean, POJO, Domain对象 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; } 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; } @Override public String toString() { return "Actor{" + "id=" + id + ", name='" + name + '\'' + ", sex='" + sex + '\'' + ", borndate=" + borndate + ", phone='" + phone + '\'' + '}'; } }
-
BasicDAO
package com.honvin.dao_.dao; import com.honvin.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; /** * @ClassName: BasicDAO * @Date: 2023/12/17 20:09 * @Author: Honvin * @Software: IntelliJ IDEA * @Description: 开发BasicDAO,是其他DAO的父类 **/ public class BasicDAO<T> { // 泛型指定具体类型 private QueryRunner qr = new QueryRunner(); /** * 开发通用的dml方法,针对任意的表 * @param sql sql语句,可以有? * @param params 传入 ? 的具体的值,可以是多个 * @return 返回受影响的行数 */ public int update(String sql, Object... params) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.update(connection, sql, params); } catch (SQLException e) { throw new RuntimeException(e); } finally { JDBCUtilsByDruid.close(null, null,connection); } } /** * 返回多个对象(即查询的结果是多行),针对任意表 * @param sql sql语句,可以有? * @param clazz 传入一个类的CLass对象,比如Actor.class * @param params 传入 ? 的具体的值,可以是多个 * @return 根据Actor.class,返回对应的 ArrayList 集合 */ public List<T> queryMulti(String sql, Class<T> clazz, Object... params) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new BeanListHandler<T>(clazz), params); } catch (SQLException e) { throw new RuntimeException(e); } finally { JDBCUtilsByDruid.close(null, null, connection); } } /** * 查询单行结果的通用方法 * @param sql sql语句,可以有? * @param clazz 传入一个类的CLass对象,比如Actor.class * @param params 传入 ? 的具体的值,可以是多个 * @return 根据Actor.class,返回 单个查询对象 */ public T querySingle(String sql, Class<T> clazz, Object... params) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new BeanHandler<T>(clazz), params); } catch (SQLException e) { throw new RuntimeException(e); } finally { JDBCUtilsByDruid.close(null, null, connection); } } /** * 查询单行单列的通用方法,即返回单值的方法 * @param sql sql语句,可以有? * @param params 传入 ? 的具体的值,可以是多个 * @return 单值 */ public Object queryScalar(String sql, Object... params) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new ScalarHandler(), params); } catch (SQLException e) { throw new RuntimeException(e); } finally { JDBCUtilsByDruid.close(null, null, connection); } } }
-
ActorDAO
package com.honvin.dao_.dao; import com.honvin.dao_.domain.Actor; /** * @ClassName: ActorDAO * @Date: 2023/12/17 20:25 * @Author: Honvin * @Software: IntelliJ IDEA * @Description: **/ public class ActorDAO extends BasicDAO<Actor> { // 1. 就有 BasicDAO 所有的方法 // 2. 根据业务需求,可以编写特有的方法 }
-
TestDAO
package com.honvin.dao_.test; import com.honvin.dao_.dao.ActorDAO; import com.honvin.dao_.domain.Actor; import org.junit.Test; import java.util.List; /** * @ClassName: TestDAO * @Date: 2023/12/17 20:27 * @Author: Honvin * @Software: IntelliJ IDEA * @Description: **/ public class TestDAO { // 测试 ActorDAO 对actor表的crud操作 @Test public void testActorDAO() { ActorDAO actorDAO = new ActorDAO(); // 1. 查询多行记录 List<Actor> actors = actorDAO.queryMulti("select * from actor where id <= ?", Actor.class, 3); System.out.println("1. 查询多行记录"); actors.forEach(System.out::println); // 2. 查询单行记录 Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 2); System.out.println("2. 查询单行记录"); System.out.println(actor); // 3. 查询单值记录 Object o = actorDAO.queryScalar("select name from actor where id = ?", 2); System.out.println("3. 查询单值记录"); System.out.println(o); // dml操作 insert, update, delete int update = actorDAO.update("insert into actor values(null, '章子怡', '女', '1982-09-17', '12345')"); System.out.println("4. 更新记录"); System.out.println(update > 0 ? "更新成功" : "执行没有影像数据表"); } }
ctorDAO extends BasicDAO {
// 1. 就有 BasicDAO 所有的方法
// 2. 根据业务需求,可以编写特有的方法
}
* TestDAO
```java
package com.honvin.dao_.test;
import com.honvin.dao_.dao.ActorDAO;
import com.honvin.dao_.domain.Actor;
import org.junit.Test;
import java.util.List;
/**
* @ClassName: TestDAO
* @Date: 2023/12/17 20:27
* @Author: Honvin
* @Software: IntelliJ IDEA
* @Description:
**/
public class TestDAO {
// 测试 ActorDAO 对actor表的crud操作
@Test
public void testActorDAO() {
ActorDAO actorDAO = new ActorDAO();
// 1. 查询多行记录
List<Actor> actors = actorDAO.queryMulti("select * from actor where id <= ?", Actor.class, 3);
System.out.println("1. 查询多行记录");
actors.forEach(System.out::println);
// 2. 查询单行记录
Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 2);
System.out.println("2. 查询单行记录");
System.out.println(actor);
// 3. 查询单值记录
Object o = actorDAO.queryScalar("select name from actor where id = ?", 2);
System.out.println("3. 查询单值记录");
System.out.println(o);
// dml操作 insert, update, delete
int update = actorDAO.update("insert into actor values(null, '章子怡', '女', '1982-09-17', '12345')");
System.out.println("4. 更新记录");
System.out.println(update > 0 ? "更新成功" : "执行没有影像数据表");
}
}