一、JDBC连接数据库的7个步骤
1、加载驱动
2、获取连接
3、编写sql
4、获取执行sql的stmt对象
有两种 stmt(存在sql注入问题 字符串拼接) pstmt(预编译可以防止sql注入)
5、执行sql 拿到结果集
6、遍历结果集
7、关闭资源(按倒序关闭,资源先开的后关,后开的先关)
主方法程序实例
java
package com.qcby.dao;
import com.qcby.model.Account;
import com.qcby.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcSelect {
public static void main(String[] args) {
Connection connection =null;
Statement statement=null;
ResultSet resultSet=null;
try {
//建立连接
connection= JdbcUtils.getConnection();
//编写sql
String sql="select * from account";
//获取执行sql的statement对象
statement=connection.createStatement();
//执行sql拿到结果集
resultSet=statement.executeQuery(sql);
//遍历结果集
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 {
//关闭资源
JdbcUtils.close(connection,resultSet,statement);
}
}
}
二、JDBC工具类的三个版本
上述的7个步骤中可以将加载驱动、获取连接和关闭资源抽取出来从而简化代码。
1.0版本
- 所有数据库连接信息直接写在代码里(地址、账号密码都固定)
- 每次要连接数据库时都新建一个连接
- 用完就直接扔掉,下次再用再新建
java
package com.qcby.utils;
import org.gjt.mm.mysql.Driver;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
//JDBC工具类,减少代码
public class JdbcUtils {
//1、加载驱动
public static void createSDriver(){
try {
//2种 直接调用方法
// DriverManager.registerDriver(new Driver());
DriverManager.registerDriver(new Driver());
//反射加载
//Class.forName(driverclass); // MySQL 8.0+驱动
}catch (SQLException e){
e.printStackTrace();
}
}
//2、获取连接
public static Connection getConnection(){
Connection connection=null;
try {
//1、加载驱动
createSDriver();
//2、获取连接
connection= DriverManager.getConnection("jdbc:mysql:///spring_db","root","12345");
}catch (SQLException e){
e.printStackTrace();
}
return connection;
}
//3、关闭连接,方法重载
public static void close(Connection connection, ResultSet resultSet,Statement statement){
try {
resultSet.close();
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
//3、关闭连接
public static void close(Connection connection,Statement statement){
try {
statement.close();
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2.0版本
- 把数据库信息移到单独的配置文件里(db.properties)
- 想改数据库地址或密码时,不用改代码,改配置文件就行
- 但还是每次新建连接,用完就扔
编写properties属性文件,程序就可以读取属性文件
java
driverclass=com.mysql.jdbc.Driver
url=jdbc:mysql:///spring_db
username=root
password=12345
java
package com.qcby.utils;
import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtil3 {
private static final String driverclass;
private static final String url;
private static final String username;
private static final String password;
static{
// 加载属性文件
Properties pro = new Properties();
InputStream inputStream = JdbcUtil3.class.getResourceAsStream("/db.properties");
try {
// 加载属性文件
pro.load(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
// 给常量赋值
driverclass = pro.getProperty("driverclass");
url = pro.getProperty("url");
username = pro.getProperty("username");
password = pro.getProperty("password");
}
/**
* 加载驱动
*/
public static void loadDriver(){
try {
// 加载驱动类
Class.forName(driverclass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 加载完驱动,获取到连接,返回连接对象
* @return
*/
public static Connection getConnection(){
// 加载驱动
loadDriver();
// 获取到连接对象,返回
Connection conn = null;
try {
// 获取到连接
conn = DriverManager.getConnection(url,username,password);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
/**
* 关闭资源
* @param conn
* @param stmt
* @param rs
*/
public static void close(Connection conn, Statement stmt, ResultSet rs){
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();
}
}
}
}
3.0版本
什么是连接池
连接池技术通过复用数据库连接,显著提升了系统性能。以电商库存系统为例,当100个用户同时购买商品时:
传统方式需要100次连接创建和销毁,消耗大量资源。而使用连接池(如配置10个连接)后:
-
系统启动时预先建立10个连接
-
前10个请求直接获取现有连接
-
使用后连接归还而非销毁
-
后续请求复用这些连接
这就像物业管理:
-
传统方式:每次有访客都现场配钥匙,离开就销毁
-
连接池方式:预先准备10把钥匙,访客使用后归还
代码
使用druid.properties配置文件管理连接池参数
连接从连接池获取而非每次新建
- 使用Druid连接池,预先创建一批连接放着
- 需要用时直接从池子里拿,用完放回去
- 可以控制最多准备多少连接、最少留多少
- 优势:速度快(不用每次都新建)、省资源(连接可以重复用)、还能防漏水(连接泄漏检测)
java
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///spring_db
username=root
password=12345
initialSize=5
maxActive=10
maxWait=3000
maxIdle=6
minIdle=3
java
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;
public class JdbcUtils2 {
// 连接池对象
private static DataSource DATA_SOURCE;
static{
// 加载属性文件
Properties pro = new Properties();
InputStream inputStream = JdbcUtils2.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, Statement stmt, ResultSet rs){
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();
}
}
}
}
测试
java
package com.qcby.dao;
import com.qcby.model.Account;
import com.qcby.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcSelect {
public static void main(String[] args) {
Connection connection =null;
Statement statement=null;
ResultSet resultSet=null;
try {
//建立连接
connection= JdbcUtils.getConnection();
//编写sql
String sql="select * from account";
//获取执行sql的statement对象
statement=connection.createStatement();
//执行sql拿到结果集
resultSet=statement.executeQuery(sql);
//遍历结果集
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 {
JdbcUtils.close(connection,resultSet,statement);
}
}
}

补充:Sql注入问题
想象你开了一家会员制餐厅:
- 正常流程:客人报会员名"张三",你查名单确认后放行
- 注入攻击:客人报会员名"张三' OR '1'='1",你的名单系统会把所有人都放进来!
漏洞是如何产生的 (字符串拼接问题)
java
String username = "aaa'or'1=1"; // 用户输入
String password = "随便输";
String sql = "SELECT * FROM users WHERE username='"+username+"' AND password='"+password+"'";
实际执行的SQL变成:
SELECT * FROM users WHERE username='aaa'or'1=1' AND password='随便输'
'1'='1'永远成立,相当于不需要密码
攻击者常用的花招
1、万能密码
用户名:admin' --
密码:随便
相当于SQL:SELECT * FROM users WHERE username='admin' --' AND password='随便'
--是SQL注释符,后面条件被忽略
2、永远为真:
用户名:aaa'or'1=1
密码:随便
相当于:SELECT * FROM users WHERE username='aaa'or'1=1' AND password='随便'
如何解决sql注入问题
使用PreparedStatement
就像改进后的餐厅检查流程:
- 提前预定格式:会员名必须是一个完整字符串(预编译)
- 严格检查输入:即使用户输入特殊符号,也只当作普通字符
基本格式:

java
// 获取到连接对象
conn = dataSource.getConnection();
// 编写SQL语句
String sql = "insert into account values (null,?,?)";
// 预编译SQL语句
stmt = conn.prepareStatement(sql);
// 设置值
stmt.setString(1,"eee");
stmt.setString(2,"1233");
// 执行sql
stmt.executeUpdate();
为什么使用PreparedStatement安全?

注意:
- 永远不要拼接SQL:就像不要直接吃陌生人给的糖果
- 始终使用PreparedStatement:这是防止SQL注入的最简单有效方法
- 即使参数固定也要用:养成良好的编码习惯