上期我们使用jdbc完成基本的增删改查操作,但是也留下了一系列问题。尤其是使用statement语句在编写实现数据库操作的时候,最大的特点就是不安全。本期将用Preparestatement语句解决sql注入安全问题
原jdbcUtil代码展示:
package utils;
import com.hp.pojo.Dept;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import com.hp.pojo.*;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import org.junit.Test;
public class JdbcUtil{
@Test
public void testRow(){
Dept dept=selectRow("select * from t_Dept where did=1",Dept.class);
}
@Test
public void testColumn(){
Integer list=selectColumn("select did from t_Dept",Integer.class);
System.out.println(list);
}
@Test
public void testOne(){
Double sumSalary= selectOne("select sum(salary) from t_emps",double.class);
System.out.println(sumSalary);
}
@Test
public void testUpdate(){
int j=update("insert into t_dept values(null,'a','b','c')");
System.out.println(j);
}
@Test//测试登录
public void testLogin(){
String username="cc ' or '1'='1";
String password="cc ' or '1'='1";
User user=selectRow("select * from t_users where username '"+username+"'and password ='"+password+"'" ,User.class);
System.out.println(user!=null?"登录成功":"登录失败");
}
//查询多行多列
public static <T> List<T> list(String sql,Class<T> c) {
//创建一个集合,存放所有的对象
List<T> tList = new ArrayList<>();
try {
//1.注册驱动-反射去加载jar包中com.mysql.jdbc.Driver这个类中的DriverManager.registerDriver(new Driver())
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection con= DriverManager.getConnection("jdbc:mysql://localhost:3306/day01?characterEncoding=utf-8","root","root");
System.out.println(con);
//定义sql
//4.需要创建statement
Statement statement = con.createStatement();
//5.statement执行sql,返回 结果集
ResultSet rs = statement.executeQuery(sql);
//结果集rs得到结果集元数据
ResultSetMetaData md=rs.getMetaData();
//获取结果集的总列数
int columnCount=md.getColumnCount();
//6.解析rs
while (rs.next()) {
T t=c.newInstance();
//1.取出某一行的每个数据,封装到对象t的每个属性
for(int i=1;i<=columnCount;i++ ){
//通过列的序号,获取每一列的值
Object value=rs.getObject(i);
if(value!=null){
//通过列的序号,获取每一列的列名
String columnName=md.getColumnName(i);
//因为列名和实体类t中的属性名一致,为每一个属性构造一个反射中的set方法
Field f=c.getDeclaredField(columnName);
//使用反射,把value给到对象t的属性中
f.set(t,value);//理解为:把value赋值给对象t的columnName属性,相当于set方法
}
}
tList.add(t);
}
//7.关闭资源
statement.close();
con.close();
}catch (Exception e){
e.printStackTrace();
}
return tList;
}
//查询一行
public static<T> T selectRow(String sql,Class<T> c) {
try {
//1.注册驱动-反射去加载jar包中com.mysql.jdbc.Driver这个类中的DriverManager.registerDriver(new Driver())
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/day01?characterEncoding=utf-8", "root", "root");
System.out.println(con);
//定义sql
//4.需要创建statement
Statement statement = con.createStatement();
//5.statement执行sql,返回 结果集
ResultSet rs = statement.executeQuery(sql);
//结果集rs得到结果集元数据
ResultSetMetaData md = rs.getMetaData();
//获取结果集的总列数
int columnCount = md.getColumnCount();
//6.解析rs
T t = null;
if (rs.next()) {
t=c.newInstance();
//1.取出某一行的每个数据,封装到对象t的每个属性
for (int i = 1; i <= columnCount; i++) {
//通过列的序号,获取每一列的值
Object value = rs.getObject(i);
if (value != null) {
//通过列的序号,获取每一列的列名
String columnName = md.getColumnName(i);
//因为列名和实体类t中的属性名一致,为每一个属性构造一个反射中的set方法
Field f = c.getDeclaredField(columnName);
//使用反射,把value给到对象t的属性中
f.set(t, value);//理解为:把value赋值给对象t的columnName属性,相当于set方法
}
}
return t;
}
//7.关闭资源
statement.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//查询一列
public static<T> T selectColumn(String sql,Class<T> c) {
try {
//1.注册驱动-反射去加载jar包中com.mysql.jdbc.Driver这个类中的DriverManager.registerDriver(new Driver())
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/day01?characterEncoding=utf-8", "root", "root");
System.out.println(con);
//定义sql
//4.需要创建statement
Statement statement = con.createStatement();
//5.statement执行sql,返回 结果集
ResultSet rs = statement.executeQuery(sql);
//结果集rs得到结果集元数据
ResultSetMetaData md = rs.getMetaData();
//获取结果集的总列数
int columnCount = md.getColumnCount();
//6.解析rs
if (rs.next()) {
T t = c.newInstance();
//1.取出某一行的每个数据,封装到对象t的每个属性
for (int i = 1; i <= columnCount; i++) {
//通过列的序号,获取每一列的值
Object value = rs.getObject(i);
if (value != null) {
//通过列的序号,获取每一列的列名
String columnName = md.getColumnName(i);
//因为列名和实体类t中的属性名一致,为每一个属性构造一个反射中的set方法
Field f = c.getDeclaredField(columnName);
//使用反射,把value给到对象t的属性中
f.set(t, value);//理解为:把value赋值给对象t的columnName属性,相当于set方法
}
}
return t;
}
//7.关闭资源
statement.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//查询单个
public static <T> T selectOne(String sql, Class<T> c){
//创建一个集合,存放所有的对象
try {
//1.注册驱动-反射去加载jar包中com.mysql.jdbc.Driver这个类中的DriverManager.registerDriver(new Driver())
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection con= DriverManager.getConnection("jdbc:mysql://localhost:3306/day01?characterEncoding=utf-8","root","root");
System.out.println(con);
//定义sql
//4.需要创建statement
Statement statement = con.createStatement();
//5.statement执行sql,返回 结果集
ResultSet rs = statement.executeQuery(sql);
//结果集rs得到结果集元数据
ResultSetMetaData md=rs.getMetaData();
//获取结果集的总列数
int columnCount=md.getColumnCount();
//6.解析rs
T t;
if(rs.next()){
t= (T) rs.getObject(1);
}
//7.关闭资源
statement.close();
con.close();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
//增删改
public static int update(String sql) {
try {
//1.注册驱动-反射去加载jar包中com.mysql.jdbc.Driver这个类中的DriverManager.registerDriver(new Driver())
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection con= DriverManager.getConnection("jdbc:mysql://localhost:3306/day01?characterEncoding=utf-8","root","root");
System.out.println(con);
//定义sql
//4.需要创建statement
Statement statement = con.createStatement();
//5.statement执行sql,返回 结果集
int i= statement.executeUpdate(sql);
//7.关闭资源
statement.close();
con.close();
return i;
}catch (Exception e){
e.printStackTrace();
}
return 0;
}
}
但是使用statement语句编写存在弊端。原因在于sql语句无法被识别出非法错误片段,从而有人可能会恶意注入非法信息或者命令。解决此类问题,我们将用到preparestatement语句来替代statement语句
什么是Preparestatement语句
PrepareStatement是一种数据库编程的概念,它是在执行SQL语句之前预编译的语句。它可以提高数据库操作的性能和安全性。 使用PrepareStatement,我们可以将SQL语句预先编译并存储在数据库中,然后在需要执行的时候,只需传入参数并执行。这样可以避免每次执行SQL语句时都需要进行解析和优化的开销。 PrepareStatement还可以防止SQL注入攻击,因为它使用参数化查询,将输入的参数和SQL语句进行分离,从而避免恶意输入破坏SQL语句的结构。
修改示例
我们需要使用object...params形参可变数组。在调用函数时,我们可以传入任意个任意类型的参数。在执行多行多列查询时,使用循环输出数据
代码如下:
PreparedStatement statement = con.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
statement.setObject(i+1,params[i]);
}
查询多行多列总代码如下:
//查询多行多列
public static <T> List<T> list(String sql, Class<T> c,Object...params) {
//创建一个集合,存放所有的对象
List<T> tList = new ArrayList<>();
try {
//1.注册驱动-反射去加载jar包中com.mysql.jdbc.Driver这个类中的DriverManager.registerDriver(new Driver())
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection con= DriverManager.getConnection("jdbc:mysql://localhost:3306/day01?characterEncoding=utf-8","root","root");
System.out.println(con);
//定义sql
//4.需要创建statement
PreparedStatement statement = con.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
statement.setObject(i+1,params[i]);
}
//5.statement执行sql,返回 结果集
ResultSet rs = statement.executeQuery();
//结果集rs得到结果集元数据
ResultSetMetaData md=rs.getMetaData();
//获取结果集的总列数
int columnCount=md.getColumnCount();
//6.解析rs
while (rs.next()) {
T t=c.newInstance();
//1.取出某一行的每个数据,封装到对象t的每个属性
for(int i=1;i<=columnCount;i++ ){
//通过列的序号,获取每一列的值
Object value=rs.getObject(i);
if(value!=null){
//通过列的序号,获取每一列的列名
String columnName=md.getColumnName(i);
//因为列名和实体类t中的属性名一致,为每一个属性构造一个反射中的set方法
Field f=c.getDeclaredField(columnName);
//使用反射,把value给到对象t的属性中
f.set(t,value);//理解为:把value赋值给对象t的columnName属性,相当于set方法
}
}
tList.add(t);
}
//7.关闭资源
statement.close();
con.close();
}catch (Exception e){
e.printStackTrace();
}
return tList;
}
修改后总代码:
package utils;
import com.hp.pojo.Dept;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import com.hp.pojo.*;
import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;
import org.junit.Test;
//jdbc工具类升级版
/*
* Statement 父类
* PreparedStatement 子类
* 1.提前传入sql,执行的时候,不传入sql
* 2.支持传入sql中的参数
* 3.解决sql注入的逻辑漏洞
* 4.提高执行效率
* Object...params可变形参数组
* 在调用函数时,可以传入任意个任意类型的参数
* */
public class JdbcUtilPlus{
@Test
public void testRow(){
Dept dept=selectRow("select * from t_Dept where did=1",Dept.class);
}
@Test
public void testColumn(){
Integer list=selectColumn("select did from t_Dept",Integer.class);
System.out.println(list);
}
@Test
public void testOne(){
Double sumSalary= selectOne("select sum(salary) from t_emps",double.class);
System.out.println(sumSalary);
}
@Test
public void testUpdate(){
int j=update("insert into t_dept values(null,'a','b','c')");
System.out.println(j);
}
@Test//测试登录
public void testLogin(){
String username="cc ' or '1'='1";
String password="cc ' or '1'='1";
User user=selectRow("select * from t_users where username=? and password =?" ,User.class,username,password);
System.out.println(user!=null?"登录成功":"登录失败");
}
//查询多行多列
public static <T> List<T> list(String sql, Class<T> c,Object...params) {
//创建一个集合,存放所有的对象
List<T> tList = new ArrayList<>();
try {
//1.注册驱动-反射去加载jar包中com.mysql.jdbc.Driver这个类中的DriverManager.registerDriver(new Driver())
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection con= DriverManager.getConnection("jdbc:mysql://localhost:3306/day01?characterEncoding=utf-8","root","root");
System.out.println(con);
//定义sql
//4.需要创建statement
PreparedStatement statement = con.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
statement.setObject(i+1,params[i]);
}
//5.statement执行sql,返回 结果集
ResultSet rs = statement.executeQuery();
//结果集rs得到结果集元数据
ResultSetMetaData md=rs.getMetaData();
//获取结果集的总列数
int columnCount=md.getColumnCount();
//6.解析rs
while (rs.next()) {
T t=c.newInstance();
//1.取出某一行的每个数据,封装到对象t的每个属性
for(int i=1;i<=columnCount;i++ ){
//通过列的序号,获取每一列的值
Object value=rs.getObject(i);
if(value!=null){
//通过列的序号,获取每一列的列名
String columnName=md.getColumnName(i);
//因为列名和实体类t中的属性名一致,为每一个属性构造一个反射中的set方法
Field f=c.getDeclaredField(columnName);
//使用反射,把value给到对象t的属性中
f.set(t,value);//理解为:把value赋值给对象t的columnName属性,相当于set方法
}
}
tList.add(t);
}
//7.关闭资源
statement.close();
con.close();
}catch (Exception e){
e.printStackTrace();
}
return tList;
}
//查询一行
public static<T> T selectRow(String sql,Class<T> c,Object...params) {
try {
//1.注册驱动-反射去加载jar包中com.mysql.jdbc.Driver这个类中的DriverManager.registerDriver(new Driver())
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/day01?characterEncoding=utf-8", "root", "root");
System.out.println(con);
//定义sql
//4.需要创建statement
PreparedStatement preState = con.prepareStatement(sql);
//在执行前,给sql传递参数
for(int i=0;i<params.length;i++){
preState.setObject(i+1,params[i]);
}
//5.statement执行sql,返回 结果集
ResultSet rs = preState.executeQuery();
//结果集rs得到结果集元数据
ResultSetMetaData md = rs.getMetaData();
//获取结果集的总列数
int columnCount = md.getColumnCount();
//6.解析rs
T t = null;
if (rs.next()) {
t=c.newInstance();
//1.取出某一行的每个数据,封装到对象t的每个属性
for (int i = 1; i <= columnCount; i++) {
//通过列的序号,获取每一列的值
Object value = rs.getObject(i);
if (value != null) {
//通过列的序号,获取每一列的列名
String columnName = md.getColumnName(i);
//因为列名和实体类t中的属性名一致,为每一个属性构造一个反射中的set方法
Field f = c.getDeclaredField(columnName);
//使用反射,把value给到对象t的属性中
f.set(t, value);//理解为:把value赋值给对象t的columnName属性,相当于set方法
}
}
return t;
}
//7.关闭资源
preState.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//查询一列
public static<T> T selectColumn(String sql,Class<T> c,Object...params) {
try {
//1.注册驱动-反射去加载jar包中com.mysql.jdbc.Driver这个类中的DriverManager.registerDriver(new Driver())
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/day01?characterEncoding=utf-8", "root", "root");
System.out.println(con);
//3.定义sql
//4.需要创建statement
PreparedStatement statement = con.prepareStatement(sql);
//在执行前,给sql传递参数
for(int i=0;i<params.length;i++){
statement.setObject(i+1,params[i]);
}
//5.statement执行sql,返回 结果集
ResultSet rs = statement.executeQuery();
//结果集rs得到结果集元数据
ResultSetMetaData md = rs.getMetaData();
//获取结果集的总列数
int columnCount = md.getColumnCount();
//6.解析rs
if (rs.next()) {
T t = c.newInstance();
//1.取出某一行的每个数据,封装到对象t的每个属性
for (int i = 1; i <= columnCount; i++) {
//通过列的序号,获取每一列的值
Object value = rs.getObject(i);
if (value != null) {
//通过列的序号,获取每一列的列名
String columnName = md.getColumnName(i);
//因为列名和实体类t中的属性名一致,为每一个属性构造一个反射中的set方法
Field f = c.getDeclaredField(columnName);
//使用反射,把value给到对象t的属性中
f.set(t, value);//理解为:把value赋值给对象t的columnName属性,相当于set方法
}
}
return t;
}
//7.关闭资源
statement.close();
con.close();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//查询单个
public static <T> T selectOne(String sql, Class<T> c,Object...params){
//创建一个集合,存放所有的对象
try {
//1.注册驱动-反射去加载jar包中com.mysql.jdbc.Driver这个类中的DriverManager.registerDriver(new Driver())
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection con= DriverManager.getConnection("jdbc:mysql://localhost:3306/day01?characterEncoding=utf-8","root","root");
System.out.println(con);
//定义sql
//4.需要创建statement
PreparedStatement statement = con.prepareStatement(sql);
//在执行前,给sql传递参数
for(int i=0;i<params.length;i++){
statement.setObject(i+1,params[i]);
}
//5.statement执行sql,返回 结果集
ResultSet rs = statement.executeQuery();
//结果集rs得到结果集元数据
ResultSetMetaData md=rs.getMetaData();
//获取结果集的总列数
int columnCount=md.getColumnCount();
//6.解析rs
T t;
if(rs.next()){
t= (T) rs.getObject(1);
}
//7.关闭资源
statement.close();
con.close();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
//增删改
public static int update(String sql,Object...params) {
try {
//1.注册驱动-反射去加载jar包中com.mysql.jdbc.Driver这个类中的DriverManager.registerDriver(new Driver())
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接对象
Connection con= DriverManager.getConnection("jdbc:mysql://localhost:3306/day01?characterEncoding=utf-8","root","root");
System.out.println(con);
//定义sql
//4.需要创建statement
PreparedStatement statement = con.prepareStatement(sql);
//在执行前,给sql传递参数
for(int i=0;i<params.length;i++){
statement.setObject(i+1,params[i]);
}
//5.statement执行sql,返回 结果集
int i= statement.executeUpdate(sql);
//7.关闭资源
statement.close();
con.close();
return i;
}catch (Exception e){
e.printStackTrace();
}
return 0;
}
}