JDBC是真的啰里啰嗦啊-但是很重要!(上):https://developer.aliyun.com/article/1492409
7、根据别名查询处理结果集扩展
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class JDBC03Test { public static void main(String[] args) throws Exception { /* 需求:查询数据表user2所有数据输出到控制台 */ //2.获取连接 String url = "jdbc:mysql://localhost:3306/day03_heima138";//连接mysql服务器地址 String username = "root";//连接mysql服务器用户名 String password = "1234";//连接mysql服务器密码,这里输入你的密码 Connection conn = DriverManager.getConnection(url, username, password); //3.定义sql String sql = "select id,username,password pwd from user2"; //4.获取发送sql语句对象 Statement st = conn.createStatement(); //5.发送sql语句 /* 1.ResultSet 表示结果集,用来存放查询的结果,因为查询有可能是多行数据,所以使用集合存储 */ ResultSet rs = st.executeQuery(sql); //6.处理结果 while(rs.next()){ //获取数据 int id = rs.getInt("id"); String userName = rs.getString("username"); /* 对应处理结果集ResultSet中的getXxx(xxx 变量名): 1)根据第几列来获取字段值:例如password属于第三列,那么这里获取password值是:rs.getString(3); 2)根据字段名或者别名来获取字段值:如果sql语句某个字段有别名,那么只能书写别名,不能书写字段 sql语句:select id,username,password pwd from user2 获取密码值:rs.getString("pwd"); */ String pwd = rs.getString("pwd"); //输出 System.out.println(id+"---"+userName+"---"+pwd); } //7.释放资源 rs.close(); st.close(); conn.close(); } }
8、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根据用户的账号和密码去数据库查询数据.
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根据用户的账号和密码去数据库查询数据
- 如果查询到数据,说明登录成功
- 如果查询不到数据,说明登录失败
9、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是数据库存在的。
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中参数以 ? 占位符的方式表示。
10、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
11、PreparedStatement的 应用(掌握)
讲解
1、需求: 根据用户名和密码查询用户信息。
代码如下所示:
说明:导包必须都得使用java.sql包下的。
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、 需求:使用预编译对象执行删除操作
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(); } }
12、执行DQL封装成集合的操作
需求:将user中的数据查询封装到实体类User对象中,然后将User对象放到list集合中,最后遍历list集合
在实际开发中我们使用三层架构进行开发,层与层之间会将传递的参数封装成实体对象,那么接下来我们来看一下dao(数据处理层)层是如何将查询出的结果封装成user对象的:
说明:封装到User类的对象中是因为我们要把查询的结果进行显示和其他的处理。
举例:
用户想在浏览器查询自己的个人信息,那么我们需要到数据库查询出来,然后把个人的全部信息封装到User类的对象中。然后在将User类的对象传递给前台,最后经过相关技术显示到页面浏览器中,达到显示个人信息的效果。
其实我们生活中到淘宝网站购买商品,查看某个商品信息也是这样做的,将数据库中有关商品信息都先全部封装到Product类的对象中。最后显示到页面中。
按照如下操作,书写代码:
新建一个User类,具体属性如下所示:
public class User{ private int id; private String name; private String city; }
在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进行封装。
13、JDBC事务
讲解
之前我们是使用MySQL的命令来操作事务。接下来我们使用JDBC来操作银行转账的事务。
准备数据
# 创建一个表:账户表. create database day05_db; # 使用数据库 use day05_db; # 创建账号表 create table account( id int primary key auto_increment, name varchar(20), money double ); # 初始化数据 insert into account values (null,'a',1000); insert into account values (null,'b',1000);
JDBC操作事务
API介绍
Connection
接口中与事务有关的方法
void setAutoCommit(boolean autoCommit) throws SQLException; false:开启事务, true:关闭事务
void commit() throws SQLException; 提交事务
void rollback() throws SQLException; 回滚事务
说明:
注意:在jdbc事务操作中,事务的控制都是通过Connection对象完成的,当一个完整的业务操作前,我们首先使用conn.setAutoCommit(false)来开启事务。默认情况下是true的,表示关闭事务,那么一条sql语句就是一个事务,默认提交事务。如果设置为false,那么表示开启事务,所有的sql语句就会都在一个事务中。
当业务操作完成之后,如果整个操作没有问题,我们需要使用conn.commit()来提交事务。当然了,如果出现了异常,我们需要使用conn.rollback()撤销所有的操作,所以出现异常,需要进行事务的回滚。
使用步骤
- 注册驱动
- 获取连接
- 开启事务
- 获取到Statement
- 使用Statement执行SQL
- 提交或回滚事务
- 关闭资源
案例代码
如下是使用jdbc操作事务的转账案例代码。
需求:a转给b 100元。
分析:
a用户 money=money-100
b用户 money=money+100
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class JDBCTest08 { public static void main(String[] args) { Connection conn = null; PreparedStatement pst1 = null; PreparedStatement pst2 = null; //生成try-catch-finally代码块快捷键:ctrl+alt+T,不要选中释放资源的代码 try { /* 需求:使用jdbc控制mysql事务。 使用Connection接口中的方法: 1.开启手动控制事务:void setAutoCommit(false) 2.一切正常,提交事务:void commit() 3.出现异常,回滚事务: void rollback() */ //使用事务完成a给b转账100元 //a-100 //b+100 //1.注册驱动 // DriverManager.registerDriver(new Driver()); //2.获取数据库连接 conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day03_heima139", "root", "1234"); //开启手动控制事务 == 1.开启手动控制事务:void setAutoCommit(false) conn.setAutoCommit(false); //3.获取发送sql语句的预编译对象 // a-100 pst1 = conn.prepareStatement("update account set money=money-100 where name=?"); // b+100 pst2 = conn.prepareStatement("update account set money=money+100 where name=?"); //4.发送sql语句 //给占位符赋值 pst1.setString(1, "a"); pst2.setString(1, "b"); //5.发送sql语句 pst1.executeUpdate(); //模拟异常 // int i = 1 / 0; pst2.executeUpdate(); //2.一切正常,提交事务:void commit() conn.commit(); } catch (Exception throwables) { throwables.printStackTrace(); try { //3.出现异常,回滚事务: void rollback() if (conn != null) { conn.rollback(); } } catch (SQLException e) { e.printStackTrace(); } } finally { //5.处理结果 //6.释放资源 try { if (pst2 != null) {//防止空指针异常 pst2.close(); } } catch (SQLException throwables) { throwables.printStackTrace(); } try { if (pst1 != null) {//防止空指针异常 pst1.close(); } } catch (SQLException throwables) { throwables.printStackTrace(); } try { if (conn != null) {//防止空指针异常 conn.close(); } } catch (SQLException throwables) { throwables.printStackTrace(); } } } }
小结
JDBC中与事务相关的API?Connection
接口中setAutoCommit,commit,rollback
JDBC操作事务的步骤?
- 注册驱动
- 获取连接
- 获取到Statement
- 开启事务
- 使用Statement执行SQL
- 提交或回滚事务
- 关闭资源
14、三层开发业务的案例分析
1、开发中,常使用分层思想
1) 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
2)不同层级结构彼此平等
3)分层的目的是:
a:解耦,就是降低代码之间的依赖关系。
b:可维护性,哪一层出现问题,直接维护哪一层。
c:可扩展性,哪一层需要添加代码,直接添加即可。
d:可重用性,一个方法可以被其它层重复调用。
2、不同层次,使用不同的包表示
具体的三层开发如下图所示: