使用Statement操作数据表一些的弊端:
问题一:存在拼串操作,繁琐
问题二:存在安全问题,SQL注入问题 (主要问题)
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user=‘a’ OR 1 = ’ AND password = ’ OR ‘1’ = ‘1’)
代码演示如下:`
public class StatementTest {
// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
@Test
public void testLogin() {
Scanner scan = new Scanner(System.in);
System.out.print("用户名:");
String userName = scan.nextLine();
System.out.print("密 码:");
String password = scan.nextLine();
//String sql SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1';
String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password + "'";
User user = get(sql, User.class);
if (user != null) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名或密码错误!");
}
}
// 使用Statement实现对数据表的查询操作
public <T> T get(String sql, Class<T> clazz) {
T t = null;
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 1.加载配置文件
InputStream is = StatementTest.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.获取连接
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.getColumnName(i+1);
// 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;
}
}
当使用:String sql = “SELECT user,password FROM user_table WHERE USER = '” + userName + “’ AND PASSWORD = '” + password + “’”;使用拼串的方式查询数据库,并返回结果如下图:
显示为查询成功。
但是使用:String sql SELECT user,password FROM user_table WHERE USER = ‘1’ or ’ AND PASSWORD = '=‘1’ or ‘1’ = ‘1’;返回结果如下图:
依然显示查询成功。这就是Statement操作数据表出现的SQL注入问题
把SQL语句输入到MySQL数据库,String sql SELECT user,password FROM user_table WHERE USER = ‘1’ or ’ AND PASSWORD = '=‘1’ or ‘1’ = '1’就会变成如上图所示。这条语句在语法上是恒成立的,所以依然可以查询成功
使用PreparedStatement替换Statement,解决SQL注入问题
代码如下:
public class PreparedStatementTest {
@Test
public void testLogin() {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String user = scanner.nextLine();
System.out.print("请输入密码:");
String password = scanner.nextLine();
//SELECT user,password FROM user_table WHERE user = '1' or ' AND password = '=1 or '1' = '1'
String sql = "SELECT user,password FROM user_table WHERE user = ? and password = ?";
User returnUser = getInstance(User.class,sql,user,password);
if(returnUser != null){
System.out.println("登录成功");
}else{
System.out.println("用户名不存在或密码错误");
}
}
/**
*
* @Description 针对于不同的表的通用的查询操作,返回表中的一条记录
* @author shkstart
* @date 上午11:42:23
* @param clazz
* @param sql
* @param args
* @return
*/
public <T> T getInstance(Class<T> clazz,String sql, Object... args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
// 获取结果集的元数据 :ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 通过ResultSetMetaData获取结果集中的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columValue = rs.getObject(i + 1);
// 获取每个列的列名
// String columnName = rsmd.getColumnName(i + 1);
String columnLabel = rsmd.getColumnLabel(i + 1);
// 给t对象指定的columnName属性,赋值为columValue:通过反射
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
}
当使用:String sql = “SELECT user,password FROM user_table WHERE user = ? and password = ?”;使用PreparedStatement通过填充占位符的方式输入SQL语句结果如下:
显然,当输入与Statement相同的SQL语句时候,并没有出现SQL注入这一安全性问题。
为什么PreparedStatement不会出现SQL注入问题而Statement会呢?
1、PreparedStatement是Statement的子接口,它表示一条预编译过SQL的语句
2、PreparedStatement 对象所代表的 SQL 语句中的参数用问号“?”(占位符)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数(即填充占位符). setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值
使用PreparedStatement对象的SQL语句:String sql = “SELECT user,password FROM user_table WHERE user = ? and password = ?”。因为我们是先使用带有占位符的SQL语句,这里的SQL语句在我们执行:ps.setObject(i + 1, args[i]);填充语句去填充占位符之前的时候,已经经过预编译。所以在我们填充占位符进去的时候,填充到占位符的就是2个数据而已,不会改变经过预编译语句的逻辑关系,因此不会出现SQL注入问题
`