【JavaSE】JDBC和连接池学习笔记
JDBC和连接池
-JDBC概述
-
基本介绍
-
JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。
-
Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而
完成对数据库的各种操作。 -
JDBC的基本原理
- 背景:调用不同数据库管理系统的方法不统一,因此在开发数据库相关应用时,会给Java工程师带来不便,不利于程序管理。
- 解决方案:Java厂商制定调用数据库方法的接口规范,各个数据库管理系统厂商开发相应的驱动包(Jar包),使Java工程师可以通过统一的接口来对不同厂商的数据库管理系统进行操作。
-
模拟JDBC
JDBC接口——厂商实现接口——Java工程师调用实现
package com.xijie.myjdbc;/*** 模拟一个JDBC接口*/ public interface MyJDBCInterface {void getConnection();void getCRUD();void closeConnection(); }
package com.xijie.myjdbc;/*** 模拟Mysql的JDBC实现*/ public class MysqlJDBC implements MyJDBCInterface{@Overridepublic void getConnection() {System.out.println("获取Mysql连接对象");}@Overridepublic void getCRUD() {System.out.println("进行Mysql增删改查");}@Overridepublic void closeConnection() {System.out.println("关闭Mysql连接对象");} }
package com.xijie.myjdbc;/*** 模拟DB2的JDBC实现*/ public class Db2JDBC implements MyJDBCInterface{@Overridepublic void getConnection() {System.out.println("获取Db2连接对象");}@Overridepublic void getCRUD() {System.out.println("进行Db2增删改查");}@Overridepublic void closeConnection() {System.out.println("关闭Db2连接对象");} }
package com.xijie.myjdbc;/*** 演示JDBC的原理*/ public class testMyJDBC {public static void main(String[] args) {MyJDBCInterface myJDBCInterface1=new MysqlJDBC();myJDBCInterface1.getConnection();myJDBCInterface1.getCRUD();myJDBCInterface1.closeConnection();//仅需创建不同的JDBC实现类即可调用不同的数据库MyJDBCInterface myJDBCInterface2=new Db2JDBC();myJDBCInterface2.getConnection();myJDBCInterface2.getCRUD();myJDBCInterface2.closeConnection();} }
-
-
JDBC带来的好处
-
如果Java直接访问数据库会怎样
- 兼容性极差:不同数据库(如 MySQL、Oracle、SQL Server)有各自的底层通信协议和 API,Java 程序要访问不同数据库,需针对每个数据库编写完全不同的代码(例如连接 MySQL 用一套逻辑,连接 Oracle 用另一套),导致代码冗余且难以维护。
- 开发效率低:开发者需深入了解每个数据库的底层细节(如网络协议、数据格式),无法专注于业务逻辑,且更换数据库时需重写所有数据库访问代码。
- 安全性和稳定性差:直接操作数据库缺乏统一的资源管理(如连接池、事务控制)机制,容易出现连接泄露、事务不一致等问题,且难以统一处理异常(如断连、权限不足)。
-
JDBC带来的好处
- 跨数据库兼容性:JDBC 定义了一套统一的接口(如
Connection
、Statement
、ResultSet
),所有数据库厂商只需提供实现这些接口的驱动(Driver),Java 程序即可通过相同的代码访问不同数据库(例如切换 MySQL 到 Oracle 时,只需更换驱动包,业务代码无需修改)。 - 简化开发:开发者无需关注数据库底层协议,只需调用 JDBC 接口即可完成连接、查询、更新等操作,降低了学习成本和开发复杂度。
- 统一资源管理:JDBC 内置了事务控制(
commit
/rollback
)、连接池支持等机制,可高效管理数据库连接(避免频繁创建 / 销毁连接的性能损耗),并保证事务一致性。 - 扩展性强:JDBC 是很多高级框架(如 MyBatis、Spring JDBC)的基础,这些框架基于 JDBC 进一步封装,提供更便捷的功能(如 ORM 映射、SQL 参数绑定),而 JDBC 的标准化确保了这些框架的通用性。
- 安全性提升:通过 JDBC 可统一处理权限校验、SQL 注入防护(如
PreparedStatement
预编译)等问题,减少直接操作数据库的安全风险。
- 跨数据库兼容性:JDBC 定义了一套统一的接口(如
-
说明
JDBC是Java提供一套用于数据库作的接口API,Java程序员只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同实现。
-
-
JDBC API
JDBC API是一系列的接口,它统一和规范了应用程序与数据库的连接、执行SQL
语句,并到得到返回结果等各类操作,相关类和接口在java.sql与javax.sql包中,在JDK8文档中可以阅读它们的详细说明。
-JDBC快速入门
-
JDBC程序编写步骤
- 注册驱动-加载对应的数据库操作实现类
- 获取连接-获取数据库连接
- 执行增删改查-发送指令给数据库执行
- 释放资源-关闭相关的连接
-
JDBC快速入门程序
#数据库初始化 CREATE DATABASE mydb; USE mydb;CREATE TABLE actor(id INT PRIMARY KEY AUTO_INCREMENT,`name` VARCHAR(64) NOT NULL DEFAULT '',gender CHAR(1) NOT NULL DEFAULT '男',birthday DATE NOT NULL,phone_num VARCHAR(12) );#搞卫生 DROP DATABASE mydb;
package com.xijie.myFirstJDBCApp;import com.mysql.jdbc.Connection; import com.mysql.jdbc.Driver;import java.sql.SQLException; import java.sql.Statement; import java.util.Properties;public class MyFirstJDBCApp {public static void main(String[] args) throws SQLException {//1.首先将mysql对应版本的驱动jar包引入项目mysql-connector-java-5.1.37-bin.jar//然后在数据库中执行mydb的数据库初始化代码//2.注册mysql的jdbc驱动Driver driver =new Driver();//3.得到连接//jdbc:mysql:// 表示协议//localhost:表示数据库ip地址//3306表示数据库端口//mydb表示数据库名String url="jdbc:mysql://localhost:3306/mydb";//准备数据库用户名和密码Properties properties=new Properties();properties.setProperty("user","root");properties.setProperty("password","123456");//使用刚刚准备好的地址和用户信息连接数据库Connection connection= (Connection) driver.connect(url,properties);//4.执行SQLString sql="insert into actor values(null,'吴亦凡','男','1999-01-01','18813212311')";//Statement用于执行静态SQL并返回生成的结果对象Statement statement=connection.createStatement();int rows=statement.executeUpdate(sql);System.out.println(rows>0?"成功":"失败");//5.关闭连接statement.close();connection.close();} }
-获取数据库连接的5种方式
-
方式1
/*** 方式一:静态加载驱动类*/ @Test public void connect01() throws SQLException {//静态加载驱动类Driver driver =new Driver();//准备数据库用户名和密码String url="jdbc:mysql://localhost:3306/mydb";Properties info=new Properties();info.setProperty("user","root");info.setProperty("password","123456");//使用刚刚准备好的地址和用户信息连接数据库Connection connection= (Connection) driver.connect(url,info);System.out.println(connection);//关闭连接connection.close(); }
-
方式2
/*** 方式二:动态加载驱动类,减少依赖性*/ @Test public void connect02() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {//使用反射加载mysql的Driver类Class mysqlDriverClass=Class.forName("com.mysql.jdbc.Driver");//使用java.sql包下的Driver,可以获取各个数据库的Driver对象java.sql.Driver driver = (java.sql.Driver) mysqlDriverClass.newInstance();//准备数据库连接信息String url="jdbc:mysql://localhost:3306/mydb";Properties info=new Properties();info.setProperty("user","root");info.setProperty("password","123456");//使用刚刚准备好的地址和用户信息连接数据库Connection connection= (Connection) driver.connect(url,info);System.out.println(connection);//关闭连接connection.close(); }
-
方式3
/*** 方式三:使用DriverManager统一管理Driver*/ @Test public void connect03() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {//使用反射加载mysql的Driver类Class mysqlDriverClass=Class.forName("com.mysql.jdbc.Driver");//使用java.sql包下的Driver,可以获取各个数据库的Driver对象java.sql.Driver driver = (java.sql.Driver) mysqlDriverClass.newInstance();//使用DriverManager统一管理DriverDriverManager.registerDriver(driver);//准备数据库连接信息String url="jdbc:mysql://localhost:3306/mydb";String user="root";String password="123456";//使用刚刚准备好的地址和用户信息连接数据库Connection connection=DriverManager.getConnection(url,user,password);System.out.println(connection);//关闭连接connection.close(); }
-
方式4
/*** 方式四:加载驱动类时自动注册驱动*/ @Test public void connect04() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {//使用反射加载mysql的Driver类,在Driver类的静态代码块中自动注册了DriverClass.forName("com.mysql.jdbc.Driver");//准备数据库连接信息String url="jdbc:mysql://localhost:3306/mydb";String user="root";String password="123456";//使用刚刚准备好的地址和用户信息连接数据库Connection connection=DriverManager.getConnection(url,user,password);System.out.println(connection);//关闭连接connection.close(); }
/*** 方式四点五:让DriverManager自动注册驱动*/ @Test public void connect04_5() throws SQLException{//准备数据库连接信息String url="jdbc:mysql://localhost:3306/mydb";String user="root";String password="123456";//使用刚刚准备好的地址和用户信息连接数据库Connection connection=DriverManager.getConnection(url,user,password);System.out.println(connection);//关闭连接connection.close(); }
-
方式5
url=jdbc:mysql://localhost:3306/mydb driver=com.mysql.jdbc.Driver user=root password=123456
/*** 方式五:从配置文件读取连接信息*/ @Test public void connect05() throws SQLException, IOException, ClassNotFoundException {//准备数据库连接信息Properties properties=new Properties();properties.load(new FileInputStream("src\\mysql.properties"));String driver=properties.getProperty("driver");String url=properties.getProperty("url");String user=properties.getProperty("user");String password=properties.getProperty("password");//注册驱动,不写也可以Class.forName(driver);//获取连接Connection connection=DriverManager.getConnection(url,user,password);System.out.println(connection);//关闭连接connection.close(); }
-
连接数据库练习
package com.xijie.conn;import java.io.FileInputStream; import java.io.IOException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; import java.util.Properties;/*** 用第五种方式连接数据库* 1. 创建 news 表* 2.使用jdbc添加5条数据* 3.修改id=1的记录,将content改成一个新的消息* 4.删除id=3的记录*/ public class JdbcConnectionPractice {public static void main(String[] args) throws ClassNotFoundException, SQLException, IOException {//准备数据库连接信息Properties properties=new Properties();properties.load(new FileInputStream("src\\mysql.properties"));String driver=properties.getProperty("driver");String url=properties.getProperty("url");String user=properties.getProperty("user");String password=properties.getProperty("password");//注册驱动,不写也可以Class.forName(driver);//获取连接Connection connection= DriverManager.getConnection(url,user,password);Statement statement=connection.createStatement();String sql="";//* 1. 创建 news 表sql="CREATE TABLE IF NOT EXISTS news (id INT,content TEXT)";statement.executeUpdate(sql);//* 2.使用jdbc添加5条数据sql="insert into news values(1,'news1'),(2,'news2'),(3,'news3'),(4,'news4'),(5,'news5')";statement.executeUpdate(sql);//* 3.修改id=1的记录,将content改成一个新的消息sql="update news set content='newnews1' where id=1";statement.executeUpdate(sql);//* 4.删除id=3的记录sql="delete from news where id=3";statement.executeUpdate(sql);System.out.println("完毕");//关闭连接connection.close();} }
-ResultSet结果集
-
基本介绍
- 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
- ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
- next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集
-
练习代码
#数据库初始化 CREATE DATABASE IF NOT EXISTS mydb; USE mydb;CREATE TABLE IF NOT EXISTS actor(id INT PRIMARY KEY AUTO_INCREMENT,`name` VARCHAR(64) NOT NULL DEFAULT '',gender CHAR(1) NOT NULL DEFAULT '男',birthday DATE NOT NULL,phone_num VARCHAR(12) );INSERT INTO actor VALUES (NULL,'jack','男','1980-01-01','156156'), (NULL,'jack','男','1980-01-01','156156'), (NULL,'jack','男','1980-01-01','156156');SELECT * FROM actor;#搞卫生 DROP DATABASE mydb;
package com.xijie.resultset;import java.io.FileInputStream; import java.sql.*; import java.util.Properties;/*** 练习ResultSet的使用* 运行前先执行mydb.sql初始化数据库的部分*/ public class ResultSetPractice {public static void main(String[] args) throws Exception {//准备数据库连接信息Properties properties=new Properties();properties.load(new FileInputStream("src\\mysql.properties"));String driver=properties.getProperty("driver");String url=properties.getProperty("url");String user=properties.getProperty("user");String password=properties.getProperty("password");//注册驱动,不写也可以Class.forName(driver);//获取连接Connection connection= DriverManager.getConnection(url,user,password);//查询数据Statement statement=connection.createStatement();String sql="select * from actor";//执行sql语句,获取结果集ResultSet resultSet=statement.executeQuery(sql);//读取结果集的内容while(resultSet.next()){int id=resultSet.getInt(1);String name=resultSet.getString(2);String gender=resultSet.getString(3);Date date=resultSet.getDate(4);String phoneNum=resultSet.getString(5);System.out.println(id+" "+name+" "+gender+" "+date+" "+phoneNum);}//关闭连接connection.close();} }
-Statement
-
基本介绍
- Statement对象用于执行静态SQL语句并返回其生成的结果的对象
- 在连接建立后,需要对数据库进行访问,执行命名或是SQL语句,可以通过
- Statement【存在SQL注入问题】
- PreparedStatement【会预处理SQL语句】
- CallableStatement【用于存储过程】
- Statement对象执行SQL语句,存在SQL注入风险
- SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库。sql_injection.sql
- 要防范 SQL注入,只要用PreparedStatement(从Statement扩展而来)取代 Statement 就可以了
-
用SQL语句演示SQL注入查询
# 演示SQL注入,用户名填入1' or,密码输入 or '1'='1' or (',这样客户端就查询到了所有用户的信息 SELECT * FROM mysql.user WHERE USER='root' AND authentication_string=PASSWORD('123456'); SELECT * FROM mysql.user WHERE USER='1' OR' AND authentication_string=PASSWORD(' OR '1'='1' OR ('');
-
在Java里演示SQL注入查询
package com.xijie.statement;import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Scanner;/*** 演示SQL注入* 用户名填入1' or,密码输入 or '1'='1' or (',这样客户端就查询到了所有用户的信息*/ public class StatementSQLInject {public static void main(String[] args) throws Exception{//输入用户名和密码Scanner scanner=new Scanner(System.in);System.out.print("请输入用户名:");String userName=scanner.nextLine();System.out.print("请输入密码:");String userPassword=scanner.nextLine();//使用反射加载mysql的Driver类,在Driver类的静态代码块中自动注册了DriverClass.forName("com.mysql.jdbc.Driver");//准备数据库连接信息String url="jdbc:mysql://localhost:3306/mysql";String user="root";String password="123456";//使用刚刚准备好的地址和用户信息连接数据库Connection connection= DriverManager.getConnection(url,user,password);//演示SQL注入Statement statement=connection.createStatement();String sql="SELECT * FROM mysql.user WHERE USER='"+userName+"' AND authentication_string=PASSWORD('"+userPassword+"');";ResultSet resultSet=statement.executeQuery(sql);if(resultSet.next()){System.out.println("登陆成功");}else{System.out.println("登录失败");}//关闭连接statement.close();connection.close();} }
-PreparedStatement
-
基本介绍
- PreparedStatement执行的SQL语句中的参数用问号(?)来表示,调用PreparedStatement对象的setXxx)方法来设置这些参数、setXxx(方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引I(从1开始),第二个是设置的SQL语句中的参数的值
- 调用executeQuery0,返回ResultSet 对象
- 调用executeUpdate0:执行更新,包括增、删、修改
-
预处理的好处
- 不再使用+拼接sql语句,减少语法错误
- 有效的解决了sql注入问题!
- 大大减少了编译次数,效率较高
-
防止SQL注入应用案例
package com.xijie.preparedStatement;import java.sql.*; import java.util.Scanner; /*** 演示PreparedStatement防止SQL注入* 用户名填入1' or,密码输入 or '1'='1' or (',这样客户端也查询不到所有用户的信息*/ public class PreparedStatementPractice {public static void main(String[] args) throws Exception{//输入用户名和密码Scanner scanner=new Scanner(System.in);System.out.print("请输入用户名:");String userName=scanner.nextLine();System.out.print("请输入密码:");String userPassword=scanner.nextLine();//使用反射加载mysql的Driver类,在Driver类的静态代码块中自动注册了DriverClass.forName("com.mysql.jdbc.Driver");//准备数据库连接信息String url="jdbc:mysql://localhost:3306/mysql";String user="root";String password="123456";//使用刚刚准备好的地址和用户信息连接数据库Connection connection= DriverManager.getConnection(url,user,password);//演示PreparedStatement防止SQL注入String sql="SELECT * FROM mysql.user WHERE USER= ? AND authentication_string = PASSWORD( ? )";PreparedStatement preparedStatement=connection.prepareStatement(sql);preparedStatement.setString(1,userName);preparedStatement.setString(2,userPassword);ResultSet resultSet=preparedStatement.executeQuery();if(resultSet.next()){System.out.println("登陆成功");}else{System.out.println("登录失败");}//关闭连接preparedStatement.close();connection.close();} }
-
预处理增删改案例
package com.xijie.preparedStatement;import java.sql.*; import java.util.Scanner;/*** 演示PreparedStatement增删改* 需要提前准备好数据库和数据表*/ public class PreparedStatementUpdatePractice {public static void main(String[] args) throws Exception{//使用反射加载mysql的Driver类,在Driver类的静态代码块中自动注册了DriverClass.forName("com.mysql.jdbc.Driver");//准备数据库连接信息String url="jdbc:mysql://localhost:3306/mydb";String user="root";String password="123456";//使用刚刚准备好的地址和用户信息连接数据库Connection connection= DriverManager.getConnection(url,user,password);//演示PreparedStatement增加数据String sql="INSERT INTO actor VALUES (?,?,?,?,?)";PreparedStatement preparedStatement=connection.prepareStatement(sql);//插入值preparedStatement.setNull(1, Types.INTEGER);preparedStatement.setString(2,"lili");preparedStatement.setString(3,"女");preparedStatement.setString(4,"1989-05-05");preparedStatement.setString(5,"489489");//运行int n=preparedStatement.executeUpdate();//检查结果if(n>0){System.out.println("添加成功");}else{System.out.println("添加失败");}//关闭指令preparedStatement.close();//演示PreparedStatement删除数据sql="Delete from actor where id=?";preparedStatement=connection.prepareStatement(sql);//插入值preparedStatement.setInt(1, 4);//运行n=preparedStatement.executeUpdate();//检查结果if(n>0){System.out.println("删除成功");}else{System.out.println("删除失败");}//关闭指令preparedStatement.close();//演示PreparedStatement修改数据sql="update actor set name=? where id=?";preparedStatement=connection.prepareStatement(sql);//插入值preparedStatement.setString(1, "老八");preparedStatement.setInt(2,3);//运行n=preparedStatement.executeUpdate();//检查结果if(n>0){System.out.println("修改成功");}else{System.out.println("修改失败");}//关闭指令preparedStatement.close();//关闭连接connection.close();} }
-JDBC API小结
- DriverManager 驱动管理类
getConnection(url, user, pwd)
:获取数据库连接
- Connection 接口
createStatement()
:创建 Statement 对象prepareStatement(sql)
:生成预处理对象
- Statement 接口
executeUpdate(sql)
:执行 DML 语句(INSERT/UPDATE/DELETE),返回影响的行数executeQuery(sql)
:执行查询语句,返回 ResultSet 对象execute(sql)
:执行任意 SQL 语句,返回布尔值(true 表示结果为 ResultSet,false 表示为更新计数)
- PreparedStatement 接口
executeUpdate()
:执行 DML 语句(预处理版本)executeQuery()
:执行查询语句,返回 ResultSetexecute()
:执行任意 SQL 语句,返回布尔值setXxx(占位符索引, 占位符的值)
:设置 SQL 参数(如setInt(1, 100)
),防止 SQL 注入setObject(占位符索引, 占位符的值)
:设置通用参数类型
- ResultSet 结果集
next()
:指针向下移动一行,若无下一行则返回 falseprevious()
:指针向上移动一行(需结果集支持滚动)getXxx(列索引|列名)
:获取对应列的值(如getInt(1)
或getString("name")
)getObject(列索引|列名)
:获取通用类型的列值
-封装JDBCUtils
-
说明
在jdbc操作中,获取连接和释放资源是经常使用到可以将其封装JDBC连接的工具类JDBCUtils
-
思路
使用JDBC查询数据库的流程为:
- 连接数据库,获取连接对象
- 写SQL语句
- 创建预处理对象
- 填写预处理对象中SQL的?对应的参数
- 执行SQL
- 返回结果
- 关闭结果集(如果有的话)、预处理对象、连接对象
我们希望除了2、4的操作由工程师写代码完成外,其他操作都由JDBCUtils完成
-
JDBCUtils案例
package com.xijie.jdbcutils;import java.sql.*;public class JDBCUtils {private static String URL;private static String USER;private static String PASSWORD;private static String DRIVER;static {try {// 加载配置文件java.util.Properties props = new java.util.Properties();java.io.InputStream is = JDBCUtils.class.getClassLoader().getResourceAsStream("mysql.properties");props.load(is);// 获取配置信息URL = props.getProperty("url");USER = props.getProperty("user");PASSWORD = props.getProperty("password");DRIVER = props.getProperty("driver");// 注册驱动Class.forName(DRIVER);} catch (Exception e) {throw new RuntimeException(e);}}/*** 获取数据库连接*/public static Connection getConnection() throws SQLException {return DriverManager.getConnection(URL, USER, PASSWORD);}/*** 创建预处理对象*/public static PreparedStatement prepareStatement(Connection conn, String sql) throws SQLException {return conn.prepareStatement(sql);}/*** 执行查询,返回结果集*/public static ResultSet executeQuery(PreparedStatement pstmt) throws SQLException {return pstmt.executeQuery();}/*** 执行更新(INSERT、UPDATE、DELETE)*/public static int executeUpdate(PreparedStatement pstmt) throws SQLException {return pstmt.executeUpdate();}/*** 关闭资源*/public static void close(ResultSet rs, Statement stmt, Connection conn) {try {if (rs != null) rs.close();} catch (SQLException e) {e.printStackTrace();}try {if (stmt != null) stmt.close();} catch (SQLException e) {e.printStackTrace();}try {if (conn != null) conn.close();} catch (SQLException e) {e.printStackTrace();}} }
package com.xijie.jdbcutils;import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet;public class JDBCUtilsTest {public static void main(String[] args) throws Exception {//获取连接Connection connection=JDBCUtils.getConnection();//测试查询SQL语句String sql="select * from actor where id=?";//获取预处理对象PreparedStatement preparedStatement=JDBCUtils.prepareStatement(connection,sql);//填写参数preparedStatement.setInt(1,1);//获取查询结果ResultSet resultSet=JDBCUtils.executeQuery(preparedStatement);//输出查询结果while(resultSet.next()){System.out.println(resultSet.getInt("id")+"\t"+resultSet.getString("name")+"\t"+resultSet.getString("gender")+"\t"+resultSet.getString("birthday")+"\t"+resultSet.getString("phone_num"));}//关闭连接JDBCUtils.close(resultSet,preparedStatement,connection);} }
-JDBC事务
-
基本介绍
- 在 JDBC 程序中,当一个
Connection
对象创建时,默认处于自动提交事务模式:每次执行 SQL 语句,若执行成功则会自动向数据库提交,且无法回滚。 - 若需将多个 SQL 语句作为一个整体执行(要么全部成功,要么全部失败),需使用事务机制。
- 调用
Connection
的setAutoCommit(false)
方法,可取消自动提交事务,开启手动事务模式。 - 当所有 SQL 语句都成功执行后,调用
commit()
方法提交事务,使所有操作生效。 - 若某个操作失败或出现异常,调用
rollback()
方法回滚事务,撤销所有已执行的操作。
- 在 JDBC 程序中,当一个
-
应用实例
package com.xijie.transaction;import com.xijie.jdbcutils.JDBCUtils; import org.junit.jupiter.api.Test;import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException;public class TransactionTest {/*** 无事务,导致一条语句执行成功,另一条语句执行失败* @throws Exception*/@Testpublic void noTransaction() throws Exception{try {//获取连接Connection connection= JDBCUtils.getConnection();//更新账户金钱数语句String sql1="update account set balance=balance-100 where id=1";String sql2="update account set balance=balance+100 where id=2";//获取预处理对象,执行第一条语句PreparedStatement preparedStatement=JDBCUtils.prepareStatement(connection,"");//执行SQLpreparedStatement.executeUpdate(sql1);//制造异常int i=1/0;preparedStatement.executeUpdate(sql2);//关闭连接JDBCUtils.close(null,preparedStatement,connection);} catch (SQLException e) {throw new RuntimeException(e);}}/*** 有事务,要么全都执行成功,要么全部失败* @throws Exception*/@Testpublic void withTransaction() throws Exception{//获取连接和预处理对象Connection connection= JDBCUtils.getConnection();PreparedStatement preparedStatement=JDBCUtils.prepareStatement(connection,"");//更新账户金钱数语句String sql1="update account set balance=balance-100 where id=1";String sql2="update account set balance=balance+100 where id=2";try {//开启事务connection.setAutoCommit(false);//执行SQLpreparedStatement.executeUpdate(sql1);//制造异常int i=1/0;preparedStatement.executeUpdate(sql2);//提交事务connection.commit();} catch (SQLException e) {//回滚connection.rollback();//关闭连接JDBCUtils.close(null,preparedStatement,connection);throw new RuntimeException(e);}} }
-JDBC批处理
-
基本介绍
-
批量操作原理
当需要批量插入或更新大量记录时,建议使用 JDBC 的批量更新机制。该机制允许将多条 SQL 语句一次性提交给数据库处理,相较于逐条执行,可显著提升处理效率。 -
核心 API 方法
JDBC 的批量处理通过Statement
或PreparedStatement
接口实现,核心方法包括:addBatch(String sql)
:添加需要批量执行的 SQL 语句(针对Statement
)。addBatch()
:添加已预编译的参数(针对PreparedStatement
)。executeBatch()
:执行批量处理,并返回每条语句的更新行数数组。clearBatch()
:清空批处理队列中的所有语句。
-
MySQL 连接配置
使用 MySQL 数据库时,需在 JDBC URL 中添加参数rewriteBatchedStatements=true
以启用批量处理优化。示例:jdbc:mysql://localhost:3306/yourdb?rewriteBatchedStatements=true
-
最佳实践
推荐将PreparedStatement
与批量处理结合使用,既能避免 SQL 注入风险,又可减少 SQL 编译次数,进一步提升性能。示例代码:try (Connection conn = DriverManager.getConnection(url, username, password);PreparedStatement pstmt = conn.prepareStatement("INSERT INTO users (name, age) VALUES (?, ?)")) {// 禁用自动提交以提高性能conn.setAutoCommit(false);// 添加批量参数for (User user : userList) {pstmt.setString(1, user.getName());pstmt.setInt(2, user.getAge());pstmt.addBatch();}// 执行批量处理int[] results = pstmt.executeBatch();conn.commit(); // 手动提交事务 } catch (SQLException e) {// 异常处理 }
-
-
测试案例
# 数据库初始化 CREATE DATABASE IF NOT EXISTS mydb; USE mydb;-- 演员表(修改 gender 类型为 VARCHAR(2)) CREATE TABLE IF NOT EXISTS actor(id INT PRIMARY KEY AUTO_INCREMENT,`name` VARCHAR(64) NOT NULL DEFAULT '',gender VARCHAR(2) NOT NULL DEFAULT '男', -- 修改为 VARCHAR(2)birthday DATE NOT NULL,phone_num VARCHAR(12) );-- 使用显式列名(推荐写法) INSERT INTO actor (`name`, gender, birthday, phone_num) VALUES ('jack', '男', '1980-01-01', '156156'), ('jack', '男', '1980-01-01', '156156'), ('jack', '男', '1980-01-01', '156156');-- 账户表(统一使用小写和反引号) 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 (`name`, balance) VALUES ('马云', 3000); INSERT INTO ACCOUNT (`name`, balance) VALUES ('马化腾', 10000);# 清理数据库 DROP DATABASE mydb;
package com.xijie.batch;import com.xijie.jdbcutils.JDBCUtils; import org.junit.jupiter.api.Test;import java.sql.Connection; import java.sql.PreparedStatement;public class BatchTest {/*** 不使用批处理添加5000条数据* 花费时间2791ms* @throws Exception*/@Testpublic void withoutBatch() throws Exception{Connection conn= JDBCUtils.getConnection();String sql="INSERT INTO ACCOUNT (`name`, balance) VALUES (?, ?)";PreparedStatement ppst=JDBCUtils.prepareStatement(conn,sql);//进行5000次插入数据操作并计时long start=System.currentTimeMillis();for(int i=0;i<5000;i++){ppst.setString(1,"jack"+i);ppst.setInt(2,i+1000);ppst.executeUpdate();}long end=System.currentTimeMillis();System.out.println("不使用批处理执行时间:"+(end-start));JDBCUtils.close(null,ppst,conn);}/*** 使用批处理添加5000条数据* 花费时间58ms* @throws Exception*/@Testpublic void withBatch() throws Exception{Connection conn= JDBCUtils.getConnection();String sql="INSERT INTO ACCOUNT (`name`, balance) VALUES (?, ?)";PreparedStatement ppst=JDBCUtils.prepareStatement(conn,sql);//进行5000次插入数据操作并计时long start=System.currentTimeMillis();for(int i=0;i<5000;i++){ppst.setString(1,"jack"+i);ppst.setInt(2,i+1000);//添加到批处理ppst.addBatch();//每1000条sql为一批if((i+1)%1000==0){ppst.executeBatch();ppst.clearBatch();}}long end=System.currentTimeMillis();System.out.println("使用批处理执行时间:"+(end-start));JDBCUtils.close(null,ppst,conn);} }
-数据库连接池
-
传统获取 Connection 问题分析
-
资源消耗高
传统 JDBC 通过DriverManager.getConnection()
创建连接时,需经历加载驱动、TCP 握手、身份验证等步骤,单次耗时约 0.05~1 秒。高并发场景下频繁创建连接会耗尽系统资源(如线程池、文件描述符),导致服务器响应缓慢甚至崩溃。 -
内存泄漏风险
每次数据库操作后需手动调用connection.close()
释放连接。若因异常未关闭(如try-catch
缺失),连接对象无法被 GC 回收,持续占用数据库会话资源,最终导致数据库内存溢出,需重启服务恢复。
3. 连接数失控
传统方式无法限制最大连接数,当并发请求超过数据库承受能力(如 MySQL 默认max_connections=151
)时,会触发以下问题:- 新连接被数据库拒绝(错误码
Too many connections
) - 数据库因资源耗尽(如线程堆栈、内存)崩溃
- 新连接被数据库拒绝(错误码
-
解决方案:数据库连接池
采用连接池(如 HikariCP、Druid)可复用预先创建的连接,避免频繁创建 / 销毁开销,并通过以下机制优化资源管理:- 连接复用:无需重复进行 TCP 握手和身份验证
- 最大连接限制:防止过度消耗数据库资源
- 自动回收:超时未使用的连接自动关闭
- 异常处理:连接池负责连接生命周期管理,降低泄漏风险
-
-
数据库连接池基本介绍
- 连接复用机制
连接池预先在内存中创建一定数量的数据库连接(即「缓冲池」)。应用程序需要访问数据库时,直接从池中获取连接,使用完毕后归还到池中,而非销毁。这避免了传统 JDBC 每次创建新连接的开销。 - 核心管理功能
连接池负责对数据库连接进行 分配、管理和释放,主要职责包括:- 连接生命周期管理(创建、验证、回收)
- 配置参数控制(最大连接数、最小空闲连接数、超时时间)
- 连接状态监控(活跃连接数、等待队列长度)
- 过载处理策略
当应用请求的连接数超过连接池的 最大连接数 时,新请求将:- 进入 等待队列 排队,直到有连接被释放
- 若等待超时(如设置
connectionTimeout=3000ms
),则抛出异常
- 连接复用机制
-
数据库连接池种类
-
核心接口定义
JDBC 规范通过javax.sql.DataSource
接口定义数据库连接池,该接口由第三方组件实现,常见实现包括: -
主流连接池对比
连接池 特点 适用场景 C3P0 老牌连接池,稳定性高,但性能一般。支持 JDBC3 规范,适合对稳定性要求高的场景。 Hibernate、Spring 框架早期版本 DBCP Apache 开发,性能优于 C3P0,但在高并发下稳定性较弱。 中小规模应用 Proxool 支持连接池监控和统计,但稳定性低于 C3P0。 需要连接监控的场景 BoneCP 性能极高(早期版本),但维护活跃度低,已逐渐被 HikariCP 取代。 历史项目升级 HikariCP 目前性能最优的连接池,字节码优化和并发控制出色,Spring Boot 默认选择。 高性能、高并发场景 Druid 阿里巴巴开源,集监控、防 SQL 注入、性能优化于一体,适合对安全性和监控有要求的项目。 互联网企业生产环境 -
选型建议
- 优先选择 HikariCP(性能优先)或 Druid(功能全面)
- 避免在新系统中使用 DBCP 和 C3P0(维护停滞)
- 生产环境需搭配监控系统(如 Druid 监控面板、Prometheus)
-
-
C3P0应用实例
<c3p0-config><named-config name="myC3P0"> <!-- 驱动类 --><property name="driverClass">com.mysql.jdbc.Driver</property><!-- url--><property name="jdbcUrl">jdbc:mysql://127.0.0.1:3306/mydb</property><!-- 用户名 --><property name="user">root</property><!-- 密码 --><property name="password">123456</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>
package com.xijie.c3p0;import com.mchange.v2.c3p0.ComboPooledDataSource; import org.junit.jupiter.api.Test;import java.sql.Connection;/*** C3P0快速入门使用* 使用C3P0需要引入Jar包,例如c3p0-0.9.1.2.jar*/ public class C3P0Test {/*** 第一种方式:通过代码配置数据源信息*/@Testpublic void c3p0Test01() throws Exception{//创建一个数据源对象ComboPooledDataSource comboPooledDataSource=new ComboPooledDataSource();//配置c3p0的数据源信息comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");comboPooledDataSource.setUser("root");comboPooledDataSource.setPassword("123456");//配置连接相关参数comboPooledDataSource.setInitialPoolSize(10);comboPooledDataSource.setMaxPoolSize(50);//可以获取连接了Connection connection=comboPooledDataSource.getConnection();System.out.println("连接成功");connection.close();}/*** 第二种方式:通过配置文件配置数据源信息* 将c3p0-config.xml文件放到src目录下,再进行编辑配置*/@Testpublic void c3p0Test02() throws Exception{//创建一个数据源对象ComboPooledDataSource comboPooledDataSource=new ComboPooledDataSource("myC3P0");//可以获取连接了//先进行5000次连接,排除初始化的影响for(int i=0;i<5000;i++){Connection connection=comboPooledDataSource.getConnection();connection.close();}//测试连接池效率,5000次连接:19mslong start=System.currentTimeMillis();for(int i=0;i<5000;i++){Connection connection=comboPooledDataSource.getConnection();connection.close();}long end=System.currentTimeMillis();System.out.println("C3P0连接5000次花费时间:"+(end-start));//测试连接池效率,500000次连接:914msstart=System.currentTimeMillis();for(int i=0;i<500000;i++){Connection connection=comboPooledDataSource.getConnection();connection.close();}end=System.currentTimeMillis();System.out.println("C3P0连接500000次花费时间:"+(end-start));} }
-
Druid应用实例
#key=value driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mydb?rewriteBatchedStatements=true #url=jdbc:mysql://localhost:3306/mydb username=root password=123456 #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
package com.xijie.druid;import com.alibaba.druid.pool.DruidDataSourceFactory;import javax.sql.DataSource; import java.io.FileInputStream; import java.sql.Connection; import java.util.Properties;public class DruidTest {public static void main(String[] args) throws Exception{//1.加入Druid的jar包//2.加入配置文件//3.读取配置文件Properties properties=new Properties();properties.load(new FileInputStream("src\\druid.properties"));//4.创建指定参数的数据库连接池DataSource dataSource= DruidDataSourceFactory.createDataSource(properties);//5.可以获取连接了,进行效率测试//先进行5000次连接,排除初始化的影响for(int i=0;i<5000;i++){Connection connection=dataSource.getConnection();connection.close();}//测试连接池效率,5000次连接:3mslong start=System.currentTimeMillis();for(int i=0;i<5000;i++){Connection connection=dataSource.getConnection();connection.close();}long end=System.currentTimeMillis();System.out.println("Druid连接5000次花费时间:"+(end-start));//测试连接池效率,500000次连接:65msstart=System.currentTimeMillis();for(int i=0;i<500000;i++){Connection connection=dataSource.getConnection();connection.close();}end=System.currentTimeMillis();System.out.println("Druid连接500000次花费时间:"+(end-start));} }
-
Druid工具类
package com.xijie.druid;import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import com.alibaba.druid.pool.DruidPooledConnection; import org.junit.jupiter.api.Test;import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties;/*** Druid 连接池工具类*/ public class DruidUtil {// 数据源实例(静态单例)private static DruidDataSource dataSource;static {try {// 加载配置文件Properties properties = loadProperties();// 创建数据源dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);// 可选:添加监控过滤器// dataSource.setFilters("stat,wall,log4j");} catch (Exception e) {throw new RuntimeException("初始化 Druid 数据源失败", e);}}/*** 加载配置文件*/private static Properties loadProperties() throws IOException {Properties properties = new Properties();// 使用类加载器加载配置文件,避免硬编码路径InputStream inputStream = DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");if (inputStream == null) {throw new IOException("未找到 druid.properties 配置文件");}properties.load(inputStream);return properties;}/*** 获取数据库连接*/public static Connection getConnection() throws SQLException {return dataSource.getConnection();}/*** 获取原生的 Druid 连接,可访问 Druid 扩展功能*/public static DruidPooledConnection getDruidConnection() throws SQLException {return (DruidPooledConnection) dataSource.getConnection();}/*** 释放资源(通用方法)*/public static void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {try {if (rs != null) rs.close();if (pstmt != null) pstmt.close();if (conn != null) conn.close(); // 归还连接到池} catch (SQLException e) {e.printStackTrace();}}/*** 获取数据源状态信息*/public static String getDataSourceStatus() {return "当前连接数: " + dataSource.getActiveCount() +" | 最大连接数: " + dataSource.getMaxActive() +" | 最小空闲连接数: " + dataSource.getMinIdle() +" | 总连接数: " + dataSource.getActiveCount() + dataSource.getPoolingCount();}/*** 关闭数据源(应用程序退出时调用)*/public static void shutdownDataSource() {if (dataSource != null && !dataSource.isClosed()) {dataSource.close();System.out.println("Druid 数据源已关闭");}}@Testpublic void test() throws Exception{Connection connection=DruidUtil.getConnection();System.out.println("获取连接成功");DruidUtil.close(connection,null,null);} }
-Apache-DBUtils
-
基本介绍
- Apache Commons DbUtils 是 Apache 软件基金会提供的开源 JDBC 工具库,通过封装 JDBC 核心 API(如
Statement
、ResultSet
),显著减少样板代码,提升开发效率。
- Apache Commons DbUtils 是 Apache 软件基金会提供的开源 JDBC 工具库,通过封装 JDBC 核心 API(如
-
核心组件
-
QueryRunner 类
-
封装 SQL 执行逻辑,线程安全,支持增删改查及批处理操作。
-
核心方法
query(String sql, ResultSetHandler<T> rsh, Object... params) // 查询 update(String sql, Object... params) // 增删改 batch(String sql, Object[][] params) // 批量操作
-
ResultSetHandler 接口
-
将
ResultSet
转换为目标数据结构,内置实现包括:实现类 功能描述 BeanHandler<T>
映射为单个 JavaBean 对象 BeanListHandler<T>
映射为 JavaBean 列表 ScalarHandler
处理单行单列数据(如聚合函数结果) MapHandler
映射为单个 Map<String, Object>
MapListHandler
映射为 List<Map<String, Object>>
-
ArrayHandler:把结果集中的第一行数据转成对象数组。
-
ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
-
BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
-
BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
-
ColumnListHandler:将结果集中某一列的数据存放到List中。
-
KeyedHandler(name):将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key.
-
MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
-
MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
-
-
-
实践案例
package com.xijie.dbutils;import com.xijie.druid.DruidUtil; import com.xijie.jdbcutils.JDBCUtils; 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.util.List;public class DbutilsTest {public static void main(String[] args) throws Exception{//得到Druid数据库连接Connection connection= DruidUtil.getConnection();//引入commons-dbutils-1.3.jar//创建QueryRunner执行类QueryRunner queryRunner=new QueryRunner();//查询全表List<Actor> actorArrayList=queryRunner.query(connection,"select * from actor", new BeanListHandler<>(Actor.class));//输出结果System.out.println("查询全表结果");for (Actor actor : actorArrayList) {System.out.println(actor);}//查询id为2的单行Actor actor=queryRunner.query(connection,"select * from actor where id=?", new BeanHandler<>(Actor.class),2);//输出结果System.out.println("查询单行结果");System.out.println(actor);//查询id为2的nameObject object=queryRunner.query(connection,"select name from actor where id=?", new ScalarHandler(),2);//输出结果System.out.println("查询单值结果");System.out.println(object);//添加一行int affectedRows=queryRunner.update(connection,"insert into actor values(null,?,?,?,?)","小罗","男","1989-08-15","13245564789");//输出结果System.out.println("插入数据影响行数");System.out.println(affectedRows);//删除一行affectedRows=queryRunner.update(connection,"delete from actor where id =?",2);//输出结果System.out.println("删除数据影响行数");System.out.println(affectedRows);//更新一行affectedRows=queryRunner.update(connection,"update actor set name=? where id =?","刘德华",1);//输出结果System.out.println("修改数据影响行数");System.out.println(affectedRows);//释放资源JDBCUtils.close(null,null,connection);} }
-DAO和增删改查通用方法-BasicDao
-
问题引出
-
Apache-DBUtils + Druid 简化了 JDBC 开发,但存在以下不足:
- SQL 语句固定化
现有实现中 SQL 语句硬编码在代码内,无法通过参数动态传入,通用性差。例如操作不同表时,需重复编写结构相似的 SQL 语句,增删改查逻辑无法复用。 - select 操作返回类型固定
查询操作的返回类型与具体表绑定(如仅返回 Actor 对象),缺乏泛型支持。若需查询其他表数据,需重新定义返回类型,导致代码冗余。 - 单类职责过重
随着表数量增加和业务复杂度提升,单个工具类无法承载所有表的操作,不符合 “单一职责原则”,会导致代码臃肿、维护困难。
- SQL 语句固定化
-
引出 BasicDAO 的解决方案(示意图):
[BasicDAO<T> (泛型基类)] ┌───────────────────────────────────┐ │ 核心通用方法: │ │ - List<T> queryRows(...) │ 多行查询(返回泛型列表) │ - T queryRow(...) │ 单行查询(返回泛型对象) │ - Object queryValue(...) │ 单值查询(如 count/name) │ - int update(...) │ 增删改操作(返回影响行数) └───────────────────────────────────┘▲│ 继承 ┌────────────┴─────────────┐ ┌────────────┴─────────────┐ │ [ActorDao] │ │ [UserDao] │ ├──────────────────────────┤ ├──────────────────────────┤ │ 具体表操作: │ │ 具体表操作: │ │ - selectAll() │ │ - selectAll() │ │ - selectById(int id) │ │ - selectById(int id) │ │ - insert(...) │ │ - insert(...) │ └──────────────────────────┘ └──────────────────────────┘操作 actor 表 操作 user 表
示意图说明:
- BasicDAO 作为泛型基类,封装通用 CRUD 逻辑,通过泛型 T 适配不同实体类(如 Actor、User)
- 每个表对应一个 DAO 子类(如 ActorDao 对应 actor 表),子类只需定义具体 SQL 语句,调用基类方法即可
- 解决 SQL 固定化问题:子类通过参数传入表名和条件,动态生成 SQL
- 解决返回类型问题:通过泛型 T 自动匹配实体类,无需为每个表单独定义返回类型
- 解决类职责问题:按表拆分 DAO 子类,每个类专注于对应表的业务逻辑
-
-
实现案例
package com.xijie.basicdao.utils;import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import com.alibaba.druid.pool.DruidPooledConnection; import org.junit.jupiter.api.Test;import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties;/*** Druid 连接池工具类*/ public class DruidUtil {// 数据源实例(静态单例)private static DruidDataSource dataSource;static {try {// 加载配置文件Properties properties = loadProperties();// 创建数据源dataSource = (DruidDataSource) DruidDataSourceFactory.createDataSource(properties);// 可选:添加监控过滤器// dataSource.setFilters("stat,wall,log4j");} catch (Exception e) {throw new RuntimeException("初始化 Druid 数据源失败", e);}}/*** 加载配置文件*/private static Properties loadProperties() throws IOException {Properties properties = new Properties();// 使用类加载器加载配置文件,避免硬编码路径InputStream inputStream = DruidUtil.class.getClassLoader().getResourceAsStream("druid.properties");if (inputStream == null) {throw new IOException("未找到 druid.properties 配置文件");}properties.load(inputStream);return properties;}/*** 获取数据库连接*/public static Connection getConnection() throws SQLException {return dataSource.getConnection();}/*** 获取原生的 Druid 连接,可访问 Druid 扩展功能*/public static DruidPooledConnection getDruidConnection() throws SQLException {return (DruidPooledConnection) dataSource.getConnection();}/*** 释放资源(通用方法)*/public static void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {try {if (rs != null) rs.close();if (pstmt != null) pstmt.close();if (conn != null) conn.close(); // 归还连接到池} catch (SQLException e) {e.printStackTrace();}}/*** 获取数据源状态信息*/public static String getDataSourceStatus() {return "当前连接数: " + dataSource.getActiveCount() +" | 最大连接数: " + dataSource.getMaxActive() +" | 最小空闲连接数: " + dataSource.getMinIdle() +" | 总连接数: " + dataSource.getActiveCount() + dataSource.getPoolingCount();}/*** 关闭数据源(应用程序退出时调用)*/public static void shutdownDataSource() {if (dataSource != null && !dataSource.isClosed()) {dataSource.close();System.out.println("Druid 数据源已关闭");}}@Testpublic void test() throws Exception{Connection connection=DruidUtil.getConnection();System.out.println("获取连接成功");DruidUtil.close(connection,null,null);} }
package com.xijie.basicdao.domain;import java.util.Date;/*** mydb中的actor的dao类*/ public class Actor {private int id;private String name;private String gender;private Date birthday;private String phone_num;public Actor() {}public Actor(int id, String name, String gender, Date birthday, String phone_num) {this.id = id;this.name = name;this.gender = gender;this.birthday = birthday;this.phone_num = phone_num;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getGender() {return gender;}public void setGender(String gender) {this.gender = gender;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public String getPhone_num() {return phone_num;}public void setPhone_num(String phone_num) {this.phone_num = phone_num;}@Overridepublic String toString() {return "Actor{" +"id=" + id +", name='" + name + '\'' +", gender='" + gender + '\'' +", birthday=" + birthday +", phone_num='" + phone_num + '\'' +'}';} }
package com.xijie.basicdao.dao;import com.xijie.druid.DruidUtil; 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.util.List;public class BasicDao<T> {private QueryRunner queryRunner=new QueryRunner();/*** 多行查询*/List<T> queryRows(String sql,Class<T> cls,Object... parameters){Connection connection=null;try {connection= DruidUtil.getConnection();return queryRunner.query(connection,sql, new BeanListHandler<T>(cls),parameters);} catch (Exception e) {throw new RuntimeException(e);}finally {DruidUtil.close(connection,null,null);}}/*** 单行查询*/T queryRow(String sql,Class<T> cls,Object... parameters){Connection connection=null;try {connection= DruidUtil.getConnection();return queryRunner.query(connection,sql, new BeanHandler<T>(cls),parameters);} catch (Exception e) {throw new RuntimeException(e);}finally {DruidUtil.close(connection,null,null);}}/*** 单值查询*/Object queryValue(String sql,Object... parameters){Connection connection=null;try {connection= DruidUtil.getConnection();return queryRunner.query(connection,sql, new ScalarHandler(),parameters);} catch (Exception e) {throw new RuntimeException(e);}finally {DruidUtil.close(connection,null,null);}}/*** 单值查询*/int update(String sql,Object... parameters){Connection connection=null;try {connection= DruidUtil.getConnection();return queryRunner.update(connection,sql,parameters);} catch (Exception e) {throw new RuntimeException(e);}finally {DruidUtil.close(connection,null,null);}} }
package com.xijie.basicdao.dao;import com.xijie.basicdao.domain.Actor;import java.util.List;public class ActorDao extends BasicDao<Actor>{/*** 查询全部*/public List<Actor> selectAll(){String sql="select * from actor";return super.queryRows(sql,Actor.class);}/*** 按id查询一行*/public Actor selectById(int id){String sql="select * from actor where id=?";return super.queryRow(sql,Actor.class,id);}/*** 按id查询名字*/public String selectNameById(int id){String sql="select name from actor where id=?";return (String)super.queryValue(sql,id);}/*** 插入一行*/public int insertOneRow(String name,String gender,String date,String phoneNum){String sql="insert into actor values(null,?,?,?,?)";return super.update(sql,name,gender,date,phoneNum);}/*** 按id删除一行*/public int deleteById(int id){String sql="delete from actor where id=?";return super.update(sql,id);}/*** 按id修改电话号码*/public int updatePhoneNumById(String phoneNum,int id){String sql="update actor set phone_num=? where id=?";return super.update(sql,phoneNum,id);} }
package com.xijie.basicdao.test;import com.xijie.basicdao.dao.ActorDao; import com.xijie.basicdao.domain.Actor; import org.junit.jupiter.api.Test;import java.util.List;/*** 测试ActorDao*/ public class ActorDaoTest {@Testpublic void test() {ActorDao actorDao=new ActorDao();//查询全部System.out.println("查询全部");List<Actor> actors=actorDao.selectAll();for (Actor actor : actors) {System.out.println(actor);}//通过id查询System.out.println("通过id查询一行");Actor actor=actorDao.selectById(1);System.out.println(actor);//通过id查询nameSystem.out.println("通过id查询name");String name=actorDao.selectNameById(1);//插入一行System.out.println("插入一行");int affectedRowsNum=actorDao.insertOneRow("小花","女","1999-09-09","123456789");System.out.println(affectedRowsNum);//删除一行System.out.println("删除一行");affectedRowsNum=actorDao.deleteById(3);System.out.println(affectedRowsNum);//根据id改电话号System.out.println("根据id改电话号");affectedRowsNum=actorDao.updatePhoneNumById("13245678915",3);System.out.println(affectedRowsNum);} }
实现案例通过完整代码展示了基于 BasicDAO 的数据库操作框架,包含 5 个核心类:
- DruidUtil.java:Druid 连接池工具类,负责数据源初始化、连接获取与释放,为整个框架提供数据库连接支持。
- Actor.java:实体类,映射数据库中 actor 表的字段,包含属性、构造方法、getter/setter 及 toString 方法。
- BasicDAO.java:泛型基类,封装通用的增删改查方法(queryRows、queryRow、queryValue、update),通过泛型 T 适配不同实体类,简化子类的数据库操作实现。
- ActorDao.java:继承 BasicDAO,针对 actor 表实现具体业务方法(查询全部、按 id 查询、插入、删除、更新等),通过调用父类方法完成数据库操作。
- ActorDaoTest.java:测试类,通过 JUnit 测试 ActorDao 中的各方法,验证数据库操作的正确性。
这些类协同工作,体现了 BasicDAO 框架的设计思想:基类封装通用逻辑,子类专注具体业务,结合 Druid 连接池和 DBUtils 工具,实现高效、可复用的数据库操作。