Statement:
Statement是java连接数据库操作的一个接口,但是现在被prepareStatement取代是因为它存在一个SQL注入问题,那什么是SQL注入呢?本篇带你了解。
在了解SQL注入之前,我们先了解一下JDBC连接数据库的知识。
连接mysql数据库:
获取数据库连接具体需要三要素:
要素一: Driver接口实现类
java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。
在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。
加载驱动:Class.forName(“com.mysql.jdbc.Driver”);
com.mysql.jdbc.Driver这个需要到mysql官网去下载一个,具体下载可以去网上找。下载后把压缩包解压
创建一个文件后把它复制到里并右击鼠标把它放到库中
要素二: URL
JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
JDBC URL的标准由三部分组成,各部分间用冒号分隔。 jdbc:子协议:子名称
(协议: JDBC URL中的协议总是jdbc ;子协议: 子协议用于标识一个数据库驱动程序;子名称: 一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名)
要素三: 用户名和密码
user,password可以用“属性名=属性值”方式告诉数据库
可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接
当三个要素都准备好后,我们需要可以建立与数据库的连接了
我们可以把这4个基本信息声明在一个配置文件中,通过Properties去获取他们。这样当你想修改数据库时,我们只需要在配置文件中修改就可以了。
(配置文件声明在工程的src目录下)
user=root //用户名
password=123456 //密码
url=jdbc:mysql://localhost:3306/test //最后一个是你想要连接的数据库
driverClass=com.mysql.cj.jdbc.Driver // 这个不用变,mysql是8.0以下把如果连接不上,把cj删了
我们可以把数据库连接和关闭资源写到一个工具类中,方便调用
//数据库的连接操作
public static Connection getConnection() throws Exception {
//1.加载配置文件
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");//"文件路径"
Properties pros = new Properties();
pros.load(is);
//2.读取配置信息
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//3.加载驱动
Class.forName(driverClass);
//4.获取连接
Connection conn = DriverManager.getConnection(url,user,password);
return conn;
}
//资源关闭操作
public static void closeResource(Connection conn, Statement ps) {
try {
if (ps != null) {
ps.close();
}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
使用Statement操作数据库:
test数据库中我们有一个user_table表,里面存放我们的用户名,密码和余额字段
现在我们对它进行一个查询操作,当我用户名和密码都输入正确的情况下提示登录成功
我们先写一个登录操作代码,里面存在一些大家可能看不懂的代码不用急,我们这篇主要介绍的是SQL注入问题,看不懂的代码下一篇会说明,这里为了方便大家观看,就把数据库连接和资源关闭也一起写上了,没有调用工具类。
// 使用Statement实现对数据表的查询操作
public static <T> T get(String sql, Class<T> clazz) {
T t = null;
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 1.加载配置文件
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
// 2.读取配置信息
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
// 3.加载驱动
Class.forName(driverClass);
// 4.获取连接
conn = DriverManager.getConnection(url, user, password);
st = conn.createStatement();
rs = st.executeQuery(sql);
// 获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
// 获取结果集的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
// 1. 获取列的别名
String columnName = rsmd.getColumnLabel(i + 1);
// 2. 根据列名获取对应数据表中的数据
Object columnVal = rs.getObject(columnName);
// 3. 将数据表中得到的数据,封装进对象
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return null;
}
}
当这个写好后,我们使用Statement对user表进行一次查询
public static void main(String[] args) {
//键盘录入用户信息
Scanner sc = new Scanner(System.in);
System.out.print("请输入你的用户名:");
String userName = sc.nextLine();
System.out.print("请输入你的密码:");
String password = sc.nextLine();
//编写SQL语句,Statement的第一个弊端,在这里可以看出它存在拼串操作,繁琐
String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password+ "'";
//get是上面写的方法
User user = get(sql, User.class);
if (user != null) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名或密码错误!");
}
}
现在我们测试一下代码:
ok,没有问题,但是,输入这个试试用户名(1’ or ),密码(=‘1’ or ‘1’ = '1)
嗯?这是怎么会登录成功?这就是SQL注入问题。
分析一波:
看看我们编写的sql语句:
当我们正常编写时,sql语句是:
SELECT USER
,PASSWORD
FROM user_table WHERE USER = 'AA ’ AND PASSWORD = ‘123456’ ;
它的查询条件是满足user= AA并且password=123456
而当我们输入上面出错的那个时我们的sql语句变成了
SELECT USER
,PASSWORD
FROM user_table WHERE USER = ’ 1 ’ OR ’ AND PASSWORD = ’ = ‘1’ OR ‘1’ = ‘1’ ;
我们的查询条件变成了user = 1 或 =AND PASSWORD = 或 ‘1’ = ‘1’ ,而 ‘1’ = ‘1’ 是恒成立的就是true所以无论如何都会登录成功。
这就是我们说的SQL注入问题,这是致命的,而在PrepareStatement中我们会使用?占位符来对我们需要输入的位置进行占位,就杜绝了这种情况。当然,PrepareStatement与Statement相比优势并不止这一点。