JDBC(二)

视频链接: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对比

  1. Statement存在SQL注入问题,PreparedStatement解决了SQL注入问题
  2. Statemen是编译一次执行一次,PreparedStatement是编译一次,可执行N次。PreparedStatement效率较高一些
  3. 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事务机制:

  1. JDBC的事务是自动提交的【只要执行任意一条DML语句,则自动提交一次(这是JDBC默认的事务行为)】
  2. 但是在实际的业务中,通常都是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中锁住了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值