1 JDBC实现登录案例
目标
模拟用户输入账号和密码登录网站
- 输入正确的账号,密码,显示登录成功
- 输入错误的账号,密码,显示登录失败
讲解
案例分析
- 使用数据库保存用户的账号和密码
- 使用SQL根据用户的账号和密码去数据库查询数据
- 如果查询到数据,说明登录成功
- 如果查询不到数据,说明登录失败
实现步骤
-- 创建数据库
create database day04_db;
-- 切换数据库
use day04_db;
-- 用户表
create table user (
id int primary key auto_increment,
username varchar(30) unique not null,
password varchar(30)
);
insert into user(username, password) values('zhangsan','123');
insert into user(username, password) values('lisi','123');
insert into user(username, password) values('wangwu','123');
select * from user;
1.使用SQL根据用户的账号和密码去数据库查询数据.
package com.itheima.sh.a_jdbc_01;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
public class JDBCTest04 {
public static void main(String[] args) throws Exception {
/*
练习:模拟登录,如果登录成功,提示登录成功,登录失败,提示失败
*/
//1.创建键盘录入对象
Scanner sc = new Scanner(System.in);
//2.提示输入用户名和密码
System.out.println("-------请输入用户名:-------");
//获取用户名
String inputUsername = sc.nextLine();
System.out.println("-------请输入密码:-------");
//获取密码
String inputPassword = sc.nextLine();
//3.获取数据
//4.注册驱动
//5.获取和数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day03_heima139", "root", "1234");
//6.获取发送sql语句的对象
Statement st = conn.createStatement();
//7.发送sql语句
/*
错误的完整的sql语句:select * from user3 where username=zhangsanand password=123
正确的完整的sql语句:select * from user3 where username='zhangsan' and password='123'
*/
ResultSet rs = st.executeQuery("select * from user3 where username='" + inputUsername + "' and password='" + inputPassword+"'");
//8.处理结果集
//用户名唯一,查询的是一条数据,所以这里使用if即可
if(rs.next()){
//rs.next() :如果当前指针指向的行有数据则返回true
//获取用户名
String username = rs.getString("username");
//输出
System.out.println("恭喜您,亲,登录成功,欢迎光临我的小店,你的用户名是"+username);
}else{
System.out.println("用户名或者密码错误");
}
//9.释放资源
rs.close();
st.close();
conn.close();
}
}
小结
登录案例步骤
- 使用数据库保存用户的账号和密码
- 让用户输入账号和密码
- 使用SQL根据用户的账号和密码去数据库查询数据
- 如果查询到数据,说明登录成功
- 如果查询不到数据,说明登录失败
2、PreparedSatement预编译对象
目标
能够理解什么是SQL注入
能够理解PreparedSatement的执行原理
讲解
SQL注入问题
sql注入:由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL 关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。
简单来说就是:用户在页面提交数据的时候人为的添加一些特殊字符,使得sql语句的结构发生了变化,最终可以在没有用户名或者密码的情况下进行登录。
案例:模拟登陆。
sql注入原因演示_模拟登陆:
sql注入代码演示:
需求: 根据用户名和密码 查询用户信息(只知道用户名,不知道密码)。
恶意注入方式:在sql语句中添加 – 是mysql的注释。
用户名username输入 zhangsan’ 空格–空格 ,密码password 随意。
select * from user where username =‘zhangsan’ – ’ and password =‘kajajha’‘’ ;
对上述sql语句进行说明:
– ’ and password =‘kajajha’‘’ ; – 表示注释的意思,这样就会将密码都给注释掉了,就相当于只根据用户名zhangsan来查询了。
注意:以上的 zhangsan’ 空格–空格 中的用户名zhangsan是数据库存在的。
package com.itheima.sh.a_jdbc_01;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
public class JDBCTest05 {
public static void main(String[] args) throws Exception {
/*
sql注入问题:在对于sql语句使用变量的部分输入一些特殊的符号导致输入的数据不正确也可以完成查询数据
*/
//1.创建键盘录入对象
Scanner sc = new Scanner(System.in);
//2.提示输入用户名和密码
System.out.println("-------请输入用户名:-------");
//获取用户名
String inputUsername = sc.nextLine();
System.out.println("-------请输入密码:-------");
//获取密码
String inputPassword = sc.nextLine();
//3.获取数据
//4.注册驱动
//5.获取和数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day03_heima139", "root", "1234");
//6.获取发送sql语句的对象
Statement st = conn.createStatement();
//7.发送sql语句
/*
sql注入:
正确的完整的sql语句:select * from user3 where username='zhangsan' -- ' and password='i182928'
说明:上述sql语句在用户输入一些特殊符号(单引号 空格 --等)导致 -- 后面额内容被注释了,结果是之后执行sql:select * from user3 where username='zhangsan'
其实就是根据用户名查询数据了,即使输入密码,由于被注释了也不会根据密码查询了
*/
ResultSet rs = st.executeQuery("select * from user3 where username='" + inputUsername + "' and password='" + inputPassword+"'");
//8.处理结果集
//用户名唯一,查询的是一条数据,所以这里使用if即可
if(rs.next()){
//rs.next() :如果当前指针指向的行有数据则返回true
//获取用户名
String username = rs.getString("username");
//输出
System.out.println("恭喜您,亲,登录成功,欢迎光临我的小店,你的用户名是"+username);
}else{
System.out.println("用户名或者密码错误");
}
//9.释放资源
rs.close();
st.close();
conn.close();
}
}
小结
问题根本原因:
之所以有sql注入的问题,无非是在参数中设置了一些特殊字符,使sql语句在拼接这些参数的时候因为特殊字符的原因改变了sql语句原来的规则。
问题的解决方案:
使用PreparedStatement 解决SQL注入问题,运行在SQL中参数以 ? 占位符的方式表示。
3、PreparedStatement解决SQL注入方案
目标
掌握PreparedStatement是如何解决SQL注入问题的
讲解
1.获取PreparedStatement对象
PreparedStatement是Statement的子接口,可以防止sql注入问题。可以通过Connection接口中的prepareStatement(sql)方法获得PreparedStatement的对象。
方法如下所示:
PreparedStatement prepareStatement(String sql) 创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库。
注意:sql提前创建好的。sql语句中需要参数。使用?进行占位。
举例:
select *from user where username=‘zhangsan’ and password = ‘123456’;
使用?进行占位
select *from user where username=? and password = ?;
String sql=”select *from user where username=? and password = ?”;
步骤一:PreparedStatement pstmt = conn.prepareStatement(sql); -----需要你事先传递sql。如果sql需要参数,使用?进行占位。
步骤二:设置参数(执行sql之前):pstmt.setXXX(int index, 要放入的值) -----根据不同类型的数据进行方法的选择。第一个参数index表示的是?出现的位置。从1开始计数,有几个问号,就需要传递几个参数。
方法的参数说明:
第一个参数:int index ;表示的是问号出现的位置。 问号是从1开始计数
第二个参数:给问号的位置传入的值。
步骤三、执行,不需要在传递sql了。
pstmt.executeQuery();—执行select
pstmt.executeUpdate();—执行insert,delete,update
小结:
1.使用预编译接口PreparedStatement 好处:
1.解决sql注入问题
2.提供效率,对sql语句只会预编译一次
2.使用编译接口PreparedStatement步骤:
1)使用连接对象调用方法获取预编译接口对象:PreparedStatement pstmt = conn.prepareStatement(sql);
2)给sql语句占位符赋值:pstmt.setXxx(第几个占位符,实际值)
3)运行sql语句:
pstmt.executeQuery();---执行select
pstmt.executeUpdate();---执行insert,delete,update
4、PreparedStatement的 应用(掌握)
目标
能够掌握PreparedSatement实现查询和添加数据
讲解
1、需求: 根据用户名和密码查询用户信息。
代码如下所示:
说明:导包必须都得使用java.sql包下的。
package com.itheima.sh.a_jdbc_01;
import java.sql.*;
import java.util.Scanner;
public class JDBCTest06 {
public static void main(String[] args) throws Exception {
/*
使用PreparedStatement接口解决sql注入问题
1.使用Connection接口对象调用方法,获取PreparedStatement预编译接口
PreparedStatement prepareStatement(String sql)
2.使用预编译接口 PreparedStatement的对象调用PreparedStatement接口中的方法给sql语句中的占位符赋值
setXxx(第几个占位符,实际值)
3.使用预编译接口 PreparedStatement对象调用PreparedStatement接口中的方法执行sql
ResultSet executeQuery() 执行DQL(查询语句)
int executeUpdate() 执行DML(增删改语句)
*/
//1.创建键盘录入对象
Scanner sc = new Scanner(System.in);
//2.提示输入用户名和密码
System.out.println("-------请输入用户名:-------");
//获取用户名
String inputUsername = sc.nextLine();
System.out.println("-------请输入密码:-------");
//获取密码
String inputPassword = sc.nextLine();
//3.获取数据
//4.注册驱动
//5.获取和数据库连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day03_heima139", "root", "1234");
//6.使用Connection接口对象调用方法,获取PreparedStatement预编译接口
// PreparedStatement prepareStatement(String sql)
String sql = "select * from user3 where username=? and password=?";
PreparedStatement pst = conn.prepareStatement(sql);
//2.使用预编译接口 PreparedStatement的对象调用PreparedStatement接口中的方法给sql语句中的占位符赋值
//setXxx(第几个占位符,实际值)
//第一个参数1表示上述sql语句中的第一个占位符(?)位置
//inputUsername :表示给第一个占位符赋的实际值
// zhangsan\'\ \-\-
pst.setString(1,inputUsername);
//第一个参数2表示上述sql语句中的第二个占位符(?)位置
//inputPassword :表示给第二个占位符赋的实际值
pst.setString(2,inputPassword);
/*
3.使用预编译接口 PreparedStatement对象调用PreparedStatement接口中的方法执行sql
ResultSet executeQuery() 执行DQL(查询语句)
*/
ResultSet rs = pst.executeQuery();
//8.处理结果集
//用户名唯一,查询的是一条数据,所以这里使用if即可
if(rs.next()){
//rs.next() :如果当前指针指向的行有数据则返回true
//获取用户名
String username = rs.getString("username");
//输出
System.out.println("恭喜您,亲,登录成功,欢迎光临我的小店,你的用户名是"+username);
}else{
System.out.println("用户名或者密码错误");
}
//9.释放资源
rs.close();
pst.close();
conn.close();
}
}
小结:
上述如何解决sql注入的问题呢?
在方法setXxx()内部解决的。例如上述 st.setString(1,inputUsername); ,将输入的用户名 "zhangsan’ – "传入给setString方法体中,在该方法体中使用转义符号 / ,将特殊符号给转义了,转义之后再发送给mysql服务器,那么特殊符号例如–就不是注释的意思就是普通字符,mysql会认为用户名的值是:zhangsan’ –
2、 需求:使用预编译对象执行删除操作
package com.itheima.sh.a_jdbc_01;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class JDBCTest07 {
public static void main(String[] args) throws Exception {
/*
需求:使用预编译对象执行删除操作
*/
//1.注册驱动
//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day03_heima139", "root", "1234");
//3.获取发送sql语句的预编译对象
PreparedStatement pst = conn.prepareStatement("delete from user3 where id=?");
//4.发送sql语句
//给占位符赋值
pst.setInt(1, 4);//第一个参数1表示上述sql语句的占位符位置是第一个,第二个参数4表示给id的实际值
int i = pst.executeUpdate();
//5.处理结果集
System.out.println("i = " + i);
//6.释放资源
pst.close();
conn.close();
}
}
5、执行DQL封装成集合的操作
需求:将user中的数据查询封装到实体类User对象中,然后将User对象放到list集合中,最后遍历list集合
在实际开发中我们使用三层架构进行开发,层与层之间会将传递的参数封装成实体对象,那么接下来我们来看一下dao(数据处理层)层是如何将查询出的结果封装成user对象的:
说明:封装到User类的对象中是因为我们要把查询的结果进行显示和其他的处理。
举例:
用户想在浏览器查询自己的个人信息,那么我们需要到数据库查询出来,然后把个人的全部信息封装到User类的对象中。然后在将User类的对象传递给前台,最后经过相关技术显示到页面浏览器中,达到显示个人信息的效果。
其实我们生活中到淘宝网站购买商品,查看某个商品信息也是这样做的,将数据库中有关商品信息都先全部封装到Product类的对象中。最后显示到页面中。
按照如下操作,书写代码:
新建一个User类,具体属性如下所示:
在DAO层使用JDBC将查询的数据封装到User类的对象中的代码,如下所示:
@Test
public void show() {
// 初始化值
Connection conn = null;
ResultSet rs = null;
PreparedStatement pst = null;
try {
conn = JDBCUtils.getConnection();
String sql = "select * from user";
// 获取发送sql的对象
pst = conn.prepareStatement(sql);
// 执行sql
rs = pst.executeQuery();
//定义集合将遍历结果集封装到List集合中
List<User> list=new ArrayList<User>();
// 处理结果
while (rs.next()) {
//由于数据库中有多行,所以需要多个User类的对象
User u = new User();
u.setId(rs.getInt("id"));
u.setName(rs.getString("name"));
u.setCity(rs.getString("city"));
// 将对象添加到集合中
list.add(u);
}
System.out.println(list.size());//3
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.release(rs, pst, conn);// 不要关闭连接,每次使用完之后,把连接还给连接池
}
}
说明:
上述将从数据库中查询的数据封装到User类的对象中,相对来说比较复杂。我们这里用户属性相对比较少,如果属性多的话会更加复杂。所以,我们完全可以使用更为简单的方式来对数据进行封装到User类的对象中。可以使用后面学习的mybatis进行封装。