JDBC
事务
事务的特性 – 概念比较重要!!
A 原子性 – 事务不可拆分 一组sql不可以拆分 要么全部成功 要么全部失败!!!
C 一致性 – 事务前后操作 数据要保持一致 转账 准过去500 你就收到了500 转账失败 你就收不到钱!!!
I 隔离性 – (isolation 记住) 事务与事务之间不产生影响!!!
D 持久性 – 事务一旦结束 commit rollback – 执行的sql语句就是真的操作了数据库 数据库中的数据 就永久的发生了变化!!!
事务的隔离性
多个事务共同操作同一个数据时产生的一些问题
事务的安全性问题
脏读: 一个事务读到了另一个事务未提交的数据,导致查询结果不一致 (不允许出现的)
**不可重复读:**一个事务读到了另一个事务已经提交的update的数据,导致多次查询结果不一致。
**虚读/幻读:**一个事务读到了另一个事务已经提交的insert delete的数据,导致多次查询结果不一致。
如何解决事务的安全性问题
语法
set session transaction isolation level 级别字符串
**read uncommitted:**脏读,不可重复读,虚读都有可能发生
**read committed:**避免脏读。但是不可重复读和虚读是有可能发生 oracle数据库 默认的隔离级别
**repeatable read:**避免脏读和不可重复读,但是虚读有可能发生。mysql的默认隔离级别
**serializable:**避免脏读,不可重复读,虚读。相当于单线程!! – 相率那是相当的低!!!
效率问题
隔离级别越高 效率越低 安全性越高
查看隔离级别
select @@tx_isolation;
隔离性
脏读
脏读:指一个事务读取了另外一个事务.未提交的数据; – 数据库中是属于非常严重问题
set session transaction isolation level read uncommitted;
脏读&演示不可重复读
**不可重复读:**一个事务读到了另一个事务已经提交的update的数据,导致多次查询结果不一致。
SET SESSION TRANSACTION ISOLATION LEVEL read committed;
不可重复读&不可重复读的解决
SET SESSION TRANSACTION ISOLATION LEVEL repeatable read;
两次读取到的结果一致,已经成功的避免了不可重复读
序列化/串行化 为什么能够解决所有的安全性问题?
所有的安全性问题都是因为事务的并发产生的!!!
序列化/串行化不允许多个事务并发执行!!! – 效率 也太低了吧!!!
避免虚读
**虚读/幻读:**一个事务读到了另一个事务已经提交的insert的数据,导致多次查询结果不一致。
由于无法演示虚读,在这里我们只去看一如何解决虚读的问题
不可重复读 – 一个事务读到了另一个事务已经提交的update的数据,导致两次查询的结果不一致
虚读/幻读 – 一个事务读到了另一个事务已经提交的insert/delete的数据,导致两次查询的结果不一致
虚读和不可重复读的区别:
虚读 强调的是数据表 记录数 的变化,主要是 insert 和 delete 语句。
不可重复读 强调的是数据表 内容 的变化,主要是 update 语句。
set session transaction isolation level serializable;
事务特性:
a 原子性 – 事务不可拆分 一组sql要么全部成功 要么全部失败
c 一致性 – 事务结束后 执行的结果前后一致 转500 这500不能少 另外一个人收到500
i 隔离性 – 事务与事务之间的影响
d 持久性 – 事务结束后 所执行的sql语句 就永久性的改变了数据库中的数据
隔离性安全性问题
脏读 – 一个事务读到了另一个事务还没有提交(正在编辑)的数据
不可重复读 – 一个事务读到了另一个事务提交了的update的数据,导致两次查询的结果不一致
虚读/幻读 – 一个事务读到了另一个事务提交了的delete,insert的数据,导致两次查询的结果不一致
隔离级别
read uncommitted – 脏读 不可重复读 虚读 都会发生
read committed – 解决脏读问题 ---- 不可重复读 虚读 会发生
repeatable read – 解决脏读 不可重复读 ------ 虚读 还会发生
serializable – 序列化 将所有的安全性问题全部解决.
从上往下 安全性越来越高 执行效率越来越低
2.JDBC的概述
什么是JDBC
JDBC其实就是SUN公司提供的一个接口规范,可以让我们java程序员使用java语言连接到数据库。
JDBC是Java访问数据库的标准规范,真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用JDBC接口中的方法即可,数据库驱动由数据库厂商提供。
数据库厂商 只要实现这套规范就可以了!!!
实现规范中的方法 – 程序员只要去看一下接口就可以了!!!
mysql oracle 其他数据库 他们都实现了 JDBC这一套规范 意味着什么?
意味着程序员只需要学习jdbc这一套规范 就可以操作 不同的数据库!!!
什么使用数据库的驱动
数据驱动就是一套java代码 实现了jdbc的规范 可以用来操作数据库
不同的数据库会提供不同的驱动!!! – 数据库厂商提供的
MySql与java是两个不同的程序,那么之间如何进行访问呢?
电脑如果想要使用驱动,那么我们需要下载显卡驱动才能够使用.
如果java程序想要使用数据库我们需要下载数据库驱动 才能够使用.
驱动:两个设备(应用)之间通信的桥梁。
2.3使用JDBC的好处
-
程序员如果要开发访问数据库的程序,只需要会调用JDBC接口中的方法即可,不用关注类是如何实现的。
-
使用同一套Java代码,进行少量的修改就可以访问其他JDBC支持的数据库
1,减少了你的学习成本
2,能够使用同一套代码操作不同的数据库
jdbc 是sun公司提供的一套规范
数据库的厂商 实现这套规范 并且实现规范中的功能
作为程序员 我们只需要按照接口中的规范调用方法就可以了
具体方法的实现 是由数据库厂商实现的
3.JDBC入门
3.1环境准备
JDBC – java提供了一套规范,是用来操作数据库的.
3.1.1准备sql语句
– 1.注册驱动
–2.获得连接
–3.基本操作 – 增删改查
– 4.释放资源
create database jdbctest;
use jdbctest;
create table user(
id int primary key auto_increment,
username varchar(20),
password varchar(20),
nickname varchar(20),
age int
);
insert into user values (null,'aaa','123','小丽',34);
insert into user values (null,'bbb','123','大王',32);
insert into user values (null,'ccc','123','小明',28);
insert into user values (null,'ddd','123','大黄',21);
3.1.2导入驱动jar包
-
创建lib文件夹
-
将mysql驱动jar包复制到lib文件夹下
-
将jar包添加成库
3.2.1开发步骤
- 注册驱动
- 获得连接
- 基本操作
- 释放资源
3.2.2代码实现
使用JDBC创建一张表
package com.test.jdbc01; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class JdbcDemo { public static void main(String[] args) throws ClassNotFoundException, SQLException { // 1.加载驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获得连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbctest", "root", "root"); // 3.基本操作:执行SQL // 3.1获得执行SQL语句的对象 Statement statement = conn.createStatement(); // 3.2编写SQL语句: String sql = "select * from user"; // 3.3执行SQL: ResultSet rs = statement.executeQuery(sql); // 3.4遍历结果集: while(rs.next()){ System.out.print(rs.getInt("id")+" "); System.out.print(rs.getString("username")+" "); System.out.print(rs.getString("password")+" "); System.out.print(rs.getString("nickname")+" "); System.out.print(rs.getInt("age")); System.out.println(); } // 4.释放资源 rs.close(); statement.close(); conn.close(); } }
4.JDBC之API详解
4.1JDBC之核心API
接口/类 | 作用 |
---|---|
DriverManager类 | 1.管理和注册数据库驱动 2.得到数据库连接对象 |
Connection接口 | 一个连接对象,可用于创建Statement和PreparedStatement对象 |
Statement接口 | 一个SQL语句对象,用于将SQL语句发送给数据库服务器。 |
PreparedStatemen接口 | 一个SQL语句对象,是Statement的子接口 |
ResultSet接口 | 用于封装数据库查询的结果集,返回给客户端Java程序 |
4.1.1DriverManager:驱动管理
4.1.1.1作用一:注册驱动
打开JDK的API文档 搜索DriverManager类
这个方法可以完成驱动的注册,但是实际开发中一般不会使用这个方法完成驱动的注册!!!
原因:
如果需要注册驱动,就会使用DriverManager.registerDriver(new Driver());,但是查看源代码发现,在代码中有一段静态代码块,静态代码块已经调用了注册驱动的方法。
当我们调用registerDriver方法传入参数 new Driver() 时就会立马调用Driver类中的静态代码块!
而在Driver静态代码块中 又注册了一次驱动,所以我们使用 registerDriver 方法注册驱动时,会注册两次驱动.
而我们只要使用到 Driver 这个类的时候,就可以执行到静态代码块.
所以我们一般会采用这种方式去注册.
4.1.1.2作用二:获得连接
这个方法就是用来获得与数据库连接的方法:这个方法中有三个参数:
参数 | 作用 |
---|---|
url | 与数据库连接的路径 |
user | 与数据库连接的用户名 |
password | 与数据库连接的密码 |
主要关注的是url的写法:
jdbc:mysql://localhost:3306/jdbctest
url字段 | 作用 |
---|---|
jdbc | 连接数据库的协议 |
mysql | 是jdbc的子协议 |
localhost | 连接的MySQL数据库服务器的主机地址.(连接是本机就可以写成localhost),如果连接不是本机的,就需要写上连接主机的IP地址。 |
3306 | MySQL数据库服务器的端口号 |
jdbctest | 数据库名称 |
url如果连接的是本机的路径,可以简化为如下格式:
jdbc:mysql:///jdbctest
4.1.2Connection:与数据库连接对象
4.1.2.1作用一:创建执行sql语句的对象
通过这两个方法,在这里产生了两个对象.
执行sql的对象 | 作用 |
---|---|
Statement | 执行SQL |
PreparedStatement | 执行SQL.对SQL进行预处理。解决SQL注入漏洞。 |
4.1.2.2作用二:事务管理
false开启事务
提交事务
回滚事务
4.1.3 Statement:执行SQL
执行sql语句
ResultSet executeQuery(String sql);
执行查询(执行select语句)。
int executeUpate(String sql);
执行修改,添加,删除的SQL语句。
返回值int代表的是影响数据库中的行数.
4.1.4ResultSet:结果集对象
通过select语句的查询结果集对象。
遍历结果集使用的方法
获取结果使用得方法
结果集获取可以使用结果集中的:
getXXX();方法通常都会有一个重载的方法。
getXXX(int columnIndex);通过位置获取对应的值
getXXX(String columnName);通过字段获取指定的值
结果集遍历原理
5.JDBC之CRUD操作(增删改查)
5.1添加数据
需求:往数据库中添加一条数据
insert into user values (null,'eee','123','阿黄',21)
代码实现
@Test
/**
* 保存操作的代码实现
*/
public void demo1(){
Connection conn = null;
Statement stmt = null;
try{
// 注册驱动:
Class.forName("com.mysql.jdbc.Driver");
// 获得连接:
conn = DriverManager.getConnection("jdbc:mysql:///jdbctest", "root", "root");
// 执行操作:
// 创建执行SQL语句对象:
stmt = conn.createStatement();
// 编写SQL语句:
String sql = "insert into user values (null,'eee','123','阿黄',21)";
// 执行SQL语句:
int num = stmt.executeUpdate(sql);
if(num > 0){
System.out.println("保存用户成功!!!");
}
}catch(Exception e){
e.printStackTrace();
}finally{
// 资源释放:
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
5.2删除数据
需求:删除第五条数据
delete from user where id = 5
代码实现
@Test
/**
* 删除操作的代码实现
*/
public void demo3(){
Connection conn = null;
Statement stmt = null;
try{
// 注册驱动:
Class.forName("com.mysql.jdbc.Driver");
// 获得连接:
conn = DriverManager.getConnection("jdbc:mysql:///jdbctest", "root", "root");
// 创建执行SQL语句对象:
stmt = conn.createStatement();
// 编写SQL:
String sql = "delete from user where id = 5";
// 执行SQL:
int num = stmt.executeUpdate(sql);
if(num > 0){
System.out.println("删除用户成功!!!");
}
}catch(Exception e){
e.printStackTrace();
}finally{
// 资源释放:
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
5.3修改数据
需求:将第四条数据nickname修改为旺财
update user set password='abc',nickname='旺财' where id = 4
代码实现
@Test
/**
* 修改操作的代码实现
*/
public void demo2(){
Connection conn = null;
Statement stmt = null;
try{
// 注册驱动:
Class.forName("com.mysql.jdbc.Driver");
// 获得连接
conn = DriverManager.getConnection("jdbc:mysql:///jdbctest", "root", "root");
// 执行操作:
// 创建执行SQL语句的对象:
stmt = conn.createStatement();
// 编写SQL语句:
String sql = "update user set password='abc',nickname='旺财' where id = 4";
// 执行SQL语句:
int num = stmt.executeUpdate(sql);
if(num > 0){
System.out.println("修改用户成功!!!");
}
}catch(Exception e){
e.printStackTrace();
}finally{
// 资源释放:
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
5.4查询数据
需求:查询所有数据并遍历
select * from user
代码实现
@Test
/**
* 查询多条记录
*/
public void demo4(){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 获得连接
conn = DriverManager.getConnection("jdbc:mysql:///jdbctest", "root", "root");
// 执行操作
// 创建执行SQL语句的对象:
stmt = conn.createStatement();
// 编写SQL:
String sql = "select * from user";
// 执行SQL:
rs = stmt.executeQuery(sql);
// 遍历结果集:
while(rs.next()){
System.out.println(rs.getInt("id")+" "+rs.getString("username")+" "+rs.getString("password"));
}
}catch(Exception e){
e.printStackTrace();
}finally{
// 资源释放:
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
}
6.JDBC工具类的抽取
因为传统JDBC的开发,注册驱动,获得连接,释放资源这些代码都是重复编写的。所以可以将重复的代码提取到一个类中来完成。
什么时候自己创建工具类?
如果一个功能经常要用到,我们建议把这个功能做成一个工具类,可以在不同的地方重用。
需求:将JDBC中重复的代码抽取出来
步骤:
- 可以把几个字符串定义成常量:用户名,密码,URL,驱动类
- 得到数据库的连接:getConnection()
- 关闭所有打开的资源:
代码实现
package com.test.utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
public class JDBCUtils {
private static final String DRIVER_CLASSNAME;
private static final String URL;
private static final String USERNAME;
private static final String PASSWORD;
static{
DRIVER_CLASSNAME="com.mysql.jdbc.Driver";
URL="jdbc:mysql:///web_test3";
USERNAME="root";
PASSWORD="abc";
}
/**
* 注册驱动的方法
*/
public static void loadDriver(){
try {
Class.forName(DRIVER_CLASSNAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获得连接的方法
*/
public static Connection getConnection(){
Connection conn = null;
try{
// 将驱动一并注册:
loadDriver();
// 获得连接
conn = DriverManager.getConnection(URL,USERNAME, PASSWORD);
}catch(Exception e){
e.printStackTrace();
}
return conn;
}
/**
* 释放资源的方法
*/
public static void release(Statement stmt, Connection conn){
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
public static void release(ResultSet rs, Statement stmt, Connection conn){
// 资源释放:
if(rs != null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
release(stmt,conn);
}
}
JDBC工具类的测试
需求:查询所有数据
代码
package com.test.jdbc02;
import com.itheima.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCDemo {
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1.获得连接
conn = JDBCUtils.getConnection();
//2.基本操作
//2.1获取操作sql的对象
stmt = conn.createStatement();
//2.2创建sql语句
String sql = "select * from user";
//2.3执行sql语句
rs = stmt.executeQuery(sql);
//2.4遍历结果集
while (rs.next()){
System.out.println(rs.getInt("id")+" "+rs.getString("username")+" "+rs.getString("password"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//释放资源
JDBCUtils.release(rs,stmt,conn);
}
}
}
7.JDBC的配置信息提取到配置文件
7.1配置文件
属性文件
格式:扩展名是.properties
内容:key=value
在后面的学习中我们大多使用 XML文件 作为配置文件
7.2提取信息到配置文件
- 首先创建一个配置文件
2.将信息提取到配置文件
7.3在工具类中解析配置文件
代码
package com.test.utils;
import org.junit.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.ResultSet;
import java.util.Properties;
public class JDBCUtils {
private static final String DRIVER_CLASSNAME;
private static final String URL;
private static final String USERNAME;
private static final String PASSWORD;
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\db.properties"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
DRIVER_CLASSNAME = properties.getProperty("driverClassName");
URL = properties.getProperty("url");
USERNAME = properties.getProperty("username");
PASSWORD = properties.getProperty("password");
}
/**
* 注册驱动的方法
*/
public static void loadDriver() {
try {
Class.forName(DRIVER_CLASSNAME);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获得连接的方法
*/
public static Connection getConnection() {
Connection conn = null;
try {
// 将驱动一并注册:
loadDriver();
// 获得连接
conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
/**
* 释放资源的方法
*/
public static void release(Statement stmt, Connection conn) {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
stmt = null;
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
conn = null;
}
}
public static void release(ResultSet rs, Statement stmt, Connection conn) {
// 资源释放:
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
release(stmt, conn);
}
}
测试代码
package com.test.jdbc02;
import com.itheima.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCDemo {
public static void main(String[] args){
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//1.获得连接
conn = JDBCUtils.getConnection();
//2.基本操作
//2.1获取操作sql的对象
stmt = conn.createStatement();
//2.2创建sql语句
String sql = "select * from user";
//2.3执行sql语句
rs = stmt.executeQuery(sql);
//2.4遍历结果集
while (rs.next()){
System.out.println(rs.getInt("id")+" "+rs.getString("username")+" "+rs.getString("password"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//释放资源
JDBCUtils.release(rs,stmt,conn);
}
}
}
8.JDBC的SQL注入漏洞问题
我们可以一起做一个登录案例来看一下注入漏洞的问题.
8.1完成登录案例
8.1.1环境准备
- 准备一张用户表
- 添加几条用户记录
8.1.2完成登录案例
1)得到用户从控制台上输入的用户名和密码来查询数据库
2) 写一个登录的方法
- 通过工具类得到连接
- 创建语句对象,使用拼接字符串的方式生成SQL语句
- 查询数据库,如果有记录则表示登录成功,否则登录失败
- 释放资源
代码
package com.test.login;
import com.test.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class LoginDemo {
public static void main(String[] args) {
//1)得到用户从控制台上输入的用户名和密码来查询数据库
Scanner sc = new Scanner(System.in);
System.out.println("请输入您的用户名");
String username = sc.nextLine();
System.out.println("请输入你的密码");
String password = sc.nextLine();
login(username,password);
}
//2) 写一个登录的方法
private static void login(String username, String password) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//2.1 通过工具类得到连接
conn = JDBCUtils.getConnection();
//2.2 创建语句对象,使用拼接字符串的方式生成SQL语句
String sql = "select * from user where username='"+ username +"' and password = '"+ password +"'";
//2.3 查询数据库,如果有记录则表示登录成功,否则登录失败
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
if (rs.next()){
System.out.println(rs.getString("nickname")+",欢迎您的访问");
}else{
System.out.println("登陆失败!");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//2.4 释放资源
JDBCUtils.release(rs,stmt,conn);
}
}
}
8.1.3演示注入漏洞
8.1.3.1什么是注入漏洞?
在盘古开天辟地之时…
在早期互联网上SQL注入漏洞普遍存在。有一个网站,用户需要进行注册,用户注册以后根据用户名和密码完成登录。假设现在用户名已经被其他人知道了,但是其他人不知道你的密码,也可以登录到网站上进行相应的操作。
8.1.3.1注入漏洞演示
如果我们正确的输入账号密码,就能查询出正确的语句
select * from user where username='aaa' and password = '123'
8.1.3.1.1第一种情况
在这里我们回顾一下mysql第一天关于sql语句的知识点
and 相当于 java中的 &&
or 相当于java中的 ||
而 – 则是sql语句中的注释
现在我们使用以上知识点的方式再去查询一次,现在假装我们不知道密码
SELECT * FROM USER WHERE username='bbb' OR 1='1' AND PASSWORD = '345'
因为现在的关联条件是or,所以只要用户名正确就可以了.
8.1.3.1.2第二种情况
再试一下使用注释的方式
SELECT * FROM USER WHERE username='ccc' -- AND PASSWORD = '345'
现在我们发现 用户名往后的地方都变成了注释,sql不再去执行后面的密码部分,所以只要用户名正确依然能够查询出来!
8.1.3.1.3第一种情况代码演示
现在我们使用以上两种方式再去测试一下程序
使用or的方式
我们要输入的用户名不再是直接输入用户名而是要输入 username’ 后面的部分
8.1.3.1.4第二种情况代码演示
使用注释的方式
现在我们再去输入账号的时候需要加一个注释了
注意 – 后面必须要有空格
我们让用户输入的密码和SQL语句进行字符串拼接。用户输入的内容作为了SQL语句语法的一部分,改变了原有SQL真正的意义,以上问题称为SQL注入。要解决SQL注入就不能让用户输入的密码和我们的SQL语句进行简单的字符串拼接。
如何结局注入漏洞的问题呢?这就需要用到接下来我们学习到的知识点了
9.PreparedStatement接口
9.1PreparedStatement简介
9.1.1PreparedStatement的好处
一.代码的可读性和可维护性.
//statement操作插入数据
stmt.executeUpdate("insert into user values(null,'"+username+"','"+password+"','"+nickname+"',"+age+")");
//PreparedStatement操作插入数据
perstmt = con.prepareStatement("insert into user values (null,?,?,?,?)");
perstmt.setString(1,username);
perstmt.setString(2,password);
perstmt.setString(3,nickname);
perstmt.setString(4,age);
perstmt.executeUpdate();
二.PreparedStatement尽最大可能提高性能
数据库的编译器会编译使用PreparedStatement处理的sql语句并缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中.
三.最重要的一点是极大地提高了安全性.
使用字符串拼接的方式操作数据库,本身就相当于用户直接操作sql语句.只要稍微懂一些sql语句,就可以写一些恶意的sql语句,产生sql注入漏洞的问题.
而如果你使用预编译语句.你传入的任何内容就不会和原来的语句发生任何匹配的关系.就解决了这个问题.
9.1.2API简介
PreparedStatement是Statement接口的子接口,继承于父接口中所有的方法。会对sql进行预编译.
Connection创建PreparedStatement对象
Connection接口中的方法 | 作用 |
---|---|
PreparedStatement prepareStatement(String sql) | 指定预编译的SQL语句,SQL语句中使用占位符? 创建一个语句对象 |
PreparedStatement接口中的方法:
PreparedStatement接口中的方法 | 作用 |
---|---|
int executeUpdate() | 增删改的操作,返回影响的行数。 |
ResultSet executeQuery() | 查询的操作,返回结果集 |
void setDouble(int parameterIndex, double x) | 将指定参数设置为给定 double 值。 |
void setFloat(int parameterIndex, float x) | 将指定参数设置为给定 float 值。 |
void setInt(int parameterIndex, int x) | 将指定参数设置为给定int 值。 |
void setLong(int parameterIndex, long x) | 将指定参数设置为给定long 值。 |
void setObject(int parameterIndex, Object x) | 使用给定对象设置指定参数的值。 |
void setString(int parameterIndex, String x) | 将指定参数设置为给定String 值。 |
9.2修改登录案例
使用PreparedStatement的步骤
- 编写SQL语句,未知内容使用?占位:“SELECT * FROM user WHERE name=? AND password=?”;
- 获得PreparedStatement对象
- 设置实际参数:setXxx(占位符的位置, 真实的值)
- 执行参数化SQL语句
- 关闭资源
修改案例
package com.itheima.login1;
import com.itheima.utils.JDBCUtils;
import java.sql.*;
import java.util.Scanner;
public class LoginDemo {
public static void main(String[] args) {
//1)得到用户从控制台上输入的用户名和密码来查询数据库
Scanner sc = new Scanner(System.in);
System.out.println("请输入您的用户名");
String username = sc.nextLine();
System.out.println("请输入你的密码");
String password = sc.nextLine();
login(username,password);
}
//2) 写一个登录的方法
private static void login(String username, String password) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
//2.1 通过工具类得到连接
conn = JDBCUtils.getConnection();
//2.2 创建语句对象,使用拼接字符串的方式生成SQL语句
String sql = "select * from user where username = ? and password = ?";
//2.3 查询数据库,如果有记录则表示登录成功,否则登录失败
//预编译sql
pstmt = conn.prepareStatement(sql);
//设置参数 1代表第一个? 2代表第二个?
pstmt.setString(1,username);
pstmt.setString(2,password);
rs = pstmt.executeQuery();
if (rs.next()){
System.out.println(rs.getString("nickname")+",欢迎您的访问");
}else{
System.out.println("登陆失败!");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//2.4 释放资源
JDBCUtils.release(rs,pstmt,conn);
}
}
}
9.3数据查询操作
9.3.1案例一
需求:使用PreparedStatement查询一条数据,封装成一个学生Student对象
环境准备
CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
age INT,
gender VARCHAR(2),
birthday DATE
);
INSERT INTO student VALUES(NULL,'孙悟空',28,'男','1990-02-02');
INSERT INTO student VALUES(NULL,'白骨精',27,'女','1991-02-02');
INSERT INTO student VALUES(NULL,'猪八戒',18,'男','2000-02-02');
INSERT INTO student VALUES(NULL,'嫦娥',18,'男','1990-05-20');
代码实现
实现Student类
package com.itheima.beans;
import java.util.Date;
public class Student {
private int id;
private String name;
private int age;
private String gender;
private Date date;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", date=" + date +
'}';
}
}
查询代码
package com.itheima.jdbc03;
import com.itheima.beans.Student;
import com.itheima.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCDemo {
public static void main(String[] args) {
//创建学生对象
Student student = new Student();
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
//获得连接
conn = JDBCUtils.getConnection();
//编写sql语句
String sql = "select * from student";
//预编译sql
pstmt = conn.prepareStatement(sql);
//执行sql
rs = pstmt.executeQuery();
if(rs.next()){
//将数据封装到Student对象中
student.setId(rs.getInt("id"));
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
student.setGender(rs.getString("gender"));
student.setDate(rs.getDate("brithday"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//释放资源
JDBCUtils.release(rs,pstmt,conn);
}
//输出学生对象,查看结果
System.out.println(student);
}
}
9.3.2案例二
需求:查询所有的学生类,封装成List返回
步骤
- 创建一个集合用来存放Student对象
- 使用JDBC查询所有的学生数据
- 每一条数据都创建一个学生对象来存储
- 将学生对象添加到集合
- 释放资源
- 打印输出集合内容
代码实现
package com.itheima.pretest;
import com.itheima.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class PreparedStatementTest_02 {
public static void main(String[] args) {
//需求:查询所有的学生数据,封装成List<Student>返回
List<Student> studentList = getStudentList();
System.out.println(studentList);
}
public static List<Student> getStudentList(){
//1.创建一个集合对象
List<Student> stuList = new ArrayList<Student>();
//2.获得连接
Connection conn = JDBCUtils.getConnection();
PreparedStatement pstmt = null;
ResultSet rs= null;
try { //3.基本操作
//3.1准备sql
String sql = "select * from student";
//3.2预编译sql
pstmt = conn.prepareStatement(sql);
//3.3设置参数
//3.4执行sql 获得rs结果集对象
rs = pstmt.executeQuery();
//3.5遍历结果集 拿到每一条记录 对应一个学生对象
while(rs.next()){
//每一次循环 都要创建一个学生对象 然后封装每一条记录的数据
Student stu = new Student();
stu.setId(rs.getInt("id"));
stu.setName(rs.getString("name"));
stu.setAge(rs.getInt("age"));
stu.setGender(rs.getString("gender"));
stu.setDate(rs.getDate("birthday"));
//数据封装完成后 学生对象 -- 存储到集合中
stuList.add(stu);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
//4.释放资源
JDBCUtils.release(rs,pstmt,conn);
}
//5.返回集合对象
return stuList;
}
}
9.3.3案例三
需求:定义三个方法
insert() 插入数据
update() 修改数据
delete() 删除数据
代码实现
完成insert()方法
private static void insert() {
Connection conn = null;
PreparedStatement pstmt = null;
try {
//获得连接
conn = JDBCUtils.getConnection();
//准备sql语句
String sql = "insert into student values(null,?,?,?,?)";
//预编译sql
pstmt = conn.prepareStatement(sql);
//插入参数
pstmt.setString(1,"黑熊精");
pstmt.setInt(2,18);
pstmt.setString(3,"男");
//java.sql.Date 注意这里的date是 sql包下的
pstmt.setDate(4, Date.valueOf("1999-03-12"));
//执行sql
int i = pstmt.executeUpdate();
System.out.println("插入了" + i + "条数据");
} catch (SQLException e) {
e.printStackTrace();
}finally {
//释放资源
JDBCUtils.release(pstmt,conn);
}
}
完成update()方法
需求:将第五条数据的姓名以及生日做修改
private static void update() {
Connection conn = null;
PreparedStatement pstmt = null;
try {
//获得连接
conn = JDBCUtils.getConnection();
//准备sql语句
String sql = "update student set name = ?,birthday = ? where id = ?";
//预编译sql
pstmt = conn.prepareStatement(sql);
//插入参数
pstmt.setString(1, "白龙马");
//java.sql.Date 注意这里的date是 sql包下的
pstmt.setDate(2, Date.valueOf("2000-03-12"));
pstmt.setInt(3, 5);
//执行sql
int i = pstmt.executeUpdate();
System.out.println("修改了" + i + "条数据");
} catch (SQLException e) {
e.printStackTrace();
} finally {
//释放资源
JDBCUtils.release(pstmt, conn);
}
}
效果
完成delete() 删除数据方法
需求:删除第一条记录
delete()方法实现
private static void delete() {
Connection conn = null;
PreparedStatement pstmt = null;
try {
//获得连接
conn = JDBCUtils.getConnection();
//准备sql语句
String sql = "delete from student where id = ?";
//预编译sql
pstmt = conn.prepareStatement(sql);
//插入参数
pstmt.setInt(1, 1);
//执行sql
int i = pstmt.executeUpdate();
System.out.println("删除了" + i + "条数据");
} catch (SQLException e) {
e.printStackTrace();
} finally {
//释放资源
JDBCUtils.release(pstmt, conn);
}
}
效果:
完整代码
package com.itheima.jdbc05;
import com.itheima.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCDemo {
public static void main(String[] args) {
//需求:定义三个方法
//insert() 插入数据
//insert();
//update() 修改数据
//update();
//delete() 删除数据
delete();
}
//删除数据
private static void delete() {
Connection conn = null;
PreparedStatement pstmt = null;
try {
//获得连接
conn = JDBCUtils.getConnection();
//准备sql语句
String sql = "delete from student where id = ?";
//预编译sql
pstmt = conn.prepareStatement(sql);
//插入参数
pstmt.setInt(1, 1);
//执行sql
int i = pstmt.executeUpdate();
System.out.println("删除了" + i + "条数据");
} catch (SQLException e) {
e.printStackTrace();
} finally {
//释放资源
JDBCUtils.release(pstmt, conn);
}
}
//修改数据
private static void update() {
Connection conn = null;
PreparedStatement pstmt = null;
try {
//获得连接
conn = JDBCUtils.getConnection();
//准备sql语句
String sql = "update student set name = ?,birthday = ? where id = ?";
//预编译sql
pstmt = conn.prepareStatement(sql);
//插入参数
pstmt.setString(1, "白龙马");
//java.sql.Date 注意这里的date是 sql包下的
pstmt.setDate(2, Date.valueOf("2000-03-12"));
pstmt.setInt(3, 5);
//执行sql
int i = pstmt.executeUpdate();
System.out.println("修改了" + i + "条数据");
} catch (SQLException e) {
e.printStackTrace();
} finally {
//释放资源
JDBCUtils.release(pstmt, conn);
}
}
//插入数据
private static void insert() {
Connection conn = null;
PreparedStatement pstmt = null;
try {
//获得连接
conn = JDBCUtils.getConnection();
//准备sql语句
String sql = "insert into student values(null,?,?,?,?)";
//预编译sql
pstmt = conn.prepareStatement(sql);
//插入参数
pstmt.setString(1, "黑熊精");
pstmt.setInt(2, 18);
pstmt.setString(3, "男");
//java.sql.Date 注意这里的date是 sql包下的
pstmt.setDate(4, Date.valueOf("1999-03-12"));
//执行sql
int i = pstmt.executeUpdate();
System.out.println("插入了" + i + "条数据");
} catch (SQLException e) {
e.printStackTrace();
} finally {
//释放资源
JDBCUtils.release(pstmt, conn);
}
}
}