视频链接:https://www.bilibili.com/video/BV1Bt41137iB?vd_source=9545770e4a2968c05878ffac8589ec6c
视频选集:P14— P29
文章目录
1.用户登录业务介绍
实现功能:
-
需求:模拟用户登录功能的实现。
-
业务描述:
程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码
用户输入用户名和密码之后,提交信息,java程序收集到用户信息
Java程序连接数据库验证用户名和密码是否合法
合法:显示登录成功
不合法:显示登录失败 -
数据的准备:在实际开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具:PowerDesigner
【使用PD工具来进行数据库表的设计】
2.PowerDesigner工具的安装
3.PowerDesigner工具进行物理建模
用这个软件对表进行设计:
对表格双击:
SQL语句自动生成:
然后对表进行保存
4.用户登录功能界面的初始化以及登录方式的实现
public class JDBCTest06 {
public static void main (String[] args) {
// 初始化一个界面
Map<String , String> userLoginInfo = initUI() ;
//验证用户名和密码
boolean loginSuccess = login(userLoginInfo) ;
//最后输出结果
System.out.println(loginSuccess?"登录成功":"登录失败");
}
//初始化用户界面
//@return用户输入的用户名和密码等登录信息
private static Map<String, String> initUI () {
Scanner s = new Scanner (System.in) ;
System.out.print("用户名: ") ;
String loginName = s.nextLine () ;
System.out.print ("密码: ") ;
String loginPwd = s.nextLine () ;
Map<String, String> userLoginInfo = new HashMap<> () ;
userLoginInfo.put ("loginName", loginName) ;
use rLoginInfo.put ("loginPwd", loginPwd) ;
return userLoginInfo;
}
//用户登录
//@param userLoginInfo 用户登录信息
//@return false表示失败,true表示成功
private static boolean login(Map<String,String> userLoginInfo){
//打标记的意识
boolean loginSuccess = false;
//单独定义变量
String loginName = userLoginInfo.get("loginName") ;
String loginPwd = userLoginInfo.get("loginPwd") ;
//JDBC代码
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1、注册驱动
Class.forName ("com.mysql.jdbc.Driver") ;
//2、获取连接
conn = DriverManager.getConnection ("jdbc:mysq1://127.0.0.1:3306/bjpowernode","root","333") ;
//3、获取数据库操作对象
stmt = conn.createStatement () ;
//4、执行sql
String sql = "select * from t_user where loginName = '"+loginName+"' and loginPwd = '"+loginPwd +"'";
rs = stmt.executeQuery(sql); //专门执行DQL语句的方法。
//5、处理结果集
if(rs.next()){
//登录成功
loginSuccess = true;
}
}catch (Exception e) {
e.printStackTrace () ;
}finally{
//6、释放资源
try {
if (rs != nu11) {
rs.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
try {
if (stmt != nu11) {
stmt.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
try {
if (conn != nu11) {
conn.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
}
return loginSuccess;
}
}
5.演示SQL注入现象
上面程序存在的问题:
用户名:fdsa
密码:fdsa' or '1'='1
登录成功
上面现象被称为SQL注入(安全隐患)【黑客经常使用】
导致SQL注入的根本原因:
答:用户输入的信息中含有sql语句的关键字,并且这些关键字参与sql语句的编译过程,导致sql语句的原意被扭曲,进而达到sql注入
6.解决SQL注入问题
- 只要用户提供的信息不参与SQL语句的编译过程,问题就解决了。
- 即使用户提供的信息中含有SQL语句的关键字,但是没有参与编译,不起作用。
- 要想用户信息不参与SQL语句的编译,那么必须使用java. sql.PreparedStatement
- PreparedStatement接口继承了java. sql.Statement
- PreparedStatement是属于预编译的数据库操作对象
- PreparedStatement原理:预先对SQL语句的框架进行编译,然后再给SQL语句传"值"
public class JDBCTest07{
public static void main (String[] args) {
// 初始化一个界面
Map<String , String> userLoginInfo = initUI() ;
//验证用户名和密码
boolean loginSuccess = login(userLoginInfo) ;
//最后输出结果
System.out.println(loginSuccess?"登录成功":"登录失败");
}
//初始化用户界面
//@return用户输入的用户名和密码等登录信息
private static Map<String, String> initUI () {
Scanner s = new Scanner (System.in) ;
System.out.print("用户名: ") ;
String loginName = s.nextLine () ;
System.out.print ("密码: ") ;
String loginPwd = s.nextLine () ;
Map<String, String> userLoginInfo = new HashMap<> () ;
userLoginInfo.put ("loginName", loginName) ;
use rLoginInfo.put ("loginPwa", loginPwd) ;
return userLoginInfo;
}
//用户登录
//@param userLoginInfo 用户登录信息
//@return false表示失败,true表示成功
private static boolean login(Map<String,String> userLoginInfo){
//打标记的意识
boolean loginSuccess = false;
//单独定义变量
String loginName = userLoginInfo.get("loginName") ;
String loginPwd = userLoginInfo.get("loginPwd") ;
//JDBC代码
Connection conn = null;
PreparedStatement ps = null; //这里使用PreparedStatement(预编译的数据库操作对象)
ResultSet rs = null;
try {
//1、注册驱动
Class.forName ("com.mysql.jdbc.Driver") ;
//2、获取连接
conn = DriverManager.getConnection ("jdbc:mysq1://127.0.0.1:3306/bjpowernode","root","333") ;
//3、获取预编译的数据库操作对象
//这行是SQL语句的框子 ?为占位符,一个?将来接受一个“值”, 注意:占位符不能使用单引号括起来
String sql = "select * from t_user where loginName = ? and loginPwd = ?";
//程序执行到此处,会发送sql语句框子给DBMS,然后DBMS进行sql语句的预先编译
ps = conn.prepareStatement (sql) ;
//给占位符?传值(第1个问号下标是1,第2个问号下标是2,JDBC中所有下标从1开始)
ps.setString(1,loginName);
ps.setString(2,loginPwd);
//4、执行sql
rs = ps .executeQuery(); //专门执行DQL语句的方法。
//5、处理结果集
if(rs.next()){
//登录成功
loginSuccess = true;
}
}catch (Exception e) {
e.printStackTrace () ;
}finally{
//6、释放资源
try {
if (rs != nu11) {
rs.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
try {
if (ps != nu11) {
ps.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
try {
if (conn != nu11) {
conn.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
}
return loginSuccess;
}
}
7.Statement和PreparedStatement对比
- Statement存在SQL注入问题,PreparedStatement解决了SQL注入问题
- Statemen是编译一次执行一次,PreparedStatement是编译一次,可执行N次。PreparedStatement效率较高一些
- PreparedStatement会在编译阶段做类型的安全检查
综上所述:PreparedStatement使用较多,只有极少数的情况下需要使用Statement
什么情况下必须使用Statement呢?
答:Statement支持SQL注入,凡事业务方面要求是需要进行sql语句拼接的,必须使用Statement【业务方面要求必须支持SQL注入的时候】
8.Statement的用途
public class JDBCTest08{
public static void main (String[] args) {
//用户在控制台输入desc就是降序,输入asc就是升序
Scanner s = new Scanner(System.in);
System.out.println("请输入desc或asc,desc表示降序,asc表示升序");
System.out.println("请输入:");
String keyWords = s.nextLine();
//执行SQL
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1、注册驱动
Class.forName ("com.mysql.jdbc.Driver") ;
//2、获取连接
conn = DriverManager.getConnection ("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","333") ;
//3、获取数据库操作对象
stmt = conn.createStatement () ;
//4、执行sql
String sql = "select ename from emp order by ename " + keyWords;
rs = stmt.executeQuery(sql); //专门执行DQL语句的方法。
//5、处理结果集
while(rs.next()){
System.out.println(rs.getString("ename"));
}
}catch (Exception e) {
e.printStackTrace () ;
}finally{
//6、释放资源
try {
if (rs != nu11) {
rs.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
try {
if (stmt != nu11) {
stmt.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
try {
if (conn != nu11) {
conn.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
}
}
}
9.PreparedStatement完成增删改
增加操作:
public class JDBCTest09{
public static void main (String[] args) {
//执行SQL
Connection conn = null;
PreparedStatement ps = null;
try {
//1、注册驱动
Class.forName ("com.mysql.jdbc.Driver") ;
//2、获取连接
conn = DriverManager.getConnection ("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","333") ;
//3、获取预编译的数据库操作对象
String sql = "insert into dept(deptno,dname,loc) values(?,?,?)";
ps = conn.prepareStatement (sql) ;
//给占位符?传值(第1个问号下标是1,第2个问号下标是2,JDBC中所有下标从1开始)
ps.setInt(1,60);
ps.setString(2,"销售部");
ps.setString(3,"上海");
//4、执行sql
int count = ps.executeUpdate(); //专门执行DQL语句的方法。
System.out.println(count);
}catch (Exception e) {
e.printStackTrace () ;
}finally{
//6、释放资源
try {
if (ps != nu11) {
ps.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
try {
if (conn != nu11) {
conn.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
}
}
}
修改操作:【其它地方不变,就修改下面的部分】
public class JDBCTest09{
public static void main (String[] args) {
//3、获取预编译的数据库操作对象
String sql = "update dept set dname = ?,loc = ? where deptno = ?";
ps = conn.prepareStatement (sql) ;
//给占位符?传值(第1个问号下标是1,第2个问号下标是2,JDBC中所有下标从1开始)
ps.setInt(1,60);
ps.setString(2,"研发一部");
ps.setString(3,"北京");
}
}
删除操作:【其它地方不变,就修改下面的部分】
public class JDBCTest09{
public static void main (String[] args) {
//3、获取预编译的数据库操作对象
String sql = "delete from dept where deptno = ?";
ps = conn.prepareStatement (sql) ;
//给占位符?传值(第1个问号下标是1,第2个问号下标是2,JDBC中所有下标从1开始)
ps.setInt(1,60);
}
}
10.JDBC的事务自动提交机制的演示
JDBC事务机制:
- JDBC的事务是自动提交的【只要执行任意一条DML语句,则自动提交一次(这是JDBC默认的事务行为)】
- 但是在实际的业务中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者同时失败
以下程序先来验证一下JDBC的事务是否是自动提交机制
public class JDBCTest09{
public static void main (String[] args) {
//执行SQL
Connection conn = null;
PreparedStatement ps = null;
try {
//1、注册驱动
Class.forName ("com.mysql.jdbc.Driver") ;
//2、获取连接
conn = DriverManager.getConnection ("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","333") ;
//3、获取预编译的数据库操作对象
String sql = "update dept set dname = ? where deptno = ?";
ps = conn.prepareStatement (sql) ;
//第一次给占位符传值
ps.setString(1,"X部门");
ps.setInt(2,30);
//4、执行sql
int count = ps.executeUpdate(); //执行第一条UPDATE语句
System.out.println(count);
//重新给占位符传值
ps.setString(1,"Y部门");
ps.setInt(2,20);
count = ps.executeUpdate(); //执行第二条UPDATE语句
System.out.println(count);
}catch (Exception e) {
e.printStackTrace () ;
}finally{
//6、释放资源
try {
if (ps != nu11) {
ps.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
try {
if (conn != nu11) {
conn.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
}
}
}
在上面第一次 ps.executeUpdate()然后就提交事务了,同时数据库更新一次
11.账户转账演示事务
先提供SQL脚本:
drop table if exists t_act;
create table t_act(
actno int,
balance double(7,2) //注意:7表示有效数字的个数,2表示小数位的个数
);
insert into t_act(actno,balance) values (111,20000);
insert into t_act(actno,balance) values (222,0);
commit;
select * from t_act;
编写代码:
public class JDBCTest09{
public static void main (String[] args) {
//执行SQL
Connection conn = null;
PreparedStatement ps = null;
try {
//1、注册驱动
Class.forName ("com.mysql.jdbc.Driver") ;
//2、获取连接
conn = DriverManager.getConnection ("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","333") ;
//将自动提交机制修改为手动提交
conn.setAutoCommit(false);//开启事务
//3、获取预编译的数据库操作对象
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement (sql) ;
ps.setDouble(1,10000);
ps.setInt(2,111);
//4、执行sql
int count = ps.executeUpdate(); //执行第一条UPDATE语句
//设置异常
String s = null;
s.toString();
//重新给占位符传值
ps.setDouble(1,10000);
ps.setInt(2,222);
count += ps.executeUpdate();
System.out.println(count == 2 ?"转账成功" : "转账失败");
//程序能够走到这里说明以上程序没有异常,事务结束,手动提交数据
conn.commit();//提交事务
}catch (Exception e) {
//回滚事务
if(conn != null){
try{
conn.rollback();
}catch (SQLException e1){
e1.printStackTrace () ;
}
}
e.printStackTrace () ;
}finally{
//6、释放资源
try {
if (ps != nu11) {
ps.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
try {
if (conn != nu11) {
conn.close() ;
}
}catch (SQLException e) {
e.printStackTrace () ;
}
}
}
}
重点三行代码:
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
12.JDBC工具类的封装
JDBC工具类:简化JDBC编程
public class DBUtil{
//工具类中的构造方法都是私有的
//因为工具类中的方法都是静态的,不需要new对象,直接采用类名调用
private DBUtil(){
}
//静态代码块在类加载时执行,并且只执行一次
static{
try {
//1、注册驱动
Class.forName ("com.mysql.jdbc.Driver") ;
}catch (Exception e) {
e.printStackTrace () ;
}
}
//获取数据库连接对象,返回连接对象
public static Connection getConnection() throws SQLException{
//2、获取连接
conn = DriverManager.getConnection ("jdbc:mysql://127.0.0.1:3306/bjpowernode","root","333") ;
return conn ;
}
//关闭资源
public static void close(Connection conn,Statement ps,ResultSet rs){
if (rs != nu11) {
try {
rs.close() ;
}catch (SQLException e) {
e.printStackTrace () ;
}
}
if (ps != nu11) {
try {
ps.close() ;
}catch (SQLException e) {
e.printStackTrace () ;
}
}
if (conn != nu11) {
try {
conn.close() ;
}catch (SQLException e) {
e.printStackTrace () ;
}
}
}
}
13.JDBC实现模糊查询
本程序有两个任务:
- 测试DBUtil是否好用
- 展示模糊查询
public class JDBCTest12{
public static void main (String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
//获取连接
conn = DBUtil.getConnection();
//获取预编译的数据库操作对象
//错误写法
//String sql = "select ename from emp where ename like ?";
//ps = conn.prepareStatement (sql) ;
//ps.setString(1,"A");
String sql = "select ename from emp where ename like '_?%'";
ps = conn.prepareStatement (sql) ;
ps.setString(1,"_A%");
rs = ps.executeQuery();
//5、处理结果集
while(rs.next()){
System.out.println(rs.getString("ename"));
}
}catch (Exception e){
e.printStackTrace();
}finally{
//释放资源
DBUtil.close(conn,ps,rs);
}
}
}
14.悲观锁和乐观锁的概念
悲观锁【行级锁】:sql语句中select 后面加上 for update【事务必须排队执行。数据锁住了,不允许并发】
乐观锁:支持并发,事务也不需要排队,只不过需要一个版本号
乐观锁演示:
15.演示行级锁机制
定义JDBCTest13:这个程序开启一个事务,这个事务专门进行查询,并且使用行级锁/悲观锁,锁住相关的记录
定义JDBCTest14:这个程序负责修改被锁定的记录
JDBCTest13:
public class JDBCTest13{
public static void main (String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
//获取连接
conn = DBUtil.getConnection();
//开启事务
conn.setAutoCommit(false);
//获取预编译的数据库操作对象
String sql = "select ename,job,sal from emp where job = ? for update";
ps = conn.prepareStatement (sql) ;
ps.setString(1,"MANAGER");
rs = ps.executeQuery();
//5、处理结果集
while(rs.next()){
System.out.println(rs.getString("ename") + "," + rs.getString("job") + "," + rs.getDouble("sal"));
}
//提交事务(事务结束)
conn.commit();
}catch (Exception e){
//回滚事务(事务结束)
if(conn != null){
try{
conn.rollback();
}catch (SQLException e1){
e1.printStackTrace () ;
}
}
e.printStackTrace();
}finally{
//释放资源
DBUtil.close(conn,ps,rs);
}
}
}
JDBCTest14:
public class JDBCTest14{
public static void main (String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try{
//获取连接
conn = DBUtil.getConnection();
//开启事务
conn.setAutoCommit(false);
//获取预编译的数据库操作对象
String sql = "update emp set sal = sal * 1.1 where job = ?";
ps = conn.prepareStatement (sql) ;
ps.setString(1,"MANAGER");
int count = ps.executeUpdate();
System.out.println(count)
//提交事务(事务结束)
conn.commit();
}catch (Exception e){
//回滚事务(事务结束)
if(conn != null){
try{
conn.rollback();
}catch (SQLException e1){
e1.printStackTrace () ;
}
}
e.printStackTrace();
}finally{
//释放资源
DBUtil.close(conn,ps,null);
}
}
}
演示的时候在JDBCTest13中的conn.commit();打断点,然后debug运行JDBCTest13,再运行JDBCTest14,在JDBCTest14中没有值输出,因为JDBCTest13中锁住了