JDBC-基础
一、JDBC简介
讲解
1. 什么是JDBC
- JDBC(Java DataBase Connectivity,java数据库连接):Sun公司提供的一套规范(接口), 是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。
2. JDBC的作用
-
实现了Java程序对不同数据库的统一操作
-
没有JDBC时,Java操作数据库的模式:
- 不同数据库的API,由数据库厂商自己提供,没有统一标准
- 需要编写不同的Java程序,来操作不同的数据库
-
有JDBC后,Java操作数据库的模式
- Sun公司规定了操作数据库的统一规范:JDBC
- 各数据库厂商在提供操作数据库的API时,都要实现JDBC规范
- 只需要编写一套程序,就可以操作不同的数据库
3. 数据库驱动:
-
由数据库厂商提供的,JDBC规范的实现类,打包形成的jar包,叫数据库驱动包
- 要操作什么数据库,就必须有什么数据库的驱动
小结
- JDBC:Java操作数据库。由一些接口和工具类组成
- 好处:实现了对不同数据库的统一访问
- 数据库驱动:由数据库厂商提供的、Jdbc规范的实现类,打包形成的jar包,叫数据库驱动包
- 要操作什么数据库,就必须导入什么数据库的驱动包
二、JDBC快速入门
1. 建表语句
drop database if exists heima;
create database heima character set utf8;
use heima;
DROP TABLE IF EXISTS USER;
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20),
PASSWORD VARCHAR(50),
NAME VARCHAR(10),
birthday DATE,
age INT,
sex CHAR(1)
) CHARACTER SET = utf8 COLLATE = utf8_general_ci;
INSERT INTO USER(id,username,PASSWORD,NAME,birthday,age,sex) VALUES (NULL, 'xiaohong', 'xiaohong', '小红', '1999-09-09', 20, '男');
INSERT INTO USER(id,username,PASSWORD,NAME,birthday,age,sex) VALUES (NULL, 'xiaolan', 'xiaolan', '小兰', '1997-09-09', 22, '女');
INSERT INTO USER(id,username,PASSWORD,NAME,birthday,age,sex) VALUES (NULL, 'xiaolv', 'xiaolv', '小吕', '1999-09-09', 20, '女');
INSERT INTO USER(id,username,PASSWORD,NAME,birthday,age,sex) VALUES (NULL, 'xiaozi', 'xiaozi', '小紫', '1999-09-09', 20, '男');
INSERT INTO USER(id,username,PASSWORD,NAME,birthday,age,sex) VALUES (NULL, 'xiaoming', 'xiaoming', '小明', '1999-09-09', 20, '男');
2. JDBC操作的步骤
- 导入数据库驱动jar包
- 编写程序
- 注册驱动:要操作哪种数据库,就要注册哪种数据库的驱动类
- 获取连接:连接上数据库
- 创建SQL执行平台:SQL执行平台提供了执行SQL语句的方法
- 执行SQL语句
- 处理结果
- 释放资源
实现
1. 导入jar包
2. 编写代码
public class JdbcQuickStart {
public static void main(String[] args) throws Exception {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
//3.创建SQL执行平台
Statement statement = connection.createStatement();
//4.执行SQL语句
ResultSet resultSet = statement.executeQuery("select * from user");
//5.处理结果
while (resultSet.next()) {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
String name = resultSet.getString("name");
String birthday = resultSet.getString("birthday");
int age = resultSet.getInt("age");
String sex = resultSet.getString("sex");
System.out.println("id: " + id + ", username: " + username +", password: "+ password + ", name: " + name + ", birthday: " + birthday + ", age: " + age + ", sex: " + sex);
}
//6.释放资源
resultSet.close();
statement.close();
connection.close();
}
}
小结
- JDBC操作的步骤
- 注册驱动
- 获取连接
- 创建SQL执行平台
- 执行SQL语句
- 处理结果
- 释放资源
三、JDBC的API详解
3.1 注册驱动
讲解
1. API介绍
- 相关类介绍
类名 | 介绍 |
---|---|
java.sql.Driver | JDBC规定的驱动类接口 |
com.mysql.jdbc.Driver | MySql驱动包里提供的驱动类,实现了Driver接口 |
java.sql.DriverManager | JDBC提供的工具类,用于注册驱动 |
DriverManager
提供了静态方法,用于注册驱动
方法名 | 返回值 |
---|---|
registerDriver(Driver driver) | void |
2. 注册驱动的方式
-
方式一(不推荐):使用
DriverManager
提供的方法注册DriverManager.registerDriver(new com.mysql.jdbc.Driver());
-
注册了2次:
-
com.mysql.jdbc.Driver
中有静态代码块,注册了一次 -
我们的
DriverManager.register()
方法又注册了一次
-
-
存在硬编码问题:代码中写死了,注册的是MySql的驱动,只能操作MySql数据库
-
-
方式二:使用反射技术注册
Class.forName("com.mysql.jdbc.Driver");
- 只注册了一次
- 可以把字符串提取到配置文件中,需要修改时只要修改配置文件即可,不需要修改程序源码,解决硬编码问题
小结
- 相关的API
- Jdbc提供的驱动类接口:
java.sql.Driver
- MySql驱动包里的驱动类:
com.mysql.jdbc.Driver
- Jdbc提供的驱动类接口:
- 如何注册驱动:
- 不建议:
DriverManager.registerDriver(new com.mysql.jdbc.Driver())
- 注册了2次
- 有硬编码问题
- 使用这种:
Class.forName("com.mysql.jdbc.Driver")
- 注册了1次
- 硬编码问题可以解决:提取到配置文件
- 不建议:
3.2 获取连接
目标
- 了解JDBC的
Connection
接口 - 掌握获取连接的方法
讲解
1. API介绍
- 相关类介绍
类名 | 介绍 |
---|---|
java.sql.DriverManager | JDBC的工具类,提供了获取连接的方法 |
java.sql.Connection | JDBC提供的,数据库连接对象的接口(无需关心具体的实现类,多态) |
DriverManager
提供了静态方法
方法名 | 返回值 |
---|---|
getConnection(url, username, password) | java.sql.Connection |
- 方法的参数介绍:
url
:数据库的连接地址- 语法:JDBC规定了 由三部分组成,三部分使用
:
连接- 第一部分:协议名,固定写法
jdbc
- 第二部分:子协议名,通常是数据库名,连接MySql写成:
mysql
- 第三部分:数据库地址,具体写法由数据库厂商自己决定:
//ip:port/database
- 第一部分:协议名,固定写法
- 示例:
- 完整写法:
jdbc:mysql://localhost:3306/heima
- 简写形式:
jdbc:mysql:///heima
,表示连接本机、默认端口数据库的heima库
- 完整写法:
- 语法:JDBC规定了 由三部分组成,三部分使用
username
:数据库的用户名password
:数据库的密码
2. 获取连接的方式
Class.forName("com.mysql.jdbc.Driver");
//获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
小结
- 相关的API
java.sql.DriverManager
:jdbc提供的工具类,提供了获取连接的方法java.sql.Connection
:jdbc提供的,连接类的接口,我们得到的是这个接口的实现类对象
- 如何获取连接:
Connection conn = DriverManager.getConnection("url地址","数据库用户名","数据库密码");
- url地址:三部分组成,使用
:
连接- 协议名:固定值
jdbc
- 子协议:
mysql
- 具体地址:由数据库厂商规定的,MySql的写法是:
- 完整写法:
//数据库ip:端口/database
- 简写形式:
///database
, 只有连接本机、3306端口的MySql时,才可以使用简写形式
- 完整写法:
- 协议名:固定值
- MySql的连接地址示例:
jdbc:mysql://localhost:3306/heima
3.3 创建SQL执行平台
讲解
API介绍
- 相关类
类名 | 介绍 |
---|---|
java.sql.Statement | JDBC提供的,用于执行SQL语句的对象接口(无需关注具体的实现类) |
java.sql.Connection
对象提供了方法,可以创建SQL执行平台
方法名 | 返回值 |
---|---|
createStatement() | java.sql.Statement |
创建SQL执行平台
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
//创建SQL执行平台
Statement stmt = conn.createStatement();
小结
- 相关的API:
java.sql.Statement
:jdbc规定的执行平台的接口
- 如何获取SQL执行平台:
Statement stmt = connection.createStatement()
3.4 执行SQL语句【重点】
讲解
1. API介绍
- 相关类
类名 | 介绍 |
---|---|
java.sql.ResultSet | JDBC提供的,用于封装查询结果集的接口 |
java.sql.Statement
对象提供了执行不同SQL语句的方法
方法名 | 参数 | 返回值 |
---|---|---|
executeQuery(String sql) | DQL语句 | java.sql.ResultSet ,查询结果集对象 |
executeUpdate(String sql) | DML语句 | int ,表示影响的行数 |
exeucte(String sql) | 任意语句 | boolean ,表示是否执行了查询 |
2. 执行SQL语句
- 执行DQL语句
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
Statement stmt = conn.createStatement();
//执行DQL语句
ResultSet rs = stmt.executeQuery("select * from user");
- 执行DML语句
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
Statement stmt = conn.createStatement();
//执行DML语句
int count = stmt.executeUpdate("delete from user where id = 1");
- 执行任意语句
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
Statement stmt = conn.createStatement();
//执行任意语句
boolean isQuery = stmt.execute("create database itcast");
小结
- 执行DQL:
statement.executeQuery(String sql)
,得到ResultSet结果集 - 执行DML:
statement.executeUpdate(String sql)
,得到int,表示影响的行数 - 执行任意语句:
statement.execute(String sql)
,得到boolean,表示 执行的是否是查询- 如果true:执行的是查询语句,有结果集ResultSet
- 如果false:执行的不是查询语句,没有结果集ResultSet
3.5 处理结果集
讲解
1. API介绍
- 相关类
类名 | 介绍 |
---|---|
java.sql.ResultSet | JDBC提供的,用于封装查询结果集的对象接口(无需关注具体的实现类) |
-
ResultSet
对象的结构ResultSet
是类似二维表的结构,存储了查询的多行多列的结果ResultSet
自带行指针- 行指针在哪一行,就能获取哪一行的数据
- 初始状态的行指针,默认指向第0行之前,必须向下移动一次,才可以获取数据
-
ResultSet
对象提供了方法
方法名 | 参数 | 返回值 |
---|---|---|
next() | boolean ,向下移动行指针,是否移动成功 | |
previous() | boolean ,向上移动行指针,是否移动成功 | |
getXXX(int columnCount) | 列序号,从1开始 | 第columnCount 列的值,XXX 指数据类型 |
getXXX(String columnName) | 列名称 | columnName 列的值,XXX 指数据类型 |
2. 循环遍历ResultSet
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from user");
//处理结果,循环遍历ResultSet
while(rs.next()){
int id = rs.getInt("id");
String username = rs.getString("username");
System.out.println("id: " + id + ", username: " + username);
}
小结
java.sql.ResultSet
:是查询结果集,是n行n列的结构,通过移动行指针,可以获取指针所在行的数据- 向下移动行指针:
resultSet.next()
- 如果返回true:向下移动成功,下一行存在
- 如果返回false:向下移动失败,下一行不存在
- 获取指针所在行里,某一列的数据:
resultSet.getXXX(int 列序号)
,从1开始resultSet.getXXX(String 列名称)
- 其中:
xxx
指的是 想要获取的数据类型,通常和字段类型是对应的
3.6 释放资源
讲解
1. 需要释放的资源
- 需要释放的资源有:
java.sql.ResultSet
,java.sql.Statement
,java.sql.Connection
- 以上三个资源都提供了
close()
方法,用于释放资源
- 以上三个资源都提供了
- 释放的顺序:
ResultSet --> Statement --> Connection
- 和获取的顺序相反
2. 释放资源的方法
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("select * from user");
while(rs.next()){
int id = rs.getInt("id");
String username = rs.getString("username");
System.out.println("id: " + id + ", username: " + username);
}
//释放资源
rs.close();
stmt.close();
conn.close();
小结
四、JDBC工具类封装
分析
JDBC存在的问题
- 重复代码问题,每次操作数据库都需要:
- 注册驱动
- 获取连接
- 释放资源
- 注册驱动的代码,每次操作数据库都要执行一次,而实际只需要执行一次
- 硬编码问题,代码中写死了,如果连接的数据库有变化,还需要修改源码:
- 驱动类名
- 数据库地址
- 数据库用户名
- 数据库密码
问题解决的思路
- 重复代码问题:提取到一个类,设置成为静态的公用方法,随用随调
- 注册驱动的代码,只需要执行一次:提取到一个类,放到静态代码块中
- 硬编码问题:提取到配置文件中,如果数据库有变化,只需要修改配置文件,不需要修改源码
实现
- 创建配置文件
jdbc.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql:///heima
username=root
password=root
- 创建工具类
JdbcUtils
public class JdbcUtils {
private static String url;
private static String username;
private static String password;
static {
InputStream is = null;
try {
//读取properties文件
is = JdbcUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
//加载到properties对象中
Properties properties = new Properties();
properties.load(is);
//获取配置文件中的信息
String driver = properties.getProperty("driver");
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
//注册驱动
Class.forName(driver);
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭流
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 获取连接
*/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
/**
* 释放资源
*/
public static void close(ResultSet rs, Statement stmt, Connection conn){
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();
}
}
}
}
小结
五、预编译对象
1. 登录功能练习
分析
- 获取用户在控制台输入的用户名和密码:
Scanner
- 执行SQL语句,判断用户名和密码是否正确:
- 如果正确:输出“登录成功”;否则输出“登录失败”
实现
/**
* 登录功能
* @author liuyp
* @date 2020/01/07
*/
public class DemoLogin {
public static void main(String[] args) {
//读取用户在控制台输入的用户名和密码
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名:");
String username = scanner.nextLine();
System.out.println("请输入密码:");
String password = scanner.nextLine();
//登录校验,得到结果
boolean success = login(username, password);
//显示结果
if (success) {
System.out.println("登录成功");
}else{
System.out.println("登录失败");
}
}
/**
* 登录校验
* @param username 用户名
* @param password 密码
* @return 登录校验结果。true表示登录成功, false表示登录失败
*/
private static boolean login(String username, String password) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JdbcUtils.getConnection();
statement = connection.createStatement();
String sql = "select * from user where username = '"+ username + "' and password = '" + password + "'";
resultSet = statement.executeQuery(sql);
if (resultSet.next()) {
return true;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtils.close(connection, statement, resultSet);
}
return false;
}
}
小结
2. SQL注入漏洞
分析
- 在刚刚的登录功能中,正常情况下:
- 必须输入正确的用户名和密码,才会登录成功;否则会登录失败
- 但是实际情况是:
- 请输入用户名:
xiaohong
, 密码:' or '1' = '1
。 - 输入的密码是不正确的,但是结果显示的却是: 登录成功
- 请输入用户名:
- 说明刚刚编写的登录功能代码,存在安全漏洞
讲解
-
什么是SQL注入漏洞?
- 通过输入一些特殊的参数,导致最终拼接的SQL语句结构出现了问题,形成的安全漏洞,叫SQL注入
- 理想的登录SQL语句:
select * from user where username = 'xiaohong' and password = 'xiaohong'
- 注入后的SQL语句:
select * from user where username = 'xiaohong' and password = '' or '1' = '1'
-
如何解决SQL注入漏洞?
- 出现SQL注入漏洞的原因,是拼接SQL语句字符串,导致SQL语句结构改变了
- 解决的方案是:使用JDBC提供的预编译对象
PreparedStatement
执行SQL语句,而不是Statement
小结
- SQL注入漏洞:通过输入一些特殊参数,导致拼接的SQL语句结构发生变化,从而绕过SQL的一些条件
- 如何解决SQL注入:使用预编译对象
PreparedStatement
代替掉Statement
3. 预编译详解
分析
-
使用步骤
-
注册驱动
-
获取连接
-
创建预编译对象,预编译SQL–和之前不同
-
执行SQL语句–和之前不同
-
处理结果
-
释放资源
-
-
API介绍
讲解
API介绍
什么是预编译对象
- 预编译对象:是指
java.sql.PreparedStatement
的对象,它是java.sql.Statement
的子接口。 - 好处:
- 更安全:可以解决SQL注入漏洞,通过预先编译SQL,SQL语句结构就不会再改变了
- 效率高:同一语句,多次执行时效率比
Statement
的效率更高 - 易于阅读
如何获取预编译对象
Connection
对象提供了方法:
方法 | 返回值 | 说明 |
---|---|---|
prepareStatement(String sql) | PreparedStatement | 预编译SQL,得到预编译对象 |
- 其中,SQL语句需要使用占位符
?
代替掉参数值,例如:
//原本SQL
String sql = "select * from user where username = '"+username+"' and password = '"+password+"'";
//预编译对象需要的SQL:把参数值替换成占位符 ?
String sql = "select * from user where username = ? and password = ?";
预编译对象的常用API
PreparedStatement
对象提供了常用方法:
方法 | 返回值 | 说明 |
---|---|---|
setXXX(int 序号, XXX value) | 给预编译SQL中第几 个?,设置参数值value | |
executeQuery() | ResultSet | 执行DQL语句,得到结果集对象 |
executeUpdate() | int | 执行DML语句,得到影响的行数 |
execute() | boolean | 执行任意语句,得到boolean:执行的是查询,结果为true;否则false |
使用示例
/**
* 预编译对象 快速入门
* @author liuyp
* @date 2020/01/07
*/
public class Demo01PreparedStatementQuickStart {
public static void main(String[] args) throws Exception {
//1. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接
Connection connection = DriverManager.getConnection("jdbc:mysql:///heima", "root", "root");
//3. 创建预编译对象
String sql = "select * from user where sex = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "男");
//4. 执行SQL语句
ResultSet resultSet = preparedStatement.executeQuery();
//5. 处理结果
while (resultSet.next()) {
String username = resultSet.getString("username");
String sex = resultSet.getString("sex");
System.out.println("username: " + username + ", sex: " + sex);
}
//6. 释放资源
JdbcUtils.close(connection, preparedStatement, resultSet);
}
}
小结
- 使用预编译对象,执行SQL语句:
- 注册驱动
- 获取连接
- 创建预编译对象
- 执行SQL语句
- 处理结果
- 释放资源
- 如何创建预编译对象?
connection.prepareStatement(String sql)
,返回值:PreparedStatement
- SQL语句:需要使用占位符?,代替掉参数值
- 设置SQL语句的参数值:
preparedStatement.setXXX(序号, 值)
- 如何执行SQL语句?
executeQuery()
:执行查询,得到resultSetexecuteUpdate()
:执行DML,得到intexecute()
:执行任意语句,得到boolean。true:表示执行的是查询;false:表示执行的不是查询
4. 预编译完成CURD
六、JDBC的事务管理
分析
建表语句
DROP TABLE IF EXISTS account;
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20),
money DOUBLE
);
INSERT INTO account(id,NAME,money) VALUES(NULL, 'tom', 10000);
INSERT INTO account(id,NAME,money) VALUES(NULL, 'jerry', 10000);
事务管理的API
java.sql.Connection
对象提供了事务管理的方法
方法 | 返回值 | 说明 |
---|---|---|
setAutoCommit(false) (相当于SQL语句:set autocommit=0 ) | void | 开启事务 |
commit() | void | 提交事务 |
rollback() | void | 回滚事务 |
事务管理的步骤
try{
//1.注册驱动
//2.获取连接
//===开启事务===
//3.创建预编译对象
//4.执行SQL语句:多次
//5.处理结果
//===提交事务===
}catch(Exception e){
//===回滚事务===
}finally{
//6.释放资源
}
实现
public class DemoTransfer {
public static void main(String[] args) throws Exception {
String from = "tom";
String to = "jerry";
Double money = 1000d;
transfer(from, to, money);
}
/**
* 转账功能
* @param from 转账人
* @param to 收款人
* @param money 转账金额
*/
private static void transfer(String from, String to, Double money) throws Exception {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
//1. 注册驱动
//2. 获取连接
connection = JdbcUtils.getConnection();
//开启事务
connection.setAutoCommit(false);
//3. 创建预编译对象
String sql = "update account set money = money + ? where name = ?";
preparedStatement = connection.prepareStatement(sql);
//4. 执行SQL语句
//4.1 扣钱
preparedStatement.setDouble(1, -money);
preparedStatement.setString(2, from);
preparedStatement.executeUpdate();
int i = 1/0;
//4.2 加钱
preparedStatement.setDouble(1, money);
preparedStatement.setString(2, to);
preparedStatement.executeUpdate();
//5. 处理结果,提交事务
connection.commit();
System.out.println("转账成功,事务提交");
} catch (Exception e) {
//5. 处理结果,回滚事务
if (connection != null) {
connection.rollback();
}
System.out.println("转账失败,事务回滚");
e.printStackTrace();
} finally {
//6. 释放资源
JdbcUtils.close(connection, preparedStatement, null);
}
}
}
小结
- 事务管理的步骤
try{
//1.注册驱动
//2.获取连接
//===开启事务===
//3.创建预编译对象
//4.执行SQL语句
//5.处理结果
//===提交事务===
}catch(Exception e){
//===回滚事务===
}finally{
//6.释放资源
}
- 开启事务:
connection.setAutoCommit(false)
- 提交事务:
connection.commit()
- 回滚事务:
connection.rollback()