一、项目准备阶段:
- 需求:
模拟用户登录功能的实现。 - 业务描述:
程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码。用户输入用户名和密码后,提交信息,java程序收集到用户信息,java程序连接数据库验证用户名密码是否合法,合法显示登录成功,不合法显示登陆失败。 - 数据准备:
使用PowerDesigner数据库建模工具设计数据库表。
二、项目代码实现:
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Test{
public static void main(String[] args) {
//用户在控制台输入信息
Map<String,String> userLoginInfo = initUI();
//验证用户名和密码
boolean loginSuccess = login(userLoginInfo);
//最后输出结果
System.out.println(loginSuccess ? "登录成功" : "登陆失败");
}
/**
*
* @param userLoginInfo 用户登录信息
* @return false表示失败,true表示成功
*/
private static boolean login(Map<String, String> userLoginInfo) {
boolean loginSuccess = false;
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
connection = DriverManager.getConnection("你自己的url","你自己的用户名","你自己的密码");
//3.获取数据库操作对象
statement = connection.createStatement();
//4.执行sql
String sql = "select * from t_user where username='"+userLoginInfo.get("username")+"' and password='"+userLoginInfo.get("password")+"' ";
resultSet = statement.executeQuery(sql);
//5.处理结果集
if (resultSet.next()){
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//6.释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
return loginSuccess;
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码等登录信息
*/
private static Map<String, String> initUI() {
Map<String,String> userLoginInfo = new HashMap();
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
userLoginInfo.put("username",username);
userLoginInfo.put("password",password);
return userLoginInfo;
}
}
三、SQL注入现象:
- 程序存在的问题:
输入如下用户名密码登陆成功:
这种现象被称为SQL注入。 - 导致sql注入的根本原因:
用户输入信息中含有sql关键字,并且这些关键字参与sql语句编译,导致sql语句原来的意思被改变,导致sql注入。
上述用户名和密码会将sql语句变成:
select * from t_user where username='info' and password='info' or '1'='1'
这串代码’1’=‘1’ 在数据库中的执行结果会返回数据库中所有用户信息。
3. 解决方法:
- 只要用户提供的信息不参与sql语句的编译,即使用户的信息中含有sql语句的关键字,但是不参与编译,问题就解决了。
- 要想用户信息不参与sql语句的编译,name必须使用java.sql.PreparedStatement。
- PreparedStatement输入预编译数据库操作对象。
- PreparedStatement的原理是与西安呢对sql语句的框架进行编译,然后再给sql语句传“值”。
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Test{
public static void main(String[] args) {
//用户在控制台输入信息
Map<String,String> userLoginInfo = initUI();
//验证用户名和密码
boolean loginSuccess = login(userLoginInfo);
//最后输出结果
System.out.println(loginSuccess ? "登录成功" : "登陆失败");
}
/**
*
* @param userLoginInfo 用户登录信息
* @return false表示失败,true表示成功
*/
private static boolean login(Map<String, String> userLoginInfo) {
boolean loginSuccess = false;
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
connection = DriverManager.getConnection("***","***","***");
//3.获取预编译的数据库操作对象
//一个?表示一个占位符,一个?将来接收一个值,?不能用单引号括起来。
preparedStatement = connection.prepareStatement("select * from t_user where username= ? and password= ?");
//占位符传值,第一个?下标是1
//setString方法在传值时会在sql语句中自动加一个‘’单引号,而setInt则不会加单引号
preparedStatement.setString(1,userLoginInfo.get("username"));
preparedStatement.setString(2,userLoginInfo.get("password"));
//4.执行sql
resultSet = preparedStatement.executeQuery();
//5.处理结果集
if (resultSet.next()){
loginSuccess = true;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//6.释放资源
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (preparedStatement != null) {
try {
preparedStatement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
return loginSuccess;
}
/**
* 初始化用户界面
* @return 用户输入的用户名和密码等登录信息
*/
private static Map<String, String> initUI() {
Map<String,String> userLoginInfo = new HashMap();
Scanner sc = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
userLoginInfo.put("username",username);
userLoginInfo.put("password",password);
return userLoginInfo;
}
}
四、PreparedStatement和Statement区别:
注意!!!!!不是因为Statement有SQL注入问题就要被淘汰!!!Statement能够完成PreparedStatement不能完成的任务,所以两种方法处于相同地位!!!!
- Statement存在sql注入问题,PreparedStatement不存在sql注入问题。
- Statement编译一次执行一次。PreparedStatement对于相同的sql语句编译一次可执行多次,效率高。
- PreparedStatement在编译阶段会做类型安全检查。
- 以后90%情况都是用PreparedStatement。但业务方面要求对sql语句拼接的必须用Statement(如对数据升序降序时ASC和DESC不能用PreparedStatement,因为setString方法传值时会自动给ASC和DESC添加单引号’',只能用Statment拼接),如果单纯给sql语句传值,可以用PreparedStatement。
- 用法辨析:
(1)PreparedStatement:传值
String sql = "select * from ?";
(2)Statement:拼接
String s = "table";
String sql = "select * from"+s;
五、PreparedStatement完成sql增删改:
port java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Test {
public static void main(String[] args) {
Connection connection = null;
PreparedStatement preparedstatement = null;
try {
//1.注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接
String url = "jdbc:mysql://xxx.xxx.xxx.xxx:aaaa/bbb";
/*
*协议:jdbc:mysql://
*目的IP地址:xxx.xxx.xxx.xxx
*目的端口号:aaaa
*数据库名:bbb
* */
String username = "***";
String password = "***";
connection = DriverManager.getConnection(url,username,password);
//3.获取数据库操作对象
preparedstatement = connection.prepareStatement("insert into vip(Cname,Tel,Mtype,Tcount,Ctime) values (?,?,?,?,?)");
preparedstatement.setString(1,"吴尊");
preparedstatement.setString(2,"199");
preparedstatement.setString(3,"银卡");
preparedstatement.setInt(4,1);
preparedstatement.setInt(5,1);
//4.执行sql
int count = preparedstatement.executeUpdate();//专门执行DML语句的,返回值是影响数据库中的记录条数
} catch (Exception throwables) {
throwables.printStackTrace();
}finally {
//6.释放资源
/*
* 关闭时遵循从小到大原则,分别try..catch,否则出现异常后面资源无法关闭
* 判断是否为空,不为空才关闭
* */
if (preparedstatement != null) {
try {
preparedstatement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}