JDBC
JDBC入门
客户端操作 MySQL 数据库的方式:
# 使用第三方客户端来访问 MySQL:SQLyog、Navicat、SQL Wave、MyDB Studio、EMS SQL Manager for MySQL
# 使用 MySQL 自带的命令行方式
# 通过 Java 来访问 MySQL 数据库
什么是 JDBC
# JDBC 是规范定义接口,具体的实现由各大数据库厂商来提供
* JDBC 是 Java 访问数据库的标准规范,真正怎么操作数据库还需要具体的实现类,也就是数据库驱动。每个数据库厂商根据自家数据库的通信格式编写好自己数据库的驱动。所以我们只需要会调用 JDBC 接口中的方法即可,数据库驱动由数据库厂商提供。
# 使用 JDBC 的好处:
* 程序员如果要开发放访问数据库的程序,只需要会调用 JDBC 接口中的方法即可,不用关注类是如何实现的。
* 使用同一套 Java 代码,进行少量的修改就可以访问其他 JDBC 支持的数据库
# 使用 JDBC 开发使用到的包:
JDBC 的核心 API
导入驱动 Jar 包
加载和注册驱动
# 为什么这样可以注册驱动?
* Driver 接口:所有数据库厂商都必须实现的接口,表示这是一个驱动类
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package com.mysql.jdbc; import java.sql.DriverManager; import java.sql.SQLException; public class Driver extends NonRegisteringDriver implements java.sql.Driver { public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } }
# 从 JDBC3 开始,目前已经普遍使用的版本可以不注册驱动而直接使用。
* Class.forName() 这句话可以省略
DriverManager 类
DriverManager 作用
# 管理和注册驱动
# 创建数据库的连接
类中的方法
使用 JDBC 连接数据库的四个参数
连接数据库的 URL 地址格式
# MySQL 写法:
# MySQL 中可以简写
# 乱码的处理
* 指定参数:?characterEncoding=utf8,表示让数据库以 UTF-8 编码来处理数据
案例:得到 MySQL 的数据库连接对象
# 使用 用户名、密码、URL 得到连接对象
# 使用 属性文件 和 URL 得到连接对象
Connection接口
# Connection 作用
* Connection 接口,具体的实现类由数据库的厂商实现,代表一个连接对象
# Connection 的方法
Statement 接口
JDBC 访问数据库的步骤
1) 注册和加载驱动(可以省略)
2)获取连接
3)Connection 获取 Statement 对象
4)使用 Statement 对象执行 SQL 语句
5)返回结果集
6)释放资源
Statement 的作用
# 代表一条语句,用于发送 SQL 语句给服务器,用于执行静态 SQL 语句并返回它所生成结果的对象
Statement 中的方法
释放资源
# 需要释放的对象:ResultSet 结果集,Statement 语句,Connection 连接
# 释放原则:先开的后关,后开的先关。ResultSet -> Statement -> Connection
# 放在哪个代码块中:finally 块
执行 DDL 操作
# 需求:使用 JDBC 在 MySQL 的数据库中创建一张学生表
# 代码
package cn.itcast.test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; /** * 创建一张学生表 */ public class Demo4DDL { public static void main(String[] args) { //1. 创建连接 Connection conn = null; Statement statement = null; try { conn = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root"); //2. 通过连接对象得到语句对象 statement = conn.createStatement(); //3. 通过语句对象发送SQL语句给服务器 // 4. 执行SQL statement.executeUpdate("create table student (id int PRIMARY key auto_increment, " + "name varchar(20) not null, gender boolean, birthday date)"); //5. 返回影响行数(DDL没有返回值) System.out.println("创建表成功"); } catch (SQLException e) { e.printStackTrace(); } //6. 释放资源 finally { //关闭之前要先判断 if (statement != null) { try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
执行 DML 操作
# 需求:向学生表中添加 4 条记录,主键是自动增长
# 步骤:
1)创建连接对象
2)创建 Statement 语句对象
3)执行 SQL 语句:executeUpdate(sql)
4)返回影响的行数
5)释放资源
# 代码
package cn.itcast.test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; /** * 向学生表中添加4条记录,主键是自动增长 */ public class Demo5DML { public static void main(String[] args) throws SQLException { // 1) 创建连接对象 Connection connection = DriverManager.getConnection("jdbc:mysql:///day24", "root", "root"); // 2) 创建Statement语句对象 Statement statement = connection.createStatement(); // 3) 执行SQL语句:executeUpdate(sql) int count = 0; // 4) 返回影响的行数 count += statement.executeUpdate("insert into student values(null, '孙悟空', 1, '1993-0324')"); count += statement.executeUpdate("insert into student values(null, '白骨精', 0, '1995-0324')"); count += statement.executeUpdate("insert into student values(null, '猪八戒', 1, '1903-0324 ')"); count += statement.executeUpdate("insert into student values(null, ' 嫦娥 ', 0, '1993 - 0311')"); System.out.println("插入了" + count + "条记录"); // 5) 释放资源 statement.close(); connection.close(); } }
执行 DQL 操作
# ResultSet 接口:
* 作用:封装数据库查询的结果集,对结果集进行遍历,取出每一条记录。
# 接口中的方法:
# 常用的数据类型转换表
* java.sql.Date 、time 、timestamp(时间戳),三个的共同父类是 java.utils.Date
# 需求:确保数据库中有 3 条以上的记录,查询所有的学员信息
* 步骤:
1)得到连接对象
2)得到语句对象
3)执行 SQL 语句得到结果集 ResultSet 对象
4)循环遍历取出每一条记录
5)输出到控制台上
6)释放资源
* 结果:
* 代码实现
package cn.itcast.test; import java.sql.*; public class Demo6SQL { public static void main(String[] args) throws SQLException { //1) 得到连接对象 Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/day24", "root", "root"); //2) 得到语句对象 Statement statement = connection.createStatement(); //3) 执行SQL语句得到结果集ResultSet对象 ResultSet rs = statement.executeQuery("select * from student"); // 4) 循环遍历取出每一条记录 while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("name"); boolean gender = rs.getBoolean("gender"); Date birthday = rs.getDate("birthday"); // 5) 输出的控制台上 System.out.println("编号:" + id + ", 姓名:" + name + ", 性别:" + gender + ", 生日:" + birthday); } // 6) 释放资源 rs.close(); statement.close(); connection.close(); } }
# 关于 ResultSet 接口中的注意事项:
1)如果光标在第一行之前,使用 rs.getXxx() 获得列值,报错:Before start of result set
2)如果光标在最后一行之后,使用 rs.getXxx() 获得值,报错:After end of result set
3)使用完毕以后要关闭结果集 ResultSet,在关闭 Statement,再关闭 Connection
数据库工具类 JdbcUtils
创建 JdbcUtils 包含 3 个方法
# 需求
1)可以把几个字符串定义成常量:用户名,密码,URL,驱动类
2)得到数据库的连接
3)关闭所有打开资源
# 代码实现
package cn.itcast.test; import java.sql.*; /** * 访问数据库的工具类 */ public class JdbcUtils { // 可以把几个字符串定义成常量:用户名、密码、URL、驱动类 private static final String USER = "root"; private static final String PWD = "123"; private static final String URL = "jdbc:mysql://localhost:3306/db3"; private static final String DRIVER = "com.mysql.jdbc.Driver"; /** * 注册驱动 */ static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 得到数据库的连接 */ public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PWD); } /** * 关闭所有已打开的资源 */ public static void close(Connection conn, Statement stmt, ResultSet rs) { 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(); } } } public static void close(Connection conn, Statement stmt) { close(conn, stmt, null); } }
案例:用户登录
# 需求:
1)有一张用户表
2)添加几条用户记录
3)使用 Statement 字符串拼接的方式实现用户的登录,用户再控制台上输入用户名和密码
# 步骤:
1)得到用户从控制台上输入的用户名和密码来查询数据库
2)写一个登录的方法
a)通过工具类得到连接
b)创建语句对象,使用拼接字符串的方式生成 SQL 语句
c)查询数据库,如果有记录则表示登录成功,否则登录失败
d)释放资源
# 代码实现
package cn.itcast.test; import cn.itcast.util.JDBCUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Scanner; public class Demo7Login { public static void main(String[] args) { // 从控制台上输入用户名和密码 Scanner sc = new Scanner(System.in); System.out.println("请输入用户名"); String name = sc.nextLine(); System.out.println("请输入密码"); String password = sc.nextLine(); login(name, password); } public static void login(String name, String password) { Connection conn = null; Statement stmt = null; ResultSet rs = null; try { // 通过工具类得到连接 conn = JDBCUtils.getConnection(); // 创建语句对象,使用拼接字符串的方式生成 SQL 语句 stmt = conn.createStatement(); // 查询数据库,如果有记录则表示登录成功,否则登录失败 String sql = "select * from user where name = '" + name + "'and password = '" + password + "'"; rs = stmt.executeQuery(sql); if (rs.next()) { System.out.println("登录成功,欢迎您:" + name); } else { System.out.println("登录失败"); } } catch (SQLException e) { e.printStackTrace(); } finally { // 释放资源 JdbcUtils.close(conn, stmt, rs); } } }
# SQL 注入问题
* 当我们输入以下密码,我们发现我们账号和密码在都错误的情况下竟然登录成功了
* 分析:
* 我们让用户输入的密码和 SQL 语句进行字符串拼接。用户输入的内容作为 SQL 语句语法的一部分,改变了原有 SQL 真正的意义,以上问题称为 SQL 注入。要解决该问题就不能让用户输入的密码和我们的 SQL 语句进行简单的字符串拼接。
PreparedStatement 接口
继承结构与作用
# PreparedStatement 是 Statement 接口的子接口,继承于父接口中所有的方法。它是一个预编译的 SQL 语句
PreparedStatement 的执行原理
# 因为有预先编译的功能,提高了 SQL 的执行效率;可以有效的防止 SQL 注入的问题,安全性更高
Connection 创建 PreparedStatement 对象
PreparedStatement 接口中的方法
PreparedStatement 的好处
# PreparedStatement() 会先将 SQL 语句发送给数据库预编译。PreparedStatement 会引用经预编译后的结果。可以多次传入不同的参数给 PreparedStatement 对象并执行。减少 SQL 编译次数,提高效率。
# 安全性更高,没有 SQL 注入的隐患。并且提高了程序的可读性。
使用 PreparedStatement 的步骤
# 步骤:
1)编写 SQL 语句,位置内容使用 ?占位:"SELECT * FROM user WHERE name = ? AND password = ?"
2)获得 PreparedStatement 对象
3)设置实际参数:setXxx(占位符的位置,真实的值)
4)执行参数化 SQL 语句
5)关闭资源
# 使用 PreparedStatement 改写上面的程序
package cn.itcast.test; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Scanner; /** * 使用PreparedStatement */ public class Demo8Login { public static void main(String[] args) { // 从控制台获取账号密码 Scanner sc = new Scanner(System.in); String name = sc.nextLine(); String password = sc.nextLine(); login(name, password); } /** * 登录的方法 * * @param name * @param password */ private static void login(String name, String password) throws SQLException { Connection conn = JdbcUtils.getConnection(); // 定义 SQL 语句,没有单引号 String sql = "select * from user where name = ? and password = ?"; // 得到语句对象 PreparedStatement ps = conn.prepareStatement(sql); // 设置参数 ps.setString(1, name); ps.setString(2, password) l; // 执行 SQL 语句 ResultSet rs = ps.executeQuery(); if (rs.next()) { System.out.println("登录成功"); } else { System.out.println("登录失败"); } // 释放资源 JdbcUtils.close(conn, ps, rs); } }
表和类的关系
# 案例:使用 PreparedStatement 查询一条数据,封装成一个学生 Student 对象
package cn.itcast.test; import cn.itcast.util.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class Demo9Student { public static void main(String[] args) throws SQLException { // 创建学生对象 Student stu = new Student(); // 获取连接对象 Connection conn = JDBCUtils.getConnection(); // 获取执行 SQL 对象 PreparedStatement ps = conn.prepareStatement("select * from student where id =?"); // 设置参数 ps.setInt(1, 2); // 执行 SQL 语句 ResultSet rs = ps.executeQuery(); if (rs.next()) { stu.setId(rs.getInt("id")); stu.setName(rs.getString("name")); stu.setGender(rs.getString("gender")); stu.setBirthday(rs.getDate("birthday")); } // 释放资源 JdbcUtils.close(conn, ps, rs); // 打印数据 System.out.println(stu); } }
# 案例:将多条记录封装成集合 List<Student>,集合中每个元素是一个 JavaBean 实体类
package cn.itcast.test; import cn.itcast.util.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 Demo10List { public static void main(String[] args) throws SQLException { // 创建一个集合 List<Student> list = new ArrayList<>(); // 创建连接对象 Connection conn = JDBCUtils.getConnection(); // 创建 SQL 执行对象 PreparedStatement ps = conn.prepareStatement("select * from student"); // 执行 SQL 语句 ResultSet rs = ps.executeQuery(); while (rs.next()) { // 每次循环是一个学生对象 Student stu = new Student(); // 封装成一个学生对象 stu.setId(rs.getInt("id")); stu.setName(rs.getString("name")); stu.setGender(rs.getString("gender")); stu.setBirthday(rs.getDate("birthday")); // 将数据放到集合中 list.add(stu); } // 关闭连接 JDBCUtils.close(rs, ps, conn); // 使用数据 for (Student student : list) { System.out.println(student); } } }
JDBC 事务的处理
# 准备数据
# API 介绍
# 步骤:
1)获取连接
2)开启事务
3)获取到 PreparedStatement
4)使用 PreparedStatement 执行两次更新操作
5)正常情况下提交事务
6)出现异常回滚事务
7)关闭资源
# 代码
package cn.itcast.test; import cn.itcast.util.JDBCUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class Demo12Transaction { // 没有异常,提交事务;出现异常,回滚事务 public static void main(String[] args) { Connection conn = null; PreparedStatement ps = null; try { // 注册驱动,获取连接 conn = JDBCUtils.getConnection(); // 开启事务 conn.setAutoCommit(false); // 获取 SQL 执行对象 ps = conn.prepareStatement("update account set balance = balance - ? where id = ?"); // 设置参数 ps.setInt(1, 500); ps.setInt(2, 1); ps.executeUpdate(); // 出现异常 System.out.println(100 / 0); // 覆盖 SQL 执行对象 ps = conn.prepareStatement("update account set balance = balance + ? where id = ?"); ps.setInt(1, 500); ps.setInt(2, 2); ps.executeUpdate(); // 提交事务 conn.commit(); System.out.println("转账成功"); } catch (SQLException e) { e.printStackTrace(); try { // 事务的回滚 conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } finally { JDBCUtils.close(ps, conn); } } }