目录
引言
一、JDBC连接数据库步骤
1. 加载驱动
2. 获取连接(URL 用户名 密码)
3. 编写sql
4. 获取执行sql的stmt的对象
5. 执行sql 拿到结果集
6. 遍历结果集
7. 关闭资源(先开的后关 后开的先关)
二、JDBC工具类
版本一:基础JDBC工具类(JdbcUtils)
版本二:配置化JDBC工具类(JdbcUtils2)
版本三:连接池JDBC工具类(JdbcUtils3)
测试
总结
JDBC(Java Database Connectivity)是Java连接数据库的标准API,但在实际开发中直接使用原始JDBC API会带来大量重复代码。本文将介绍三种不同版本的JDBC工具类实现,展示如何通过逐步优化来提高开发效率和代码质量。
首先,需要加载数据库驱动。驱动是各个数据库生产商提供的实现类,用于实现JDBC接口规范。对于MySQL数据库,需要导入mysql-connector-java-5.1.13-bin.jar
驱动包。
在Java代码中,通常使用以下方式加载驱动:
Class.forName("com.mysql.jdbc.Driver");
注意:通过
Class.forName("com.mysql.jdbc.Driver")
加载驱动,可以避免直接使用new Driver()
方式带来的依赖问题和驱动jar包加载两次的问题。
加载驱动后,需要获取数据库连接。使用DriverManager
类的getConnection
方法,传入数据库连接URL、用户名和密码。
String url = "jdbc:mysql:///spring_db"; // 如果是本地数据库,localhost:3306可以省略
String user = "root";
String password = "12345";
Connection conn = DriverManager.getConnection(url, username, password);
URL格式:
jdbc:mysql://localhost:3306/spring_db
jdbc
:主协议
mysql
:子协议(可能变化)
localhost
:主机
3306
:默认端口号
spring_db
:数据库名称
接下来,编写需要执行的SQL语句。SQL语句可以是查询、插入、更新或删除操作。
String sql = "SELECT * FROM t_user";
有两种方式获取执行SQL的对象:Statement
和PreparedStatement
。
Statement
Statement
接口用于执行静态SQL语句,但存在SQL注入问题(字符串拼接)。
Statement stmt = conn.createStatement();
注意:
Statement
接口虽然简单易用,但在处理用户输入时容易受到SQL注入攻击。例如,如果用户输入的用户名或密码包含恶意SQL代码,可能会导致数据库泄露或被篡改。
PreparedStatement
PreparedStatement
是Statement
的子接口,支持预编译SQL语句,采用占位符的形式,能有效防止SQL注入问题。推荐使用。
String sql = "SELECT * FROM t_user WHERE username = ? AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username); // 设置第一个参数
pstmt.setString(2, password); // 设置第二个参数
注意:使用
PreparedStatement
时,SQL语句中的参数用?
占位符代替,再通过setXxx
方法设置具体值。这种方式不仅提高了代码的可读性和可维护性,还能有效防止SQL注入攻击。
执行SQL语句并获取结果集。如果是查询语句,返回的结果集封装在ResultSet
接口中。
ResultSet rs = pstmt.executeQuery(); // 查询语句
// int affectedRows = pstmt.executeUpdate(); // 增删改语句
注意:
executeQuery
方法用于执行查询语句,返回一个ResultSet
对象;executeUpdate
方法用于执行增删改语句,返回受影响的行数。
遍历ResultSet
结果集,获取查询的数据。
while (rs.next()) {
int id = rs.getInt("id");
String username = rs.getString("username");
String password = rs.getString("password");
String email = rs.getString("email");
System.out.println(id + ", " + username + ", " + password + ", " + email);
}
注意:
ResultSet
内部维护一个游标,默认指向第一行数据之前,调用next()
方法向下移动游标。获取值的方法有多种,如getInt
、getString
等,可以根据字段类型选择合适的方法。
最后,必须关闭所有打开的资源,包括ResultSet
、Statement
和Connection
对象。关闭资源的代码一般放在finally
块中,确保一定会执行。
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (pstmt != null) {
try {
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
注意:关闭资源的顺序应与打开的顺序相反,即先开的后关,后开的先关。这样可以确保资源的正确释放,避免内存泄漏和资源占用问题。
通过以上步骤,我们可以使用JDBC连接数据库并执行SQL操作。结合PreparedStatement
可以有效防止SQL注入问题,确保数据库操作的安全性。
这是最基础的JDBC工具类实现(加载驱动,获取连接和关闭资源可提取出来,重复使用,即JDBC工具类1.0版本),主要解决了驱动加载和连接获取的基本需求。
package com.qcby.utils;
import com.mysql.jdbc.Driver;
import java.sql.*;
/**
* JDBC工具类 简化开发
* 减少重复性代码
* JDBC的工具类 1.0版本
*/
public class JdbcUtils {
/**
* 加载驱动
*/
public static void createDriver(){
try{
//2种
//1.直接调用方法
DriverManager.registerDriver(new Driver());
//2.反射加载
//Class.forName("com.mysql.jdbc.Driver");
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 获取连接
* @return
*/
public static Connection getConnection(){
Connection connection=null;
try {
//1.加载驱动
createDriver();
//2.获取连接
connection=DriverManager.getConnection("jdbc:mysql:///spring_db","root","12345");
}catch (SQLException e){
e.printStackTrace();
}
return connection;
}
/**
* 关闭资源
*/
//1.查询 重载方法
public static void close(Connection connection, ResultSet resultSet, Statement statement){
try{
resultSet.close();
statement.close();
connection.close();
}catch (SQLException e){
e.printStackTrace();
}
}
//2.增删改
public static void close(Connection connection, Statement statement){
try{
statement.close();
connection.close();
}catch (SQLException e){
e.printStackTrace();
}
}
}
特点分析
优点:
简单直接,适合小型项目或快速原型开发
封装了基本的连接获取和资源关闭操作
缺点:
硬编码数据库连接信息,不利于维护
每次调用都会重复注册驱动(虽然JDBC规范允许重复注册,但不推荐)
缺乏异常处理机制
没有连接池支持,性能较差
这个版本通过读取配置文件来提高灵活性,解决了硬编码问题。
package com.qcby.utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
/**
* JDBC的工具类 2.0版本(智能一些),编写properties属性文件,程序就可以读取属性文件
* 1. 驱动类
* 2. 数据库地址
* 3. 用户名
* 4. 密码
*/
public class JdbcUtils2 {
//静态不可更改常量
private static final String diverclass;
private static final String url;
private static final String username;
private static final String password;
//静态代码块 类一加载的时候就会执行,调用的这个类的时候
static {
//加载db.properties文件读取链接数据库的所有参数
// 加载属性文件
Properties pro = new Properties();
InputStream inputStream = JdbcUtils.class.getResourceAsStream("/db.properties");
// 加载属性文件
try {
pro.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
// 给常量赋值
diverclass = pro.getProperty("driverclass");
url = pro.getProperty("url");
username = pro.getProperty("username");
password = pro.getProperty("password");
}
/**
* 加载驱动
*/
public static void createDriver(){
try{
//2种
//1.直接调用方法
//DriverManager.registerDriver(new Driver());
//2.反射加载
Class.forName(diverclass);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 获取连接
* @return
*/
public static Connection getConnection(){
Connection connection=null;
try {
//1.加载驱动
createDriver();
//2.获取连接
connection= DriverManager.getConnection(url,username,password);
}catch (SQLException e){
e.printStackTrace();
}
return connection;
}
/**
* 关闭资源
*/
//1.查询
public static void close(Connection connection, ResultSet resultSet, Statement statement){
try{
resultSet.close();
statement.close();
connection.close();
}catch (SQLException e){
e.printStackTrace();
}
}
//2.增删改
public static void close(Connection connection, Statement statement){
try{
statement.close();
connection.close();
}catch (SQLException e){
e.printStackTrace();
}
}
}
#db.properties
driverclass=com.mysql.jdbc.Driver
url=jdbc:mysql:///spring_db
username=root
password=12345
改进点
配置文件支持:
将数据库连接信息移到db.properties
文件中
通过Properties
类加载配置
异常处理改进:
将检查异常转换为运行时异常,强制调用方处理
驱动加载优化:
使用Class.forName()
替代直接注册驱动
仍然存在的问题
仍然没有连接池支持
资源管理可以更完善
配置文件路径硬编码
这是最完善的版本,引入了连接池技术(Druid)来提高性能和资源利用率。
package com.qcby.utils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* JDBC的工具类 1.0版本
* JDBC的工具类 2.0版本(智能一些),编写properties属性文件,程序就可以读取属性文件
* JDBC的工具类 3.0版本,加入连接池对象
*/
public class JdbcUtils3 {
// 连接池对象
private static DataSource DATA_SOURCE;
static{
// 加载属性文件
Properties pro = new Properties();
InputStream inputStream = JdbcUtils3.class.getResourceAsStream("/druid.properties");
try {
// 加载属性文件
pro.load(inputStream);
// 创建连接池对象
DATA_SOURCE = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 从连接池中获取连接,返回。
* @return
*/
public static Connection getConnection(){
Connection conn = null;
try {
conn = DATA_SOURCE.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* 关闭资源
* @param conn
* @param stmt
* @param rs
*/
public static void close(Connection conn, ResultSet rs, Statement stmt){
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 关闭资源
* @param conn
* @param stmt
*/
public static void close(Connection conn, Statement stmt){
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
#druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///spring_db
username=root
password=12345
initialSize=5
maxActive=10
maxWait=3000
maxIdle=6
minIdle=3
核心优势
连接池支持:
使用Druid连接池,显著提高性能
连接可复用,减少创建和销毁开销
配置灵活性:
所有数据库配置(包括连接池参数)都通过druid.properties
管理
可以轻松调整连接池大小、超时时间等参数
更健壮的异常处理:
构造阶段失败会抛出运行时异常
方法内部捕获异常但不吞掉,便于排查问题
资源管理优化:
虽然关闭逻辑与版本二类似,但配合连接池使用更合理
最佳实践建议
异常处理:
生产环境中应使用日志框架记录异常,而不是简单打印
考虑自定义异常类提供更友好的错误信息
资源管理:
使用try-with-resources语法(Java 7+)自动关闭资源
对于连接池,close()
方法实际上是将连接返回到池中
连接池选择:
Druid是不错的选择,也可以考虑HikariCP(性能更好)
根据项目需求配置合适的连接池参数
SQL操作:
对于生产代码,应使用PreparedStatement防止SQL注入
考虑使用ORM框架(MyBatis、Hibernate)进一步简化开发
特性 | 1.0版本 | 2.0版本 | 3.0版本 |
---|---|---|---|
配置管理 | 硬编码 | 配置文件 | 连接池专属配置 |
连接创建 | 每次新建 | 每次新建 | 连接池复用 |
性能表现 | 差(100ms/次) | 差 | 优(<10ms/次) |
并发支持 | 无 | 无 | 队列机制 |
适用场景 | 学习/测试 | 小型项目 | 生产环境 |
package com.qcby.model;
/**
* 对应数据库的账户表
* 实体类
*/
public class Account {
private Integer id;
private String name;
private Double money;
public Account(){
}
public Account(Integer id, String name, Double money) {
this.id = id;
this.name = name;
this.money = money;
}
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 Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
package com.qcby.dao;
import com.qcby.model.Account;
import com.qcby.utils.JdbcUtils;
import com.qcby.utils.JdbcUtils2;
import com.qcby.utils.JdbcUtils3;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcDao {
public void select01(){
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//jdbc连接数据库
//1.加载驱动
//2.获取连接(URL 用户名 密码)
connection = JdbcUtils.getConnection();
//3.编写sql
String sql="select * from account";
//4.获取执行sql的stmt的对象
statement = connection.createStatement();
// 两个 stmt(sql注入问题 字符串拼接) pstmt(预编译 防止sql植入问题 占位符))
//5.执行sql 拿到结果集
resultSet = statement.executeQuery(sql);
//6.遍历结果集
while (resultSet.next()){
Account account=new Account();
account.setId(resultSet.getInt("id"));
account.setName(resultSet.getString("username"));
account.setMoney(resultSet.getDouble("money"));
System.out.println(account);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//7.关闭资源(先开的后关 后开的先关)
JdbcUtils.close(connection, resultSet, statement);
}
}
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//jdbc连接数据库
//1.加载驱动
//2.获取连接(URL 用户名 密码)
connection = JdbcUtils3.getConnection();
//3.编写sql
String sql="select * from account";
//4.获取执行sql的stmt的对象
statement = connection.createStatement();
// 两个 stmt(sql注入问题 字符串拼接) pstmt(预编译 防止sql植入问题 占位符))
//5.执行sql 拿到结果集
resultSet = statement.executeQuery(sql);
//6.遍历结果集
while (resultSet.next()){
Account account=new Account();
account.setId(resultSet.getInt("id"));
account.setName(resultSet.getString("name"));
account.setMoney(resultSet.getDouble("money"));
System.out.println(account);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//7.关闭资源(先开的后关 后开的先关)
JdbcUtils3.close(connection, resultSet, statement);
}
}
// public void select(){
// //jdbc连接数据库
// //1.加载驱动
// //2.获取连接(URL 用户名 密码)
// //3.编写sql
// //4.获取执行sql的stmt的对象
// // 两个 stmt(sql注入问题 字符串拼接) pstmt(预编译 防止sql植入问题 占位符))
// //5.执行sql 拿到结果集
// //6.遍历结果集
// //7.关闭资源(先开的后关 后开的先关)
// }
}
JDBC工具类的演进反映了软件开发中"封装重复、提高灵活性、优化性能"的普遍原则:
基础版:解决基本功能,适合简单场景
配置化版:通过外部配置提高灵活性
连接池版:引入连接池提升性能和资源利用率
在实际项目中,建议直接使用成熟的ORM框架或经过充分测试的JDBC工具类库,除非有特殊需求需要自定义实现。