目录
4.2.5 PreparedStatement防止注入的原理
1. JDBC概述
JDBC就是使用Java语言操作关系型数据库的一套API
全称:( Java DataBase Connectivity ) Java 数据库连接
接口的实现类就叫驱动
各个数据库厂商去实现这套接口,提供数据库驱动jar包,真正执行的代码是驱动 jar包中的实现类
如果需要操作MySQL数据库,就需要在项目中导入MySQL数据库的驱动包。
2. 通过Java操作数据库的流程
第一步:编写Java代码
第二步:Java代码将SQL发送到MySQL服务端
第三步:MySQL服务端接收到SQL语句并执行该SQL语句
第四步:将SQL语句执行的结果返回给Java代码
2.1 编写代码步骤
1. 创建工程,导入驱动jar包
2. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
3. 获取连接
url : 连接路径
语法:jdbc:mysql://ip地址(域名):端口号/数据库名称?参数 键值对
如 : jdbc:mysql://127.0.0.1:3306/db1
如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数 据库名称?参数键值对 配置 useSSL=false 参数,禁用安全连接方式,解决 警告提示
如 : jdbc:mysql://127.0.0.1:3306/db1?useSSL=false
Connection(数据库连接对象)
作用:1. 获取执行 SQL 的对象 2. 管理事务
Connection conn =
DriverManager.getConnection(url, username,
password);
4. 定义SQL语句
String sql = “update…” ;
5. 获取执行SQL对象
执行SQL语句需要SQL执行对象,而这个执行对象就是 Statement对象
Statement stmt = conn.createStatement();
6. 执行SQL
stmt.executeUpdate(sql);
7. 处理返回结果
8. 释放资源
代码示例
public class JDBCDemo {
public static void main(String[] args) throws Exception {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接
String url = "jdbc:mysql://127.0.0.1:3306/db1";
String username = "root";
String password = "1234";
Connection conn =DriverManager.getConnection(url, username,
password);
//3. 定义sql
String sql = "update account set money = 2000 where id = 1";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
int count = stmt.executeUpdate(sql);//返回受影响的行数
//6. 处理结果
System.out.println(count);
//7. 释放资源
stmt.close();
conn.close();
}
}
2.2 API详解
2.2.1 获取执行对象
1. 普通执行SQL对象
Statement createStatement()
2. 预编译SQL的执行SQL对象:防止SQL注入
PreparedStatement prepareStatement(sql)
2.2.2 事务管理
MySQL事务管理的操作:
开启事务 : BEGIN; 或者 START TRANSACTION;
提交事务 : COMMIT;
回滚事务 : ROLLBACK;
MySQL默认是自动提交事务
如果手动开始事务, MySQL将不会自动提交事务, 则需要手动提交
3. 执行sql语句
3.1 执行DML语句
代码示例
public void testDML() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username,password);
//3. 定义sql
String sql = "update account set money = 3000 where id = 1";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
int count = stmt.executeUpdate(sql);//执行完DML语句,返回受影响的行数
//6. 处理结果
//System.out.println(count);
if(count > 0){
System.out.println("修改成功~");
}else{
System.out.println("修改失败~");
}
//7. 释放资源
stmt.close();
conn.close();
}
3.2 执行DDL语句
executeUpdate(sql);
代码示例
public void testDDL() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "drop database db2";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//5. 执行sql
int count = stmt.executeUpdate(sql);//执行完DDL语句,可能是0
//6. 处理结果
System.out.println(count);
//7. 释放资源
stmt.close();
conn.close();
}
3.3 执行DQL语句
ResultSet(结果集对象)
作用: 封装了SQL查询语句的结果。
ResultSet executeQuery(sql):执行DQL 语句,返回 ResultSet 对象
1. boolean next()
将光标从当前位置向前移动一行
判断当前行是否为有效行
方法返回值说明:
true : 有效行,当前行有数据
false : 无效行,当前行没有数据
2. xxx getXxx(参数):获取数据
xxx : 数据类型;如: int getInt(参数) ;String getString(参 数)
代码示例
public void testResultSet() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "select * from account";
//4. 获取statement对象
Statement stmt = conn.createStatement();
//5. 执行sql
ResultSet rs = stmt.executeQuery(sql);
//6. 处理结果, 遍历rs中的所有数据
// 6.1 光标向下移动一行,并且判断当前行是否有数据
while (rs.next()){
//6.2 获取数据 getXxx()
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
System.out.println(id);
System.out.println(name);
System.out.println(money);
}
//7. 释放资源
rs.close();
stmt.close();
conn.close();
}
3.4 案例
需求:
查询account账户表数据,封装为Account对象中,并且 存储到ArrayList集合中
步骤
查询account账户表数据,封装为Account对象中,并且存储 到ArrayList集合中
* 1. 定义实体类Account
* 2. 查询数据,封装到Account对象中
* 3. 将Account对象存入ArrayList集合中
代码示例
public void testResultSet2() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn =DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "select * from account";
//4. 获取statement对象
Statement stmt = conn.createStatement();
//5. 执行sql
ResultSet rs = stmt.executeQuery(sql);
// 6.创建集合
List<Account> list = new ArrayList<>();
// 6.1 光标向下移动一行,并且判断当前行是否有数据
while (rs.next()){
Account account = new Account();
//6.2 获取数据 getXxx()
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
//6.3 赋值
account.setId(id);
account.setName(name);
account.setMoney(money);
//6.4 存入集合
list.add(account);
}
System.out.println(list);
//7. 释放资源
rs.close();
stmt.close();
conn.close();
}
4. SQL注入
4.1 SQL注入的原因
SQL注入是通过操作输入来修改事先定义好的SQL语句,用以达 到执行代码对服务器进行攻击的方法。
用户名随意写,密码写成 ' or '1' ='1
代码示例
// 接收用户输入 用户名和密码
String name = "sjdljfld";
String pwd = "' or '1' = '1";
String sql = "select * from tb_user where
username = '"+name+"' and password = '"+pwd+"'";
// 获取stmt对象
Statement stmt = conn.createStatement();
// 执行sql
ResultSet rs = stmt.executeQuery(sql);
String sql = "select * from tb_user where username = '"+name+"' and password = '"+pwd+"'";
在这里密码写成 ' or '1' ='1,where后面的条件就会永远是true, 不论用户名是什么,都会成功执行sql
注入原因 字符串的拼接问题
4.2 PreparedStatement概述
PreparedStatement作用:
预编译SQL语句并执行:预防SQL注入问题
4.2.1 获取 PreparedStatement 对象
SQL语句中的参数值,使用占位符 ?替代。
String sql = "select * from user where username = ? and password = ?";
通过Connection对象获取,并传入对应的sql语句
PreparedStatement pstmt = conn.prepareStatement(sql);
4.2.2 设置参数值
PreparedStatement对象:setXxx(参数1,参数2):给 ? 赋 值
Xxx:数据类型 ;
如 setInt (参数1,参数2)
参数1是 ?的位置编号,从1 开始
参数2是 ?的值
4.2.3 执行SQL语句
executeUpdate(); 执行DDL语句和DML语句
executeQuery(); 执行DQL语句
调用这两个方法时不需要传递SQL语句,因为获取SQL语 句执行对象时已经对SQL语句进行预编译了。
4.2.4 使用PreparedStatement改进
public void testPreparedStatement() throws Exception {
//获取连接:如果连接的是本机mysql并且端口是默认的3306 可以简化书写
String url = "jdbc:mysql:///db1?useSSL=false";
String username = "root";
String password = "1234";
Connection conn = DriverManager.getConnection(url, username, password);
// 接收用户输入 用户名和密码
String name = "zhangsan";
String pwd = "' or '1' = '1";
// 定义sql
String sql = "select * from tb_user where username = ? and password = ?";
// 获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
// 设置?的值
pstmt.setString(1,name);
pstmt.setString(2,pwd);
// 执行sql
ResultSet rs = pstmt.executeQuery();
// 判断登录是否成功
if(rs.next()){
System.out.println("登录成功~");
}else{
System.out.println("登录失败~");
}
//7. 释放资源
rs.close();
pstmt.close();
conn.close();
}
4.2.5 PreparedStatement防止注入的原理
将敏感字符进行转义
'\'or \'1\' = \'1'
5. 数据库连接池
5.1 数据库连接池简介
数据库连接池是个容器,负责分配、管理数据库连接
作用:
资源重用
提升系统响应速度
避免数据库连接遗漏
连接池是在一开始就创建好了一些连接(Connection)对象存储起 来。用户需要连接数据库时,不需要自己创建连接,而只需要从连 接池中获取一个连接进行使用,使用完毕后再将连接对象归还给连 接池;这样就可以起到资源重用,也节省了频繁创建连接销毁连接 所花费的时间,从而提升了系统响应的速度。
5.2 数据库连接池实现
标准接口:DataSource
实现此接口的方法
Connection getConnection()
以后就不需要通过 DriverManager 对象获取 Connection 对象,而是通过连接池(DataSource)获取 Connection 对 象。
Druid(德鲁伊)
Druid连接池是阿里巴巴开源的数据库连接池项目
功能强大,性能优秀,是Java语言最好的数据库连接池之一
5.3 Driud使用
导入jar包 druid-1.1.12.jar
定义配置文件
加载配置文件
获取数据库连接池对象
获取连接
5.3.1 Driud数据库连接池的实现
代码示例
public class DruidDemo {
public static void main(String[] args) throws Exception {
//1.导入jar包
//2.定义配置文件
//3. 加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("druid.properties"));
//4. 获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//5. 获取数据库连接 Connection
Connection connection = dataSource.getConnection();
System.out.println(connection);
//获取到了连接后就可以继续做其他操作了
}
}
6. 练习
需求
完成商品品牌数据的增删改查操作
查询:查询所有数据
添加:添加品牌
修改:根据id修改
删除:根据id删除
1. 创建数据库表 tb_brand
2. 在pojo包(javaBean类存放的包)下实体类 Brand
6.1 查询数据
public void testSelectAll() throws Exception {
//3. 加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("jdbcdemo/src/druid.properties"));
//4. 获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//5. 获取数据库连接 Connection
Connection conn = dataSource.getConnection();
//6. 定义SQL
String sql = "select * from tb_brand";
//7. 获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//8. 执行SQL
ResultSet rs = pstmt.executeQuery();
//9. 处理结果 List<Brand> 封装Brand对象,装载List集合
Brand brand = null;
List<Brand> brands = new ArrayList<>();
while (rs.next()){
//获取数据
int id = rs.getInt("id");
String brandName = rs.getString("brand_name");
String companyName = rs.getString("company_name");
int ordered = rs.getInt("ordered");
String description = rs.getString("description");
int status = rs.getInt("status");
//封装Brand对象
brand = new Brand();
//赋值
brand.setId(id);
brand.setBrandName(brandName);
brand.setCompanyName(companyName);
brand.setOrdered(ordered);
brand.setDescription(description);
brand.setStatus(status);
//装载集合
brands.add(brand);
}
System.out.println(brands);
//10. 释放资源
rs.close();
pstmt.close();
conn.close();
}
6.2 添加数据
public void testAdd() throws Exception {
// 接收页面提交的参数
String brandName = "香飘飘";
String companyName = "香飘飘";
int ordered = 1;
String description = "绕地球一圈";
int status = 1;
//加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("jdbcdemo/src/druid.properties"));
//获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//获取数据库连接 Connection
Connection conn = dataSource.getConnection();
//定义SQL
String sql = "insert into tb_brand(
brand_name,
company_name,
ordered,
description,
status)
values(?,?,?,?,?)";
//获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//设置参数
pstmt.setString(1,brandName);
pstmt.setString(2,companyName);
pstmt.setInt(3,ordered);
pstmt.setString(4,description);
pstmt.setInt(5,status);
//执行SQL
int count = pstmt.executeUpdate(); // 影响的行数
//处理结果
System.out.println(count > 0);
//释放资源
pstmt.close();
conn.close();
}
6.3 修改数据
6.4 删除数据
public void testDeleteById() throws Exception {
// 接收页面提交的参数
int id = 4;
//获取Connection
//加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("jdbcdemo/src/druid.properties"));
//获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//获取数据库连接 Connection
Connection conn = dataSource.getConnection();
//定义SQL
String sql = " delete from tb_brand where id = ?";
//获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//设置参数
pstmt.setInt(1,id);
//执行SQL
int count = pstmt.executeUpdate(); // 影响的行数
//处理结果
System.out.println(count > 0);
//释放资源
pstmt.close();
conn.close();
}