**
创建一个用户类,然后模拟用户输入账号密码然后登陆这一操作。
**
public class User {
private String user;
private String password;
public User() {
}
public User(String user, String password) {
super();
this.user = user;
this.password = password;
}
@Override
public String toString() {
return "User [user=" + user + ", password=" + password + "]";
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
**
使用Statement模拟登陆
**
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.Scanner;
import org.junit.Test;
public class StatementTest {
// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
//如何避免出现sql注入:只要用 PreparedStatement(从Statement扩展而来) 取代 Statement
@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'
//此时加入用户名字输入:1' or 用户密码输入:=1 or '1' = '1
//此时也是查询结果也是正确的。这就是sql注入。
//拼接sql语句就忍了,sql注入是无法忍受的。sql注入将且变成了或,因此导致错误。
// 因此statement这种方式被淘汰。
String sql = "SELECT user,password FROM user_table WHERE user = 'user' AND password = 'password';
User returnUser = get(sql,User.class);
if(returnUser != 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;
}
}
**
使用PreparedStatement模拟登陆
**
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.Scanner;
import org.junit.Test;
import com.alibaba.util.JDBCUtils;
/**
*
* @Description 演示使用PreparedStatement替换Statement,解决SQL注入问题
* @author shkstart Email:shkstart@126.com
* @version
* @date 上午11:52:37
*
* 除了解决Statement的拼串、sql问题之外,PreparedStatement还有哪些好处呢?
* 1.PreparedStatement操作Blob的数据(视频,音乐),而Statement做不到。因为有流可以传数据。
* 2.PreparedStatement可以实现更高效的批量操作。
*
*/
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'
// 因为使用PreparedStatement,所以DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
//但是在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
//综上所述,PreparedStatement能解决了注入问题。
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;
}
}
**
具体说明
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String user = scanner.nextLine();
System.out.print("请输入密码:");
String password = scanner.nextLine();
String sql = "SELECT user,password FROM user_table WHERE user = 'user' AND password = 'password';
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 = ?”;
//有预编译,不会改变。
sql注入的本质就是逻辑关系的改变,之所以Preparedment能解决sql注入问题,是因为(主要是占位符的作用,有预编译不会改变逻辑关系):因为使用PreparedStatement,所以DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
但是在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
//综上所述,PreparedStatement能解决了注入问题。
- 除了解决Statement的拼串、sql问题之外,PreparedStatement还有哪些好处呢?1.PreparedStatement操作Blob的数据(视频,音乐),而Statement做不到。因为有流可以传数据。
2.PreparedStatement可以实现更高效的批量操作(比如一万次操作,效率会更高)。**