文章目录
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。
一、JDBC使用的流程
1、流程与环境
- 搭建开发环境(数据库、jar包)
- 在程序中加载数据库驱动
- 建立连接
- 创建用于向数据库发送SQL的Statement对象
- 从代表结果集的ResultSet中取出数据
- 断开与数据库的连接,并释放相关资源
jar包:mysql-connector-java-5.1.7-bin.jar
-- 准备数据环境
CREATE DATABASE jdbctest;
USE jdbctest;
CREATE TABLE user(
uid INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20),
password VARCHAR(20),
name VARCHAR(20)
);
INSERT INTO user VALUES(NULL, 'aaa', '111', '小明');
INSERT INTO user VALUES(NULL, 'bbb', '222', '小红');
INSERT INTO user VALUES(NULL, 'ccc', '333', '小华');
2、JDBC操作基本实例
Connection conn = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 1. 加载驱动
// 下面的方法会导致驱动注册两次,不采用
//DriverManager.registerDriver(new Driver());
// 采用字节码对象注册,只注册一次
Class.forName("com.mysql.jdbc.Driver");
// 2. 获得连接
// 数据库连接对象(主协议+子协议+主机名+端口号+数据库名、用户名、密码)
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest", "root", "root");
// 3. 创建执行sql语句的对象
String sql = "SELECT * FROM user";
statement = conn.createStatement();
// 4. 执行sql语句
resultSet = statement.executeQuery(sql);
// 5. 遍历查询结果
while (resultSet.next()) {
int uid = resultSet.getInt("uid");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String name = resultSet.getString("name");
System.out.println("uid:" + uid + " username:" + username + " password:" + password + " name:" + name);
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
// 6. 释放资源
// 释放结果集
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
// 释放查询对象
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
// 释放连接
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
3、JDBC的资源释放
Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet,Statement和Connection对象。
特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。
Connection的使用原则是尽量晚创建,尽量早的释放。
二、 JDBC API
1、DriverManager
-
注册驱动
- Class.forName(“com.mysql.jdbc.Driver”);
-
获得连接
- Connection getConnection(String url, String username, String password);
url写法: jdbc:mysql://localhost:3306/test
- jdbc:协议
- mysql:子协议
- localhost:主机名
- 3306:端口号
- test:数据库名
本机默认端口的数据库可简写:
jdbc:mysql:///test
// 会注册驱动两次
DriverManager.registerDriver(new Driver());
// 原因分析
// 1. 加载Driver.class文件时,执行里的静态代码块会注册一次
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
// 2. 执行DriverManager的registerDriver()方法,注册一次
// 正确的做法:采用字节码对象进行注册
Class.forName("com.mysql.jdbc.Driver");
2、Connection
- 创建执行SQL语句对象
方法 | 描述 |
---|---|
Statement createStatement() | 创建SQL语句对象(存在SQL注入的漏洞) |
PreparedStatement prepareStatement(String sql) | 预编译SQL语句 |
CallableStatement prepareCall(String sql) | 执行SQL中的存储过程 |
- 进行事物的管理
方法 | 描述 |
---|---|
setAutoCommit(boolean autoCommit) | 设置事务是否自动提交 |
commit() | 事务提交 |
rollback() | 事务回滚 |
3、StateMent
-
执行SQL语句
方法 描述 boolean execute(String sql) 执行SQL语句,执行SELECT语句返回true,否则返回false Result executeQuery(String sql) 执行SQL中的SELECT语句 int executeUpdate(String sql) 执行SQL中的INSERT / UPDATE / DELETE 语句,返回的int值代表影响的行数 -
执行批处理操作
方法 | 描述 |
---|---|
addBatch(String sql) | 添加到批处理 |
executeBatch() | 执行批处理 |
clearBatch() | 清空批处理 |
3、ResultSet 结果集
结果集:是对查询语句(SELECT)查询结果的封装。
方法 | 描述 |
---|---|
next() | 下一行,没有时为null |
getObject() | 以Object的类型获得结果集的数据 |
getXXX() | 以指定的类型获得结果集的数据 |
三、 JDBC的CRUD操作
1、向数据库中保存记录
// 编写SQL
String sql = "INSERT INTO user VALUES(null, 'eee', '555', '小亮')";
// 执行SQL:
int i = statement.executeUpdate(sql);
if (i > 0) {
System.out.println("保存成功!");
}
2、修改数据库中的记录
sql = "UPDATE user SET username='qqq', password='456', name='James' WHERE uid=4 ";
i = statement.executeUpdate(sql);
if (i > 0) {
System.out.println("修改成功啦!");
}
3、删除数据库中的记录
sql = "DELETE FROM user WHERE uid = 4";
i = statement.executeUpdate(sql);
if (i > 0) {
System.out.println("删除成功啦");
}
4、查询数据库中的记录
// 查询多条记录==>while 单条记录==>if
sql = "select * from user";
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
System.out.println(("uid:" + resultSet.getInt("uid") + " username:" + resultSet.getString("username") + " password:" + resultSet.getString("password") + " name:" + resultSet.getString("name")));
}
四、JDBC的工具类的抽取
为了简化JDBC的开发,可以将一些重复的代码进行提取,并将需要配置的参数写入配置文件,利用Properities类进行获取。
- jdbc.properities配置文件(src目录下)
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbctest
username=root
password=102
- 抽取JDBC工具类
public class JdbcUtils {
private static final String driverClass;
private static final String url;
private static final String username;
private static final String password;
static {
// 加载属性文件并解析
Properties properties = new Properties();
// 属性文件的输入流:使用类的加载器方式进行获取
InputStream is = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
try {
properties.load(is);
} catch (IOException e) {
e.printStackTrace();
}
driverClass = properties.getProperty("driverClass");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
}
// 注册驱动的方法
public static void loadDriver() throws ClassNotFoundException {
Class.forName(driverClass);
}
// 获得连接的方法
public static Connection getConnection() throws SQLException, ClassNotFoundException {
loadDriver();
Connection conn = DriverManager.getConnection(url, username, password);
return conn;
}
// 资源的释放
public static void release(Statement statement, Connection connection) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
// 资源的释放 重载
public static void release(Statement statement, Connection connection, ResultSet resultSet) {
release(statement, connection);
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
}
}
五、SQL注入漏洞及解决
1、SQL注入漏洞演示
public static boolean login(String username, String password) {
Connection conn = null;
Statement statement = null;
ResultSet resultSet = null;
try {
conn = JdbcUtils.getConnection();
statement = conn.createStatement();
String sql = "SELECT * FROM user WHERE username ='" + username + "' AND password = '" + password + "'";
resultSet = statement.executeQuery(sql);
if (resultSet.next()) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.release(statement, conn, resultSet);
}
return false;
}
上面就是模拟服务器从网页获取到用户登录的用户名和密码,利用字符串拼接组成SQL语句,在进行数据库查询,以判断用户信息是否合法。
-
恶意注入
如果用户写入的用户名为:【abc’ OR ‘2=2】或 【abc’ – 】密码:任意
这样最终交由MySQL执行的SQL语句:
SELECT * FROM user WHERE username=‘abc’ OR ‘2=2’ AND password=‘xxx’;
SELECT * FROM user WHERE username=‘abc’ - - ’ AND password='xxx’
这样无论密码什么,Mysql都会返回数据,引起误判断(-- 为MySQL的注释)。
2、SQL注入漏洞的解决
PreparedStatement 是 Statement 的子接口,它的实例对象可以通过调用Connection.preparedStatement(sql)方法获得,相对于Statement对象而言:
- PreperedStatement 可以避免SQL注入的问题。
- Statement会使数据库频繁编译SQL,可能造成数据库缓冲区溢出。
PreparedStatement 可对SQL进行预编译,从而提高数据库的执行效率。 - PreperedStatement对于sql中的参数,使用占位符的形式进行替换,简化sql语句的编写。
3、PreparedStatement的使用
- 保存数据
String sql = "INSERT INTO user VALUES (NULL,?,?,? )";
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, "qqq");
preparedStatement.setString(2, "123");
preparedStatement.setString(3, "王五");
int count = preparedStatement.executeUpdate();
if (count > 0) {
System.out.println("保存成功!");
}
- 修改数据
String sql = "UPDATE user set username = ?, password = ?, name = ? WHERE uid = ?";
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setString(1, "xmlx");
preparedStatement.setString(2, "5452");
preparedStatement.setString(3, "张金");
preparedStatement.setInt(4, 5);
int count = preparedStatement.executeUpdate();
if (count > 0) {
System.out.println("修改成功!");
}
- 删除数据
String sql = "DELETE FROM user WHERE uid = ?";
preparedStatement = conn.prepareStatement(sql);
preparedStatement.setInt(1, 11);
int count = preparedStatement.executeUpdate();
if (count > 0) {
System.out.println("删除成功!");
}
- 查询数据
String sql = "SELECT * FROM user";
preparedStatement = conn.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
System.out.println(("uid:" + resultSet.getInt("uid") + " username:" + resultSet.getString("username") + " password:" + resultSet.getString("password") + " name:" + resultSet.getString("name")));
}
六、数据库连接池 C3P0
连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。
1、特点
- 应用程序直接获取连接的缺点
用户每次请求都需要向数据库获得连接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出。
-
C3PO连接池
引入jar: c3p0-0.9.1.2.jar
2、C3P0连接池的使用
- 连接池配置文件c3p0-config.xml(src目录下)
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///jdbctest</property>
<property name="user">root</property>
<property name="password">root</property>
<property name="initialPoolSize">5</property> <!-- 初始化连接数 -->
<property name="maxPoolSize">20</property> <!-- 最大连接数 -->
</default-config>
</c3p0-config>
- 使用连接池的JDBC工具类
public class JdbcUtils2 {
// 连接池对象
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
// 获得连接的方法
public static Connection getConnection() throws SQLException, ClassNotFoundException {
Connection conn = dataSource.getConnection();
return conn;
}
// 资源的释放
public static void release(Statement statement, Connection connection) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
// 资源的释放 重载
public static void release(Statement statement, Connection connection, ResultSet resultSet) {
release(statement, connection);
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
}
}
- 使用连接池
public static void demo2() {
Connection conn = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 获得连接
conn = JdbcUtils2.getConnection();
// 编写Sql
String sql = "SELECT * FROM user";
// 预编译SQL
preparedStatement = conn.prepareStatement(sql);
// 设置参数
// 执行SQL
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
System.out.println(("uid:" + resultSet.getInt("uid") + " username:" + resultSet.getString("username") + " password:" + resultSet.getString("password") + " name:" + resultSet.getString("name")));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.release(preparedStatement, conn, resultSet);
}
}