什么是 JDBC
JDBC 是 Java Data Base Connectivity 的缩写,指的是 Java 提供了一些访问数据库的统一接口。如果我们在 Java 中想要访问数据库,就要使用 JDBC。
连接数据库
在连接数据库之前,我们要确保有 3 个信息:
- 数据库连接串
- 用户名
- 密码
有了这 3 个东西,我们就可以连接任意一个数据库,这里我们用 IDEA 中的 DataBase 功能,启动一个 H2 数据库。
当我们启动数据库后,我们使用 DriverManager
的 getConnection
方法来返回一个数据库连接(注意 Connection
是要关闭的,这里我们使用了 try...resource 自动关闭)
package com.github.hcsp.exception;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Main {
public static void main(String[] args) {
File projectDir = new File(System.getProperty("basedir", System.getProperty("user.dir")));
String jdbcUrl = "jdbc:h2:file:" + new File(projectDir, "learn").getAbsolutePath();
try (Connection connection = DriverManager.getConnection(jdbcUrl, "", "")) {
System.out.println(jdbcUrl);
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("数据库连接异常");
}
}
}
当我们运行的时候,可能会遇到这种错误:
Java 连接数据库的时候,需要相应的数据库驱动,由于我们用的是 H2 数据库,所以就安装 h2 的驱动:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
<scope>compile</scope>
</dependency>
再试一下,发现没有报错,那就说明我们成功连接上数据库了。由于我们本地的数据库处于测试目的,没有账号和密码,如果有的话,只要在 getConnection
方法中传入就可以了。
执行 SQL 语句
那连接上数据库以后,我们就可以执行 SQL 语句了。
connection.createStatement
可以创建一个 SQL 语句,它也是需要 close
的,然后使用 executeQuery
来执行语句。
ResultSet
是一串数据,调用 next
方法取出来的是一行数据,我们可以使用相应的 get
方法,依次取出每一列的数据,它的列是从 1 开始的。
package com.github.hcsp.exception;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class Main {
public static void main(String[] args) {
File projectDir = new File(System.getProperty("basedir", System.getProperty("user.dir")));
String jdbcUrl = "jdbc:h2:file:" + new File(projectDir, "learn").getAbsolutePath();
try (Connection connection = DriverManager.getConnection(jdbcUrl, "", "");
Statement statement = connection.createStatement()) {
final ResultSet resultSet = statement.executeQuery("select * from USERS_DATA");
while (resultSet.next()) {
System.out.println("用户ID 是 " + resultSet.getInt(1));
System.out.println("用户名称 是 " + resultSet.getString(2));
}
System.out.println(jdbcUrl);
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("数据库连接异常");
}
}
}
到这里,我们就实现了用 Java 代码连接数据库,并且执行 SQL 语句的功能。
可以看到,由于 Java 的封装性,假如未来我们更换数据库的话,只要把连接串改一改就可以了,下面的所有方法,都是调用的接口类型的方法,所以是通用的。
SQL 注入
SQL 注入是指在输入的字符串之中注入SQL指令,在设计不良的程序当中忽略了字符检查,那么这些注入进去的恶意指令就会被数据库服务器误认为是正常的SQL指令而运行,因此遭到破坏或是入侵。(维基百科)
假设我们现在有个需求,是由客户端输入一个用户名,查询某个用户是否存在。我们可能会这样实现:
public class Main {
public static void main(String[] args) {
final boolean isUserExist = checkUserExist("zzh1");
System.out.println(isUserExist);
}
public static boolean checkUserExist(String userName) {
File projectDir = new File(System.getProperty("basedir", System.getProperty("user.dir")));
String jdbcUrl = "jdbc:h2:file:" + new File(projectDir, "learn").getAbsolutePath();
try (Connection connection = DriverManager.getConnection(jdbcUrl, "", "");
Statement statement = connection.createStatement()) {
final ResultSet resultSet = statement.executeQuery("select * from USERS_DATA where name = '" + userName + "'");
return resultSet.next();
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("数据库连接异常");
}
}
}
看上去没什么问题,其实这个代码非常危险,因为我们已经假定了传入的字符串都是正常的用户名,但是对于由用户输入的所有数据,都要看作是不可信的。
假如现在传入的不是 zzh1 这种字符串,而是 zzh1;delete from USERS_DATA where ID = 2;
这种包含 SQL 语句的字符串,我们这样拼接上去的话,就导致执行了攻击者的 SQL 语句。
所以 SQL 注入的重点,实际上是我们不能完全信任由用户输入的数据,并且把这些数据拼接到 SQL 语句上。
PrepareStateMent
PrepareStateMent
可以解决这个问题,它在创建 SQL 语句的时候,使用 ?
作为占位符,采用参数传入的形式,即参数只会作为参数,而不会改变已经传入的 SQL 语句的结构。
public class Main {
public static void main(String[] args) {
final boolean isUserExist = checkUserExist("zzh");
System.out.println(isUserExist);
}
public static boolean checkUserExist(String userName) {
File projectDir = new File(System.getProperty("basedir", System.getProperty("user.dir")));
String jdbcUrl = "jdbc:h2:file:" + new File(projectDir, "learn").getAbsolutePath();
try (Connection connection = DriverManager.getConnection(jdbcUrl, "", "");
PreparedStatement statement = connection.prepareStatement("select * from USERS_DATA where name = ?")) {
statement.setString(1, userName);
final ResultSet resultSet = statement.executeQuery();
return resultSet.next();
} catch (SQLException throwables) {
throwables.printStackTrace();
throw new RuntimeException("数据库连接异常");
}
}
}