第一章、JDBC概述
一、定义
Java Database Connectivity(JDBC)是Java语言中用于编写数据库操作的API。它允许Java应用程序通过标准的数据库访问方式来访问不同的数据库。JDBC提供了一组类和接口,使得开发人员可以编写数据库操作相关的代码,例如连接数据库、执行SQL查询和更新等操作。
1、JDBC的本质
JDBC就是一套接口
- JDBC是SUN公司制定的一套接口(interface)
- 接口都有调用者和实现者
- 面向接口调用,面向接口写实现类,这都属于面向接口编程
为什么要面向接口编程??
- 解耦合:降低程序的耦合度,提高程序的扩展力
- 多态机制就是非常典型的面向抽象编程,不要面向具体编程
- 多态就是:父类型引用指向子类型对象
建议:
Animal a = new Cat();
Animal a = new Dog();
//喂养的方法
public void feed(Animal a){ //面向父类型编程
}
不建议:
Dog d = new Dog();
Cat c = new Cat();
2、JDBC接口的意义
因为每一个数据库管理系统DBMS的底层实现原理都不一样
Oracle数据库有自己的原理
MySQL数据库也有自己的原理
MS SqlServer数据库也有自己的原理
......
每一个数据库产品都有自己独特的实现原理
3、下载MySQLjar包驱动
二、模拟JDBC本质
1、接口
JDBC.java
package 接口;
/*
SUN公司负责制定这套JDBC接口
*/
public interface JDBC{
/*
连接数据库的方法
*/
void getConnection();
}
2、实现类(驱动)
MySQL.java
package 实现类;
import 接口.JDBC;
/*
MySQL的数据库厂家负责编写JDBC接口的实现类
*/
public class MySQL implements JDBC{
public void getConnection(){
//具体这里的代码怎么写,对于我们Java程序员来说没关系
//这段代码涉及到MySQL底层数据库的实现原理
System.out.println("连接MySQL数据库成功!");
return;
}
}
//实现类被称为驱动。(MySQL驱动)
Oracle.java
package 实现类;
import 接口.JDBC;
/*
Oracle的数据库厂家负责编写JDBC接口的实现类
*/
public class Oracle implements JDBC{
public void getConnection(){
//具体这里的代码怎么写,对于我们Java程序员来说没关系
//这段代码涉及到Oracle底层数据库的实现原理
System.out.println("连接Oracle数据库成功!");
}
}
//实现类被称为驱动。(Oracle驱动)
SqlServer.java
package 实现类;
import 接口.JDBC;
/*
SqlServer的数据库厂家负责编写JDBC接口的实现类
*/
public class SqlServer implements JDBC{
public void getConnection(){
//具体这里的代码怎么写,对于我们Java程序员来说没关系
//这段代码涉及到SqlServer底层数据库的实现原理
System.out.println("连接SqlServer数据库成功!");
}
}
//实现类被称为驱动。(SqlServer驱动)
//xxx.jar 当中有很多x.class文件,都是对JDBC接口进行的实现
3、配置文件
jdbc.properties
className=实现类.SqlServer
4、调用者
JavaProgrammer.java
package 调用者;
import 接口.JDBC;
import 实现类.MySQL;
import 实现类.Oracle;
import java.lang.reflect.Constructor;
import java.util.ResourceBundle;
import java.lang.reflect.InvocationTargetException;
/*
java程序员角色
不需要关心具体是哪个品牌的数据库,只需要面向JDBC接口写代码
面向接口编程,面向抽象编程,不要面向具体编程
*/
public class JavaProgrammer{
public static void main(String[] args){
//JDBC jdbc = new MySQL();
//JDBC jdbc = new Oracle();
//JDBC jdbc = new 实现类.SqlServer();
//创建对象可以通过反射机制
ResourceBundle bundle = ResourceBundle.getBundle("配置文件/jdbc");
String className = bundle.getString("className");
JDBC jdbc = null;
try{
Class<?> c = Class.forName(className);
Constructor<?> con = c.getDeclaredConstructor();
Object object = con.newInstance();
if(object instanceof JDBC){
jdbc = (JDBC) object;
}
}catch(ClassNotFoundException e){
e.printStackTrace();
}catch(NoSuchMethodException e){
e.printStackTrace();
}catch(InstantiationException e){
e.printStackTrace();
}catch(IllegalAccessException e){
e.printStackTrace();
}catch(InvocationTargetException e){
e.printStackTrace();
}
//以下代码都是面向接口调用方法,不需要修改
jdbc.getConnection();
}
}
三、配置驱动环境变量
1、使用文本编辑器配置
先从官网下载对应的驱动jar包,然后将其配置到环境变量CLASSPATH当中
CLASSPATH=.;C:\Users\yuliang\Desktop\学习\JDBC\相关学习资源\mysql-connector-j-8.0.33\mysql-connector-j-8.0.33\mysql-connector-j-8.0.33.jar
以上的配置是针对于文本编辑器的方式开发,使用IDEA工具的时候,不需要配置以上的环境变量,IDEA有自己的配置方式
2、使用IDEA配置
第二章、JDBC编程六步(JDK19,MySQL8.0.33)
一、概述
1、注册驱动
作用:告诉Java程序,即将要连接的是哪个品牌的数据库
2、获取连接对象
表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,重量级的,使用完之后一定要关闭
3、获取数据库操作对象
专门执行sql语句的对象
4、执行SQL语句
主要执行DQL,DML语句
5、处理查询结果集
只有当第四步执行的是select语句的时候,才有这第五步处理查询结果集
6、释放资源
使用完资源之后一定要关闭资源,Java和数据库属于进程间的通信,开启之后一定要关闭。
二、相关概念与组件
下面是JDBC的一些核心概念和组件:
1、DriverManager(驱动管理器)
这是JDBC的基本类之一,它负责加载数据库驱动程序,并协调与不同数据库之间的连接。
2、Driver(驱动程序)
驱动程序是一个实现了JDBC接口的类,用于与特定数据库进行通信。每个数据库供应商通常都提供自己的驱动程序实现。
3、Connection(连接)
Connection接口代表与特定数据库的连接。通过Connection接口,可以创建Statement(用于执行静态SQL语句)和PreparedStatement(用于执行预编译的SQL语句)。
4、Statement(语句)
Statement接口用于执行静态的SQL语句,并返回其生成的结果。它用于执行不带参数的简单SQL查询。
5、PreparedStatement(预编译语句)
PreparedStatement接口用于执行预编译的SQL语句,允许使用占位符(?)来动态设置参数,从而防止SQL注入攻击并提高性能。
6、ResultSet(结果集)
ResultSet接口用于表示从数据库中检索的结果集。通过它,可以遍历和访问查询返回的数据。
JDBC允许开发人员通过Java代码执行数据库操作,如建立连接、执行查询和更新数据库等。通常的JDBC操作包括加载驱动程序、建立连接、创建和执行语句、处理结果集等。JDBC是许多Java应用程序与各种关系型数据库进行交互的重要方式之一。
三、注册驱动
1、java.sql.Driver:接口
是Java中的接口(interface)
2、com.mysql.cj.jdbc.Driver
是mysql的对于java.sql.Driver接口的实现类也叫做Driver
3、java.sql.DriverManager
是java.sql包中的类
4、public static void registerDriver(java.sql.Driver driver) throws SQLException
DriverManager类中的静态方法
5、java.sql.SQLException
java.sql包中的异常类
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
public class JDBCTest01{
public static void main(String[] args){
try{
//1、注册驱动
Driver driver = new com.mysql.cj.jdbc.Driver(); //多态,父类型引用指向子类型对象
DriverManager.registerDriver(driver);
}catch(SQLException e){
e.printStackTrace();
}
}
}
6、注册驱动的第二种方法
package 注册驱动的另一种方式;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.SQLException;
//import com.mysql.cj.jdbc.Driver;
//注册驱动的另一种方式(这种方式常用)
public class JDBCTest01{
public static void main(String[] args){
try{
//注册驱动
//注册驱动的第一种方式
//DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//注册驱动的第二种方式:常用的
//为什么这种方式常用?因为参数是一个字符串,字符串可以写到xxx.properties属性配置文件中
//以下方法不需要接受返回值,因为我们只想用它的类加载动作
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "123456");
System.out.println("数据库连接对象:" + conn.toString());
}catch(SQLException e){
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}
四、获取数据库连接对象
1、public static Connection getConnection(String url,String user, String password) throws SQLException
java.sql.DriverManager 类的静态方法,用于获取连接对象
2、url参数详解
url:统一资源定位符(网络中某个资源的绝对路径)
https://www.baidu.com/ 这就是url
url包括哪几部分?
- 协议
- IP
- port
- 资源名
jdbc:mysql://localhost:3306/bjpowernode
- jdbc:mysql:// 协议
- localhost IP地址
- 3306 端口号
- bjpowernode 具体的数据库实例名
说明:localhost和127.0.0.1 都是本机IP地址
什么是通信协议,有什么用?
- 通信协议是通信之前就提前定好的数据传输格式
- 数据包具体怎么传数据,格式是提前定好的
oracle的url:
jdbc:oracle:thin:@hostname:1521:database
3、java.sql.Connection:接口
java.sql包中的接口
mysql中的实现类是:com.mysql.cj.jdbc.ConnectionImpl
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
public class JDBCTest01{
public static void main(String[] args){
try{
//1、注册驱动
Driver driver = new com.mysql.cj.jdbc.Driver(); //多态,父类型引用指向子类型对象
DriverManager.registerDriver(driver);
//2、获取连接对象
String url = "jdbc:mysql://localhost:3306/bjpowernode";
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, user, password);
//相当于
//Connection conn = new com.mysql.cj.jdbc.ConnectionImpl();
System.out.println("数据库连接对象:" + conn.toString()); //数据库连接对象:com.mysql.cj.jdbc.ConnectionImpl@2aceadd4
}catch(SQLException e){
e.printStackTrace();
}
}
}
五、获取数据库操作对象
1、public Statement createStatement() throws SQLException
数据库连接对象的实例方法,用于获取数据库操作对象
2、java.sql.Statement:接口
java.sql包中的接口
mysql中的实现类:com.mysql.cj.jdbc.StatementImpl
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;
public class JDBCTest01{
public static void main(String[] args){
try{
//1、注册驱动
//注册mysql数据库
Driver driver = new com.mysql.cj.jdbc.Driver(); //多态,父类型引用指向子类型对象
//注册oracle数据库
//Driver driver = new oracle.jdbc.driver.OracleDriver();
DriverManager.registerDriver(driver);
//2、获取连接对象
String url = "jdbc:mysql://localhost:3306/bjpowernode";
String user = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, user, password);
//相当于
//Connection conn = new com.mysql.cj.jdbc.ConnectionImpl();
System.out.println("数据库连接对象:" + conn.toString()); //数据库连接对象:com.mysql.cj.jdbc.ConnectionImpl@2aceadd4
//3、获取数据库操作对象(Statement专门执行sql语句的对象)
Statement stmt = conn.createStatement();
//相当于
//Statement stmt = new com.mysql.cj.jdbc.StatementImpl();
System.out.println("数据库操作对象:" + stmt.toString()); //数据库操作对象:com.mysql.cj.jdbc.StatementImpl@77f1baf5
}catch(SQLException e){
e.printStackTrace();
}
}
}
六、执行sql语句
1、JDBC中的sql语句不需要提供分号结尾
JDBC中的sql语句不需要分号结尾,Python中的sql语句需要分号结尾。
2、public int executeUpdate(String sql) throws SQLException
返回值是“影响数据库中的记录条数”
3、 执行insert语句:使用executeUpdate方法
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Connection;
import java.sql.Statement;
public class JDBCTest01{
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
try{
//1、注册驱动
//注册mysql数据库
Driver driver = new com.mysql.cj.jdbc.Driver(); //多态,父类型引用指向子类型对象
//注册oracle数据库
//Driver driver = new oracle.jdbc.driver.OracleDriver();
DriverManager.registerDriver(driver);
//2、获取连接对象
String url = "jdbc:mysql://localhost:3306/bjpowernode";
String user = "root";
String password = "123456";
conn = DriverManager.getConnection(url, user, password);
//相当于
//Connection conn = new com.mysql.cj.jdbc.ConnectionImpl();
System.out.println("数据库连接对象:" + conn.toString()); //数据库连接对象:com.mysql.cj.jdbc.ConnectionImpl@2aceadd4
//3、获取数据库操作对象(Statement专门执行sql语句的对象)
stmt = conn.createStatement();
//相当于
//Statement stmt = new com.mysql.cj.jdbc.StatementImpl();
System.out.println("数据库操作对象:" + stmt.toString()); //数据库操作对象:com.mysql.cj.jdbc.StatementImpl@77f1baf5
//4、执行sql语句
String sql = "insert into dept (deptno, dname, loc) values (50, '人事部', '西安')";
sql = "delete from dept where deptno = 50";
//专门执行DML语句的(insert,delete,update)
//返回值是“影响数据库中的记录条数”
int count = stmt.executeUpdate(sql);
System.out.println(count == 1 ? "保存成功" : "保存失败");
}catch(SQLException e){
e.printStackTrace();
}finally{
//6、释放资源
//为了保证资源一定释放,在finally语句块中关闭资源
//并且要遵循从小到大依次关闭
//分别对其try...catch
if(null != stmt){
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(null != conn){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
4、执行delete语句:使用executeUpdate方法
package DML.delete;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;
public class DeleteTest01{
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
try{
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "123456");
stmt = conn.createStatement();
String sql = "delete from dept where deptno = 50";
System.out.println(stmt.executeUpdate(sql) == 1? "删除成功" : "删除失败");
}catch(SQLException e){
e.printStackTrace();
}finally{
if(null != stmt){
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(null != conn){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
5、执行update语句:使用executeUpdate方法
package DML.update;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;
public class UpdateTest01{
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
try{
DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode", "root", "123456");
stmt = conn.createStatement();
String sql = "insert into dept values(50, '人事部', '西安')";
sql = "update dept set dname = '人力资源部' where deptno = 50";
System.out.println(stmt.executeUpdate(sql) == 1 ? "成功" : "失败");
}catch(SQLException e){
e.printStackTrace();
}finally{
if(null != stmt){
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(null != conn){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
6、public ResultSet executeQuery(String sql) throws SQLException
com.mysql.cj.jdbc.StatementImpl类中的实例方法
返回查询结果集ResultSet类型对象
七、处理查询结果集
1、java.sql.ResultSet:接口
java.sql包中的接口
mysql中的实现类:com.mysql.cj.jdbc.result.ResultSetImpl
2、public boolean next() throws SQLException
返回值是true或false
每调用一次next方法,指针向下移动一行,若下一行有数据则返回true,否则返回false
3、public String getString(int columnIndex) throws SQLException
表示SQL语句中select子句中的顺序,从1开始
4、public String getString(String columnName) throws SQLException
表示SQL语句中select子句中的字段名,而非数据库中表的字段名,若起别名则要执行别名
package 处理查询结果集;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ResourceBundle;
//遍历结果集
public class JDBCTest{
public static void main(String[] args){
ResourceBundle bundle = ResourceBundle.getBundle("处理查询结果集/dbinfo");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement();
//执行sql语句
String sql = "select e.empno as 'a', e.ename, e.sal from emp e";
//int executeUpdate(insert/delete/update);
//ResultSet executeQuery(select);
rs = stmt.executeQuery(sql); //专门执行DQL语句的方法
//处理查询结果集
/*boolean flag = rs.next();
System.out.println(flag); //true
if(flag){
//光标指向的行有数据
//取数据
//getString()方法的特点是:不管数据库中的数据类型是什么,都以String的形式取出
String empno = rs.getString(1); //JDBC中所有下标都是从1开始,不是从0开始的
String ename = rs.getString(2);
String sal = rs.getString(3);
System.out.println(empno + "," + ename + "," + sal);
}
flag = rs.next();
System.out.println(flag); //true
if(flag){
//以下程序中的1 2 3 说的是第几列
String empno = rs.getString(1); //JDBC中所有下标都是从1开始,不是从0开始的
String ename = rs.getString(2);
String sal = rs.getString(3);
System.out.println(empno + "," + ename + "," + sal);
}*/
while(rs.next()){
/*String empno = rs.getString(1); //JDBC中所有下标都是从1开始,不是从0开始的
String ename = rs.getString(2);
String sal = rs.getString(3);
System.out.println(empno + "," + ename + "," + sal);*/
/*
//String empno = rs.getString("empno");
String empno = rs.getString("a"); //重点注意:列名称不是表中的列名称,而是查询结果集的列名称
String ename = rs.getString("ename");
String sal = rs.getString("sal");
System.out.println(empno + "," + ename + "," + sal);
*/
//除了可以以String类型取出之外,还可以以特定的类型取出
/*int empno = rs.getInt("a"); //重点注意:列名称不是表中的列名称,而是查询结果集的列名称
String ename = rs.getString("ename");
double sal = rs.getDouble("sal");
System.out.println(empno + "," + ename + "," + (sal + 100));*/
int empno = rs.getInt(1);
String ename = rs.getString(2);
double sal = rs.getDouble(3);
System.out.println(empno + "," + ename + "," + (sal + 200));
}
}catch(SQLException | ClassNotFoundException e){
e.printStackTrace();
}finally{
if(null != rs){
try{
rs.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(null != stmt){
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(null != conn){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
八、释放资源
//6、释放资源
//为了保证资源一定释放,在finally语句块中关闭资源
//并且要遵循从小到大依次关闭
//分别对其try...catch
if(null != stmt){
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(null != conn){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
//或者
try{
if(null != stmt){
stmt.close();
}
}catch(SQLException e){
e.printStackTrace();
}
try{
if(null != conn){
conn.close();
}
}catch(SQLException e){
e.printStackTrace();
}
九、从属性资源文件中读取连接数据库的信息
dbinfo.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/bjpowernode
user=root
password=123456
package 将连接数据库的所有信息配置到配置文件中;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;
import java.util.ResourceBundle;
//不建议将连接数据库的信息直接写死在java程序中
public class JDBCTest{
public static void main(String[] args){
//使用资源绑定器绑定属性配置文件
ResourceBundle bundle = ResourceBundle.getBundle("将连接数据库的所有信息配置到配置文件中/dbinfo");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Connection conn = null;
Statement stmt = null;
try{
//String driver = "com.mysql.cj.jdbc.Driver";
Class.forName(driver);
//String url = "jdbc:mysql://localhost:3306/bjpowernode";
//String user = "root";
//String password = "123456";
conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement();
String sql = "insert into dept (deptno, dname, loc) values (80, '销售部', '西安')";
System.out.println(stmt.executeUpdate(sql) == 1 ? "成功" : "失败");
}catch(SQLException e){
e.printStackTrace();
}catch(ClassNotFoundException e){
e.printStackTrace();
}finally{
if(null != stmt){
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(null != conn){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
}
}
第三章、SQL注入
一、用户登录案例
实现功能:
- 需求:模拟用户登录功能的实现
业务描述:
- 程序运行的时候,提供一个输入的入口,可以让用户输入用户名和密码
- 用户输入用户名和密码之后,提交信息,java程序收集到用户信息
- java程序连接数据库验证用户名和密码是否合法
- 合法:显示登录成功
- 不合法:显示登录失败
数据的准备:
- 在实际开发中,表的设计会使用专业的建模工具,我们这里安装一个建模工具:PowerDesigner
- 使用PD工具来进行数据库表的设计(参见 user-login.sql脚本)
1、代码实现
package 用户登录案例;
import java.util.Map;
import java.util.HashMap;
import java.util.Scanner;
import java.util.ResourceBundle;
import java.sql.DriverManager;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCTest{
public static void main(String[] args){
//初始化一个界面
Map<String, String> userLoginInfo = JDBCTest.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("用户名:");
/**
* next():读取到用户输入的数据以空格终止,只会读取到空格以前,不会读取到空格
* nextLine():读取到用户输入的数据的整个行,包括空格
*/
//String loginName = s.next();
String loginName = s.nextLine();
System.out.print("密码:");
//String loginPwd = s.next();
String loginPwd = s.nextLine();
Map<String, String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName", loginName);
userLoginInfo.put("loginPwd", loginPwd);
return userLoginInfo;
}
/**
*用户登录
*@param userLoginInfo 用户登录信息
*@return false 表示登录失败,true 表示登录成功
*/
private static boolean login(Map<String, String> userLoginInfo){
boolean flag = false;
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
ResourceBundle bundle = ResourceBundle.getBundle("用户登录案例/dbinfo");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement();
/**
* 第一种实现
*/
// String sql = "select loginPwd from t_user where loginName = " + "'" + loginName + "'";
// System.out.println(sql);
// rs = stmt.executeQuery(sql);
//
// String queryLoginPwd = null;
// if (rs.next()){
// queryLoginPwd = rs.getString(1);
// }
// flag = loginPwd.equals(queryLoginPwd);
/**
* 第二种实现
*/
String sql = "select * from t_user where loginName = '"+loginName+"' and loginPwd = '"+loginPwd+"'";
//以上代码正好完成了sql语句的拼接,以下代码的含义是:发送sql语句给DBMS,DBMS进行sql编译
//正好将用户提供的“非法信息”编译进去,导致了原sql语句的含义被扭曲了
System.out.println(sql);
rs = stmt.executeQuery(sql);
if(rs.next()){
flag = true;
}
//System.out.println("jzq".equals(null)); //false
}catch(SQLException | ClassNotFoundException e){
e.printStackTrace();
}finally{
if(null != rs){
try{
rs.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(null != stmt){
try{
stmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
if(null != conn){
try{
conn.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
return flag;
}
}
二、SQL注入
1、概述
当前程序存在的问题:
- 用户名:aa
- 密码 bb' or '1' = '1 登录成功
- 这种现象被称为SQL注入(安全隐患),黑客经常使用
SQL注入是一种常见的网络安全漏洞,它允许攻击者通过将恶意的SQL代码插入到应用程序的输入字段中来操纵数据库。这种攻击可能会导致敏感数据泄露、数据损坏,甚至完全控制数据库。以下是SQL注入的一些常见类型和防范措施:
-
基于输入的SQL注入:攻击者通过在应用程序的输入字段中插入恶意SQL代码来利用这种类型的注入。例如,在一个登录表单中,攻击者可以输入
' OR '1'='1
来绕过认证。防范措施:使用参数化查询和预编译语句可以有效防止基于输入的SQL注入。确保对用户输入进行严格的验证和过滤,或者使用ORM(对象关系映射)工具来处理数据库交互,因为它们通常会自动处理SQL注入问题。
-
盲注SQL注入:这种类型的注入通常发生在应用程序对恶意输入的响应没有明显的变化时。攻击者可以使用盲注技术逐步猜测和验证数据库中的数据。
防范措施:严格限制数据库用户的权限和访问范围,以及对应用程序的输入进行严格的验证和过滤。
-
错误的配置引起的SQL注入:如果数据库或应用程序的安全配置不正确,可能会导致攻击者能够利用这些漏洞进行SQL注入攻击。
防范措施:确保数据库服务器和应用程序都采取了最佳的安全配置实践。限制对数据库的访问权限,以最小化攻击者可能获得的权限。
-
时间延迟SQL注入:攻击者可以利用应用程序对恶意输入的处理时间不同来判断数据库查询的结果。
防范措施:确保应用程序对输入的响应时间是固定的,不会因为不同的查询结果而产生明显的延迟。
为了防止SQL注入攻击,开发人员应该始终使用参数化查询和预编译语句,并严格验证和过滤所有的用户输入。同时,定期审查应用程序的安全配置,并遵循最佳的安全实践和标准来保护应用程序和数据库免受攻击。
2、导致SQL注入的根本原因
用户输入的信息中含有SQL语句的关键字,并且这些关键字参与了sql语句的编译过程
导致sql语句的原意被扭曲,进而达到sql注入
三、解决SQL注入问题
1、思路
只要用户提供的信息不参与SQL语句的编译过程,问题就解决了
即使用户提供的信息中含有SQL语句的关键字,但是没有参与编译,不起作用
2、java.sql.PreparedStatement接口
mysql中的实现类:com.mysql.cj.jdbc.ClientPreparedStatement
要想用户信息不参与SQL语句的编译,那么必须使用java.sql.PreparedStatement接口
PreparedStatement 接口继承了java.sql.Statement接口
PreparedStatement是属于预编译的数据库操作对象
PreparedStatement的原理是:预先对SQL语句的框架进行编译,然后再给SQL语句传“值”
3、解决SQL注入的关键
用户提供的信息中即使含有sql语句的关键字,但是这些关键字并没有参与编译,不起作用
4、代码实现
package 避免sql注入;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Scanner;
/**
* 解决SQL注入问题
* 测试结果:
* 用户名:aa
* 密码:bb' or '1' = '1
* 登录失败
*/
public class JDBCTest {
public static void main(String[] args) {
//初始化用户登录界面
Map<String, String> userLoginInfo = initUI();
//判断是否成功登录
boolean loginSuccess = loginUI(userLoginInfo);
//输出结果
System.out.println(loginSuccess ? "登录成功" : "登录失败");
}
private static boolean loginUI(Map<String, String> userLoginInfo) {
boolean flag = false;
ResourceBundle bundle = ResourceBundle.getBundle("避免sql注入/dbinfo");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
String loginName = userLoginInfo.get("loginName");
String loginPwd = userLoginInfo.get("loginPwd");
Connection conn = null;
PreparedStatement ps = null; //这里使用PreparedStatement 预编译的数据库操作对象
ResultSet rs = null;
try{
//1、注册驱动
Class.forName(driver);
//2、获取数据库连接对象
conn = DriverManager.getConnection(url, user, password);
//3、获取预编译的数据库操作对象
/**
* SQL语句的框架,其中一个 ? 表示一个占位符
* 一个 ? 将来接受一个“值”
* 注意:占位符不能使用单引号括起来
*/
String sql = "select * from t_user where loginName = ? and loginPwd = ?";
//程序指定到此处,会发送sql语句的框架给DBMS,然后DBMS进行sql语句的预先编译
ps = conn.prepareStatement(sql);
System.out.println(ps); //com.mysql.cj.jdbc.ClientPreparedStatement: select * from t_user where loginName = ** NOT SPECIFIED ** and loginPwd = ** NOT SPECIFIED **
//给占位符 ? 传值,第1个问号下标是1,第2个问号下标是2,JDBC中所有的下标从1开始
ps.setString(1, loginName);
ps.setString(2, loginPwd);
//4、执行sql
rs = ps.executeQuery();
//5、处理查询结果集
if(rs.next()){
flag = true;
}
}catch (SQLException | ClassNotFoundException e){
e.printStackTrace();
}finally {
//6、释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return flag;
}
private static Map<String, String> initUI() {
Scanner input = new Scanner(System.in);
System.out.print("用户名:");
String loginName = input.nextLine();
System.out.print("密码:");
String loginPwd = input.nextLine();
Map<String, String> userLoginInfo = new HashMap<>();
userLoginInfo.put("loginName", loginName);
userLoginInfo.put("loginPwd", loginPwd);
return userLoginInfo;
}
}
四、Statement和PreparedStatement接口的区别
1、预编译
Statement存在sql注入问题,PreparedStatement进行预编译,解决了SQL注入问题
只是sql语句传值:使用PreparedStatement
需要sql语句拼接:使用Statement
2、PreparedStatement执行效率高
DBMS在执行SQL语句的时候会先进行编译,然后再执行,如果两个输入的SQL语句完全一致,则第二次输入的SQL语句不会编译,直接执行。
Statement是编译一次执行一次,PreparedStatement是编译一次,可以执行多次,PreparedStatement效率较高一点
3、PreparedStatement会做类型安全检查
PreparedStatement会在编译阶段做类型的安全检查
综上所述,PreparedStatement使用较多,只有极少数的情况下需要使用Statement
五、演示Statement的用途
业务方面要求必须支持SQL注入的时候
- Statement支持SQL注入,凡是业务方面要求是需要进行sql语句拼接的,必须使用Statement
- 单纯的使用sql语句传值,必须使用PreparedStatement
package 演示必须使用Statement的情况;
import java.sql.*;
import java.util.Scanner;
/**
* 演示必须使用Statement的情况
*
* 使用PreparedStatement报错:
* java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''asc'' at line 1
*/
public class JDBCTest {
public static void main(String[] args) {
/*
//用户在控制台输入的asc表示升序,desc表示降序
Scanner input = new Scanner(System.in);
System.out.println("请输入asc或desc,asc表示升序,desc表示降序");
System.out.print("请输入:");
String keyWords = input.nextLine();
//执行SQL
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try{
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "123456");
String sql = "select * from t_user order by id ?";
ps = conn.prepareStatement(sql);
ps.setString(1,keyWords);
rs = ps.executeQuery();
while (rs.next()){
int id = rs.getInt("id");
String loginName = rs.getString("loginName");
String loginPwd = rs.getString("loginPwd");
System.out.println(id + "," + loginName + "," + loginPwd);
}
}catch (SQLException | ClassNotFoundException e){
e.printStackTrace();
}finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
*/
//用户在控制台输入的asc表示升序,desc表示降序
Scanner input = new Scanner(System.in);
System.out.println("请输入asc或desc,asc表示升序,desc表示降序");
System.out.print("请输入:");
String keyWords = input.nextLine();
//执行SQL
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "123456");
stmt = conn.createStatement();
String sql = "select * from t_user order by id " + keyWords;
rs = stmt.executeQuery(sql);
while (rs.next()){
int id = rs.getInt("id");
String loginName = rs.getString("loginName");
String loginPwd = rs.getString("loginPwd");
System.out.println(id + "," + loginName + "," + loginPwd);
}
}catch (SQLException | ClassNotFoundException e){
e.printStackTrace();
}finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
六、使用PreparedStatement完成增删改
package 使用PreparedStatement完成增删改;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 使用PreparedStatement完成insert,delete,update
*/
public class JDBCTest {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "123456");
/**
* 增加操作
*/
// String sql = "insert into dept (deptno, dname, loc) values (?, ?, ?)";
// ps = conn.prepareStatement(sql);
// ps.setInt(1,50);
// ps.setString(2, "人事部");
// ps.setString(3, "西安");
/**
* 修改操作
*/
// String sql = "update dept set dname = ?, loc = ? where deptno = ?";
// ps = conn.prepareStatement(sql);
// ps.setInt(3,50);
// ps.setString(1, "销售部");
// ps.setString(2, "西安");
/**
* 删除操作
*/
String sql = "delete from dept where deptno = ?";
ps = conn.prepareStatement(sql);
ps.setInt(1,50);
//执行sql语句
int count = ps.executeUpdate();
System.out.println(count);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
第四章、JDBC事务
一、概述
1、JDBC中的事务是自动提交的
只要执行任意一条DML语句,则自动提交(commit)一次,这是JDBC默认的事务行为
但是在实际的业务当中,通常都是N条DML语句共同联合才能完成的,必须保证他们这些DML语句在同一个事务中同时成功或者同时失败
2、验证JDBC事务为自动提交
测试结果:JDBC中只要执行任意一条DML语句,就会提交一次事务
package JDBC事务;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTest01 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
try {
//1、注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2、获取数据库连接对象
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root", "123456");
//3、获取预编译数据库操作对象
String sql = "update dept set dname = ? where deptno = ?";
ps = conn.prepareStatement(sql);
//第一次给占位符赋值
ps.setString(1, "xx部门");
ps.setInt(2, 50);
int count = ps.executeUpdate(); //执行第一条update语句 此时事务已经提交,数据库中的数据已经修改
//违反了事务的一致性原则
System.out.println(count);
//重新给占位符传值
ps.setString(1, "yy部门");
ps.setInt(2, 60);
//4、执行sql语句
count = ps.executeUpdate(); //执行第二条update语句
System.out.println(count);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
//6、释放资源
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
二、银行转账演示事务
将自动提交机制修改为手动提交
数据库连接对象.setAutoCommit(false);
重点三行代码:
- conn.setAutoCommit(false);
- conn.commit();
- conn.rollback();
package JDBC事务.银行转账演示事务;
import java.sql.*;
import java.util.ResourceBundle;
/**
* sql脚本:
* drop table if exists t_act;
* create table t_act(
* actno int primary key auto_increment,
* balance decimal(7, 2) not null
* );
*
* insert into t_act (actno, balance) values(111,20000),(222,0);
* commit;
* select * from t_act;
*/
public class 银行转账演示事务 {
public static void main(String[] args) {
ResourceBundle bundle = ResourceBundle.getBundle("JDBC事务/dbinfo");
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
Connection conn = null;
PreparedStatement ps = null;
try {
Class.forName(driver);
conn = DriverManager.getConnection(url, user, password);
//将自动提交机制修改为手动提交
conn.setAutoCommit(false); //开启事务
String sql = "update t_act set balance = ? where actno = ?";
ps = conn.prepareStatement(sql);
//给?传值
ps.setDouble(1, 10000);
ps.setInt(2, 111);
int count = ps.executeUpdate();
//制造异常
// throw new NullPointerException();
// String s = null;
// s.toString();
ps.setDouble(1, 10000);
ps.setInt(2, 222);
count = count + ps.executeUpdate();
System.out.println(count == 2 ? "转账成功" : "转账失败");
//程序能够走到这说明以上程序没有异常,事务结束,手动提交事务
conn.commit(); //提交事务
} catch (ClassNotFoundException | SQLException e) {
//回滚事务
if (conn != null) {
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}finally {
if (ps != null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
第五章、JDBC工具类封装
package util;
import java.sql.*;
/**
* JDBC工具类,简化JDBC编程
*/
public class DBUtil {
/**
* 工具类中的构造方法都是私有的
* 因为工具类当中的方法都是静态的,不需要new对象,直接采用类名调用
* 将构造方法设置为private是为了防止调用这new对象
*/
private DBUtil(){}
//静态代码块在类型加载时执行,并且只执行一次
static{
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接对象
* @return 返回数据库连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode");
}
/**
* 关闭资源
* @param rs 结果集对象
* @param stmt 数据库操作对象
* @param conn 数据库连接对象
* @throws SQLException
*/
public static void close(ResultSet rs, Statement stmt, Connection conn) throws SQLException{
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
}
public static void close(Statement stmt, Connection conn) throws SQLException{
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
}
}
第六章、模糊查询
工具类
package util;
import java.sql.*;
import java.util.ResourceBundle;
public class JDBCUtil {
private JDBCUtil(){}
static{
ResourceBundle bundle = ResourceBundle.getBundle("util/dbinfo");
String driver = bundle.getString("driver");
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
ResourceBundle bundle = ResourceBundle.getBundle("util/dbinfo");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");
return DriverManager.getConnection(url, user, password);
}
public static void close(Statement stmt, Connection conn) throws SQLException{
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
}
public static void close(ResultSet rs, Statement stmt, Connection conn) throws SQLException{
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
}
}
使用工具类
package 模糊查询;
import util.JDBCUtil;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class 模糊查询 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtil.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();
while (rs.next()){
System.out.println(rs.getString("ename"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
try {
JDBCUtil.close(rs, ps, conn);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}