什么是 JDBC
JDBC 规范定义接口,具体的实现由各大数据库厂商来实现。
JDBC 是 Java 访问数据库的标准规范,真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用 JDBC 接口中的方法即可,数据库驱动由数据库厂商提供。
- 使用 JDBC 的好处:
- 程序员如果要开发访问数据库的程序,只需要会调用 JDBC 接口中的方法即可,不用关注类是如何实现的。
- 使用同一套 Java 代码,进行少量的修改就可以访问其他 JDBC 支持的数据库
加载和注册驱动
注:从 JDBC3 开始,目前已经普遍使用的版本。可以不用注册驱动而直接使用。Class.forName 这句话可以省略。
DriverManager 类
DriverManager 作用:
- 管理和注册驱动
- 创建数据库的连接
类中的方法:
DriverManager 类中的静态方法 | 描述 |
---|---|
Connection getConnection (String url, String user, String password) | 通过连接字符串,用户名,密码来得到数据库的连接对象 |
Connection getConnection (String url, Properties info) | 通过连接字符串,属性对象来得到连接对象 |
使用 JDBC 连接数据库的四个参数
JDBC 连接数据库的四个参数 | 说明 |
---|---|
用户名 | 登录的用户名 |
密码 | 登录的密码 |
连接字符串 | URL 不同的数据库 URL 是不同的,mysql 的写法:jdbc:mysql://localhost:3306/数据库[?参数名=参数值] |
驱动类的字符串名 | com.mysql.jdbc.Driver |
连接数据库的 URL 地址格式:
协议名:子协议://服务器名或 IP 地址:端口号/数据库名?参数=参数值
MySQL 写法:
[外链图片转存中…(img-08ukgFxM-1626916752738)]
MySQL 中可以简写:
乱码的处理
如果数据库出现乱码,可以指定参数: ?characterEncoding=utf8,表示让数据库以 UTF-8 编码来处理数据。
jdbc:mysql://localhost:3306/数据库?characterEncoding=utf8
案例:得到 MySQL 的数据库连接对象
- 使用用户名、密码、URL 得到连接对象
package test04_jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Demo02 {
public static void main(String[] args) throws SQLException {
String url = "jdbc:mysql://localhost:3306/db1?characterEncoding=utf8";
Connection connection = DriverManager.getConnection(url, "root", "123456");
System.out.println(connection);
}
}
- 使用属性文件和 url 得到连接对象
package test04_jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class Demo03 {
public static void main(String[] args) throws SQLException {
String url = "jdbc:mysql://localhost:3306/db1";
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password", "123456");
Connection connection = DriverManager.getConnection(url, info);
System.out.println(connection);
}
}
Connection 接口:
Connection 作用:
Connection 接口,具体的实现类由数据库的厂商实现,代表一个连接对象。
Connection 方法:
Statement 接口
JDBC 访问数据库的步骤
- 注册和加载驱动(可以省略)
- 获取连接
- Connection 获取 Statement 对象
- 使用 Statement 对象执行 SQL 语句
- 返回结果集
- 释放资源
Statement 作用:
代表一条语句对象,用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象。
Statement 中的方法:
Statement 接口中的方法 | 描述 |
---|---|
int executeUpdate(String sql) | 用于发送 DML 语句,增删改的操作,insert、update、delete 参数:SQL 语句, 返回值:返回对数据库影响的行数 |
ResultSet executeQuery(String sql) | 用于发送 DQL 语句,执行查询的操作。select 参数:SQL 语句,返回值:查询的结果集 |
释放资源
- 需要释放的对象:ResultSet 结果集,Statement 语句,Connection 连接
- 释放原则:先开的后关,后开的先关。ResultSet Statement Connection
- 放在哪个代码块中:finally 块
执行 DDL 操作
需求:使用 JDBC 在 MySQL 的数据库中创建一张学生表
代码:
package test04_jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo04 {
public static void main(String[] args) {
Connection connection=null;
Statement statement=null;
try {
connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/db1", "root", "123456");
statement=connection.createStatement();
statement.executeUpdate("create table student(\n" +
" id int primary key auto_increment,\n" +
" name varchar(20) not null,\n" +
" gender boolean,\n" +
" birthday date\n" +
");");
System.out.println("success");
} catch (SQLException throwables) {
throwables.printStackTrace();
}
finally {
if(statement!=null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(connection!=null) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
执行 DML 操作
- 需求:向学生表中添加 4 条记录,主键是自动增长
- 步骤:
- 创建连接对象
- 创建 Statement 语句对象
- 执行 SQL 语句:executeUpdate(sql)
- 返回影响的行数
- 释放资源
- 代码:
package test04_jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class Demo05 {
public static void main(String[] args) {
Connection connection=null;
Statement statement=null;
try {
connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/db1", "root", "123456");
statement=connection.createStatement();
int count = 0;
count += statement.executeUpdate("insert into db1.student values (null, 'heyonghu', 1, '2017-2-4');");
count += statement.executeUpdate("insert into db1.student values (null, 'heyonghu', 1, '2017-2-4');");
count += statement.executeUpdate("insert into db1.student values (null, 'heyonghu', 1, '2017-2-4');");
count += statement.executeUpdate("insert into db1.student values (null, 'heyonghu', 1, '2017-2-4');");
count += statement.executeUpdate("insert into db1.student values (null, 'heyonghu', 1, '2017-2-4');");
count += statement.executeUpdate("insert into db1.student values (null, 'heyonghu', 1, '2017-2-4');");
System.out.println(count);
} catch (Exception throwables) {
throwables.printStackTrace();
}
finally {
if(statement!=null){
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(connection!=null) {
try {
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
执行DQL
ResultSet 接口:
- 作用:封装数据库查询的结果集,对结果集进行遍历,取出每一条记录。
- 接口中的方法:
ResultSet 接口中的方法 | 描述 |
---|---|
boolean next() | 1) 游标向下移动 1 行; 2) 返回 boolean 类型,如果还有下一条记录,返回 true,否则返回 false |
数据类型 getXxx() | 1) 通过字段名,参数是 String 类型。返回不同的类型 ; 2) 通过列号,参数是整数,从 1 开始。返回不同的类型 |
常用数据类型转换表
SQL 类型 | Jdbc 对应方法 | 返回类型 |
---|---|---|
BIT(1) bit(n) | getBoolean() | boolean |
TINYINT | getByte() | byte |
SMALLINT | getShort() | short |
INT | getInt() | int |
BIGINT | getLong() long | |
CHAR,VARCHAR | getString() | String |
Text(Clob) Blob | getClob getBlob() | Clob Blob |
DATE | getDate() | java.sql.Date 只代表日期 |
TIME | getTime() | java.sql.Time 只表示时间 |
TIMESTAMP | getTimestamp() | java.sql.Timestamp 同时有日期和时间 |
** java.sql.Date、Time、Timestamp(时间戳),三个共同父类是:java.util.Date
需求:确保数据库中有 3 条以上的记录,查询所有的学员信息
- 步骤:
- 得到连接对象
- 得到语句对象
- 执行 SQL 语句得到结果集 ResultSet 对象
- 循环遍历取出每一条记录
- 输出的控制台上
- 释放资源
结果:
package test04_jdbc;
import java.sql.*;
public class Demo06DQL {
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1", "root", "123456");
statement = connection.createStatement();
resultSet = statement.executeQuery("select * from student");
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
boolean gender = resultSet.getBoolean("gender");
Date birthday = resultSet.getDate("birthday");
System.out.println(id + name + birthday);
}
} catch (Exception throwables) {
throwables.printStackTrace();
} finally {
try {
resultSet.close();
statement.close();
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
关于 ResultSet 接口中的注意事项:
- 如果光标在第一行之前,使用 rs.getXX()获取列值,报错:Before start of result set
- 如果光标在最后一行之后,使用 rs.getXX()获取列值,报错:After end of result set
- 使用完毕以后要关闭结果集 ResultSet,再关闭 Statement,再关闭 Connection
数据库工具类 JdbcUtils
- 什么时候自己创建工具类?
如果一个功能经常要用到,我们建议把这个功能做成一个工具类,可以在不同的地方重用。
需求:
上面写的代码中出现了很多重复的代码,可以把这些公共代码抽取出来。
创建类 JdbcUtil 包含 3 个方法:
- 可以把几个字符串定义成常量:用户名,密码,URL,驱动类
- 得到数据库的连接:getConnection()
- 关闭所有打开的资源:
close(Connection conn, Statement stmt),close(Connection conn, Statement stmt, ResultSet rs)
package test04_jdbc;
import java.sql.*;
public class JdbcUtils {
private static final String USER = "root";
private static final String PWD = "123456";
private static final String URL = "jdbc:mysql://localhost:3306/db1";
private static final String DRIVER = "com.mysql.jdbc.Driver";
static {
try {
Class.forName(DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
Connection connection = null;
try {
connection = DriverManager.getConnection(URL, USER, PWD);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
return connection;
}
}
public static void close(Connection conn, Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
public static void close(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
close(connection, statement);
}
}
}
案例:用户登陆
需求:
- 有一张用户表
- 添加几条用户记录
- 使用 Statement 字符串拼接的方式实现用户的登录, 用户在控制台上输入用户名和密码。
步骤:
- 得到用户从控制台上输入的用户名和密码来查询数据库
- 写一个登录的方法
a) 通过工具类得到连接
b) 创建语句对象,使用拼接字符串的方式生成 SQL 语句
c) 查询数据库,如果有记录则表示登录成功,否则登录失败
d) 释放资源
package test04_jdbc;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Scanner;
public class Demo07 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("please you input your user:");
String name = scanner.nextLine();
System.out.println("please you input your password");
String password = scanner.nextLine();
login(name, password);
}
public static void login(String name, String password) {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
connection = JdbcUtils.getConnection();
statement = connection.createStatement();
String sql = "select * from user where name='"+name+"' and password = '"+password+"';";
System.out.println(sql);
resultSet = statement.executeQuery(sql);
if(resultSet.next()){
System.out.println("success");
}else{
System.out.println("fail");
}
} catch (Exception throwables) {
System.out.println("11111111");
throwables.printStackTrace();
}finally {
JdbcUtils.close(connection,statement,resultSet);
}
}
}
SQL 注入问题
- 当我们输入以下密码,我们发现我们账号和密码都不对竟然登录成功了
- 问题分析:
我们让用户输入的密码和 SQL 语句进行字符串拼接。用户输入的内容作为了 SQL 语句语法的一部分,改变了
原有 SQL 真正的意义,以上问题称为 SQL 注入。要解决 SQL 注入就不能让用户输入的密码和我们的 SQL 语句进行简单的字符串拼接。
PreparedStatement 接口
继承结构与作用:
PreparedStatement 是 Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语句
PreparedSatement 的执行原理
- 因为有预先编译的功能,提高 SQL 的执行效率。
- 可以有效的防止 SQL 注入的问题,安全性更高。
Connection 创建 PreparedStatement 对象
PreparedSatement 的好处
- prepareStatement()会先将 SQL 语句发送给数据库预编译。PreparedStatement 会引用着预编译后的结果。可以多次传入不同的参数给 PreparedStatement 对象并执行。减少 SQL 编译次数,提高效率。
- 安全性更高,没有 SQL 注入的隐患。
- 提高了程序的可读性
使用 PreparedStatement 的步骤:
- 编写 SQL 语句,未知内容使用?占位:“SELECT * FROM user WHERE name=? AND password=?”;
- 获得 PreparedStatement 对象
- 设置实际参数:setXxx(占位符的位置, 真实的值)
- 执行参数化 SQL 语句
- 关闭资源
PreparedStatement 中设置参数的方法 | 描述 |
---|---|
void setDouble(int parameterIndex, double x) | 将指定参数设置为给定 Java double 值。 |
void setFloat(int parameterIndex, float x) | 将指定参数设置为给定 Java REAL 值。 |
void setInt(int parameterIndex, int x) | 将指定参数设置为给定 Java int 值。 |
void setLong(int parameterIndex, long x) | 将指定参数设置为给定 Java long 值。 |
void setObject(int parameterIndex, Object x) | 使用给定对象设置指定参数的值。 |
void setString(int parameterIndex, String x) | 将指定参数设置为给定 Java String 值。 |
- 使用 PreparedStatement 改写上面的登录程序,看有没有 SQL 注入的情况
package test04_jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;
public class Demo08 {
public static void main(String[] args) throws SQLException {
Scanner scanner = new Scanner(System.in);
System.out.println("please you input user:");
String name = scanner.nextLine();
System.out.println("please you please input pwd:");
String pwd = scanner.nextLine();
login(name, pwd);
}
private static void login(String name, String pwd) throws SQLException {
Connection connection = JdbcUtils.getConnection();
String sql = "select * from user where name = ? and password=?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,name);
preparedStatement.setString(2,pwd);
ResultSet resultSet = preparedStatement.executeQuery();
if(resultSet.next()){
System.out.println("success "+name);
}
else{
System.out.println("fail");
}
JdbcUtils.close(connection, preparedStatement, resultSet);
}
}
表与类的关系
案例:使用 PreparedStatement 查询一条数据,封装成一个学生 Student 对象
package test04_jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Demo09 {
public static void main(String[] args) throws SQLException {
Student student = new Student();
Connection connection = JdbcUtils.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("select * from student where id = ?");
preparedStatement.setInt(1, 2);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
student.setId(resultSet.getInt("id"));
student.setName(resultSet.getString("name"));
student.setGender(resultSet.getBoolean("gender"));
student.setBirthday(resultSet.getDate("birthday"));
System.out.println(student);
}
JdbcUtils.close(connection, preparedStatement, resultSet);
}
}
案例:将多条记录封装成集合 List,集合中每个元素是一个 JavaBean 实体类
- 需求: 查询所有的学生类,封装成 List返回
- 代码:
package test04_jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
public class Demo10 {
public static void main(String[] args) throws SQLException {
ArrayList<Student> students = new ArrayList<>();
Connection connection = JdbcUtils.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("select * from student");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
Student student = new Student();
student.setId(resultSet.getInt("id"));
student.setName(resultSet.getString("name"));
student.setGender(resultSet.getBoolean("gender"));
student.setBirthday(resultSet.getDate("birthday"));
students.add(student);
}
System.out.println(students);
JdbcUtils.close(connection, preparedStatement, resultSet);
}
}
PreparedStatement 执行 DML 操作
package test04_jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Demo11 {
public static void main(String[] args) throws SQLException {
//insert();
//update();
delete();
}
//插入记录
private static void insert() throws SQLException {
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("insert into student values(null,?,?,?)");
ps.setString(1, "小白龙");
ps.setBoolean(2, true);
ps.setDate(3, java.sql.Date.valueOf("1999-11-11"));
int row = ps.executeUpdate();
System.out.println("插入了" + row + "条记录");
JdbcUtils.close(connection, ps);
}
//更新记录: 换名字和生日
private static void update() throws SQLException {
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("update student set name=?, birthday=? where id=?");
ps.setString(1, " 黑 熊 怪 ");
ps.setDate(2, java.sql.Date.valueOf("1999-03-23"));
ps.setInt(3, 5);
int row = ps.executeUpdate();
System.out.println("更新" + row + "条记录");
JdbcUtils.close(connection, ps);
}
//删除记录: 删除第 5 条记录
private static void delete() throws SQLException {
Connection connection = JdbcUtils.getConnection();
PreparedStatement ps = connection.prepareStatement("delete from student where id=?");
ps.setInt(1, 5);
int row = ps.executeUpdate();
System.out.println("删除了" + row + "条记录");
JdbcUtils.close(connection, ps);
}
}
JDBC 事务的处理
之前我们是使用 MySQL 的命令来操作事务。接下来我们使用 JDBC 来操作银行转账的事务。
准备数据
API 介绍
开发步骤
- 获取连接
- 开启事务
- 获取到 PreparedStatement
- 使用 PreparedStatement 执行两次更新操作
- 正常情况下提交事务
- 出现异常回滚事务
- 最后关闭资源
package test04_jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Demo12 {
//没有异常,提交事务,出现异常回滚事务
public static void main(String[] args) {
//1) 注册驱动
Connection connection = null;
PreparedStatement ps = null;
try {
//2) 获取连接
connection = JdbcUtils.getConnection();
//3) 开启事务
connection.setAutoCommit(false);
//4) 获取到 PreparedStatement
//从 jack 扣钱
ps = connection.prepareStatement("update account set balance = balance - ? where name = ? ");
ps.setInt(1, 500);
ps.setString(2, "Jack");
ps.executeUpdate();
//出现异常
System.out.println(100 / 0);
//给 rose 加钱
ps = connection.prepareStatement("update account set balance = balance + ? where name=?");
ps.setInt(1, 500);
ps.setString(2, "Rose");
ps.executeUpdate();
//提交事务
connection.commit();
System.out.println("转账成功");
} catch (Exception e) {
e.printStackTrace();
try {
//事务的回滚
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
System.out.println("转账失败");
} finally {
//7) 关闭资源
JdbcUtils.close(connection, ps);
}
}
}