一、引言


1.1 如何操作数据库

使用客户端工具访问数据库,需要手工建立连接,输入用户名和密码登录,编写 SQL 语句,点击执行,查看操作结果(结果集或受影响行数)。

image.png

1.2 实际开发中,会采用客户端操作数据库吗?

在实际开发中,当用户的数据发生改变时,不可能通过客户端操作执行 SQL 语句,因为操作量过大,无法保证效率和正确性。

二、JDBC(Java Database Connectivity)

2.1 什么是 JDBC?

JDBC(Java Database Connectivity) Java 连接数据库的规范(标准),可以使用 Java 语言连接数据库完成 CRUD (CREATE READ UPDATE DELETE)操作。

2.2 JDBC 核心思想

Java 中定义了访问数据库的接口,可以为多种关系型数据库提供统一的访问方式。由数据库厂商提供驱动实现类(Driver 数据库驱动)。

image.png

2.2.1 MySQL 数据库驱动
  • mysql-connector-java-5.1.X 适用于 5.X 版本
  • mysql-connector-java-8.0.X 适用于 8.X版本

2.2.2 JDBC API

JDBC 是由多个接口和类进行功能实现。

类型 权限定名 简介
class java.sql.DriverManager 管理多个数据库驱动类,提供了获取数据库连接的方法
interface java.sql.Connection 代表一个数据库连接(当connection不是null时,表示已连接数据库)
interface java.sql.Statement 发送SQL语句到数据库工具
interface java.sql.ResultSet 保存SQL查询语句的结果数据(结果集)
class java.sql.SQLException 处理数据库应用程序时所发生的异常

2.3 环境搭建

  • 在项目下新建 lib 文件夹,用于存放 jar 文件。
  • 将 mysql 驱动mysql-connector-java-5.1.X复制到项目的 lib 文件夹中。
  • 选中 lib 文件夹右键 Add as Libraay,点击 OK。

三、JDBC 开发步骤【重点


3.1 注册驱动

使用 Class.forName(“com.mysql.jdbc.Driver”);手动加载字节码文件到 JVM 中。

  1. Class.forName("com.mysql.jdbc.Driver");//加载驱动

3.2 连接数据库

  • 通过 DriverManager.getConnection(url,user,password) 获取数据库连接对象
    • URL:jdbc:mysql://localhost:3306/database
    • username:root
    • password:1234
  1. Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/cundao?useUnicode=true&characterEncoding=utf8", "root","123456");

URL(Uniform Resource Locator) 统一资源定位符:由协议、IP、端口、SID(程序实例名称)组成

3.3 获取发送 SQL 的对象
通过 Connection 对象获得 Statement 对象,用于对数据库进行通用访问。

  1. Statement statement = conn.createStatement();

3.4 执行SQL 语句

执行 SQL 语句并接收执行结果。

  1. String sql ="INSERT INTO t_jobs(JOB_ID,JOB_TITLE,MIN_SALARY,MAX_SALARY) VALUES('JAVA_Le','JAVA_Lecturer',4000,10000);";
  2. int result = statement.executeUpdate(sql);//执行SQL语句并接收结果

3.5 处理结果

接受处理操作结果。

  1. if(result == 1){
  2. System.out.println("Success");
  3. }
  • 受影响行数:逻辑判断、方法返回。
  • 查询结果集:迭代、依次获取。

3.6 释放资源

遵循先开后关原则,释放所使用到的资源对象。

  1. statement.close();
  2. conn.close();

3.7 综合案例

示例1:

整合以上核心六步,实现向数据库表中删除一条数据。

  1. package com.alpaak.part15.demo1;
  2. import java.sql.Connection;
  3. import java.sql.DriverManager;
  4. import java.sql.Statement;
  5. public class DeleteJdbc {
  6. public static void main(String[] args) throws Exception{
  7. //1.加载驱动
  8. Class.forName("com.mysql.jdbc.Driver");
  9. //2.获得连接对象
  10. Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/cundao?useSSL=false","root","123456");
  11. //3.获得执行SQL的对象
  12. Statement statement = connection.createStatement();
  13. //4.执行SQL语句,并接收结果
  14. int result = statement.executeUpdate("delete from users where username = 'admin';");
  15. //5.处理结果
  16. if(result==1){
  17. System.out.println("删除成功!");
  18. }else{
  19. System.out.println("删除失败!");
  20. }
  21. //6.释放资源
  22. statement.close();
  23. connection.close();
  24. }
  25. }

四、ResultSet(结果集)

在执行查询 SQL 后,存放查询到的结果集数据。

4.1 接收结果集

ResultSet rs = statement.executeQuery(sql);

  1. ResultSet rs= statement.executeQuery("SELECT * FROM t_employees;");

4.2 遍历 ResultSet 中的数据

ResultSet 以表(table)结构进行临时结果的存储,需要通过 JDBC API 将其中数据进行依次获取。

  • 数据行指针:初始位置在第一行数据前,每调用一次 boolean next()方法ResultSet 的指针向下移动一行,结果为 true,表示当前行有数据。
  • rs.getXxx(整数);代表根据列的编号顺序获得,从 1 开始。
  • rs.getXxx(“列名”);代表根据列名获得。
  1. boolean next() throws SQLException //判断 rs 结果集中下一行是否存在数据
  1. int getInt(int columnIndex) throws SQLException //获得当前行第N列的int值
  2. int getInt(String columnLabel) throws SQLException //获得当前行columnLabel列的int值
  3. double getDouble(int columnIndex) throws SQLException //获得当前行第N列的double值
  4. double getDouble(String columnLabel) throws SQLException //获得当前行columnLabel列的double值
  5. String getString(int columnIndex) throws SQLException //获得当前行第N列的String值
  6. String getString(String columnLabel) throws SQLException //获得当前行columnLabel列的String值
  7. ......
  • 注意:列的编号从 1 开始。

    4.3 综合案例

    示例2

    对users 表中的所有数据进行遍历。

4.3.1 根据列的名称获取
  1. package com.alpaak.part15.demo2;
  2. import java.sql.*;
  3. public class Demo2_Query1 {
  4. public static void main(String[] args) throws ClassNotFoundException, SQLException {
  5. //1.加载驱动
  6. Class.forName("com.mysql.jdbc.Driver");
  7. //2.获取数据库连接对象
  8. Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/cundao?useSSL=false", "root", "123456");
  9. //3.获取发送 sql 语句对象
  10. Statement statement = connection.createStatement();
  11. //4.执行 SQL 语句并接收结果集
  12. ResultSet resultSet = statement.executeQuery("select * from student;");
  13. //5 处理结果集
  14. while (resultSet.next()) {
  15. //5.1有数据,依据列名获取数据
  16. int no = resultSet.getInt("no");
  17. String name = resultSet.getString("name");
  18. int age = resultSet.getInt("age");
  19. String sex = resultSet.getString("sex");
  20. System.out.println(no + "\t" + name + "\t" + age + "\t" + sex);
  21. }
  22. //6.释放资源
  23. resultSet.close();
  24. statement.close();
  25. connection.close();
  26. }
  27. }

4.3.2 根据列的编号获取数据
  1. package com.alpaak.part15.demo2;
  2. import java.sql.*;
  3. /**
  4. * jdbc 依据列索引获取数据
  5. */
  6. public class Demo2_Query2 {
  7. public static void main(String[] args) throws ClassNotFoundException, SQLException {
  8. //1.加载驱动
  9. Class.forName("com.mysql.jdbc.Driver");
  10. //2.获取数据库连接对象
  11. Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/cundao?useSSL=false", "root", "123456");
  12. //3.获取发送 sql 语句对象
  13. Statement statement = connection.createStatement();
  14. //4.执行 SQL 语句并接收结果集
  15. ResultSet resultSet = statement.executeQuery("select * from student;");
  16. //5 处理结果集
  17. while (resultSet.next()) {
  18. //5.1有数据,依据列索引获取数据
  19. int no = resultSet.getInt(1);
  20. String name = resultSet.getString(2);
  21. int age = resultSet.getInt(3);
  22. String sex = resultSet.getString(4);
  23. System.out.println(no + "\t" + name + "\t" + age + "\t" + sex);
  24. }
  25. //6.释放资源
  26. resultSet.close();
  27. statement.close();
  28. connection.close();
  29. }
  30. }

五、 常见错误

  • java.lang.ClassNotFoundException:找不到类(类名书写错误、没有导入jar包)
  • java.sql.SQLException:与sql语句相关的错误 (约束错误、表名列名书写错误) 建议:在客户端工具中测试SQL语句之后再粘贴在代码中
  • com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 原因:列值Sting类型没有加单引号
  • Duplicate entry ‘1’ for key ‘PRIMARY’ 原因,主键值已存在或混乱,更改主键值或清空表
  • com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column ‘password’ in
    • 原因:可能输入的值的类型不对,确定是否插入的元素时对应的值的类型正确

六、 综合案例【登录】

6.1 创建表

  • 创建一张用户表 User
    • id ,主键、自动增长。
    • 用户名,字符串类型,唯一、非空
    • 密码,字符串类型,非空
    • 手机号码,字符串类型
  • 插入 2 条测试语句

6.2 实现登录

  • 通过控制台用户输入用户名和密码。
  • 用户输入的用户名和密码作为条件,编写查询 SQL 语句。
  • 如果该用户存在,提示登录成功,反之提示失败。

七、SQL注入问题

7.1 什么是 SQL 注入

用户输入的数据中有 SQL 关键字或语法并且参与了 SQL 语句的编译,导致 SQL 语句编译后的条件含义为 true,一直得到正确的结果。这种现象称为 SQL 注入。

需求:实现登录功能,需要通过传入的登录名和密码查询数据库表中的用户是否存在。
如果使用的是Statement语句来执行sql语句。

  1. String sql="select * from account where username='"+loginName+"' and password='"+loginPwd+"'";

如果应用中传入:

  1. 》请输入用户名:
  2. test1
  3. 》请输入密码:
  4. abc' or '1'='1
  5. username="test1"
  6. password="abc' or '1'='1"

那么当上面的sql拼接变量值之后的sql语句为

  1. select * from account where username='test1' and password='abc' or '1'='1'

那么将登录成功。

7.2 如何避免 SQL 注入

由于编写的 SQL 语句是在用户输入数据,整合后再进行编译。所以为了避免 SQL 注入的问题,我们要使 SQL 语句在用户输入数据前就已进行编译成完整的 SQL 语句,再进行填充数据。

八、PreparedStatement【重点


PreparedStatement 继承了 Statement 接口,执行 SQL 语句的方法无异。

8.1 PreparedStatement的应用

作用:

  • 预编译SQL 语句,效率高。
    PS:什么是预编译?
    预编译语句PreparedStatement是java.sql中的一个接口,它是Statement的子接口。通过Statement对象执行sql语句时,需要将sql语句发送给DBMS,由DBMS首先进行编译再执行(在创建通道的时候并不进行sql的编译工作,事实上也无法进行编译)。而通过PreparedStatement不同,在创建PreparedStatement对象时就指定了sql语句,该语句立即发送给DBMS进行编译,当该语句被执行时,DBMS直接运行编译后的sql语句,而不需要像其他sql语句那样首先将被编译。
    一般在考虑反复使用一个sql语句时才使用预编译,预编译语句常常放在一个循环中使用(在这种情况下预编译的优势就很明显了),通过反复设置参数从而达到多次使用该语句;
  • 安全,避免SQL注入 。
  • 可以动态的填充数据,执行多个同构的 SQL 语句。

8.1.1 参数标记
  1. //1.预编译 SQL 语句
  2. PreparedStatement pstmt = conn.prepareStatement("select * from user where username=? and password=?");
  • 注意:JDBC中的所有参数都由 ?符号占位,这被称为参数标记。在执行SQL语句之前,必须为每个参数提供值。
  • 简化SQL语句的编写,提高SQL语句执行效率,提高安全性。

8.1.2 动态参数绑定

pstmt.setXxx(下标,值) 参数下标从 1 开始,为指定参数下标绑定值

  1. //1.预编译 SQL 语句
  2. PreparedStatement pstmt = conn.prepareStatement("select * from user where username=? and password=?");
  3. //2.为参数下标赋值
  4. pstmt.setString(1,username);
  5. pstmt.setString(2,password);

封装工具类

  • 在实际JDBC的使用中,存在着大量的重复代码:例如连接数据库、关闭数据库等这些操作!
  • 我们需要把传统的JDBC代码进行重构,抽取出通用的JDBC工具类!以后连接任何数据库、释放资源都可以使用这个工具类。

9.1 重用性方案

  • 封装获取连接、释放资源两个方法。
    • 提供public static Connection getConnection(){}方法。
    • 提供public static void closeAll(Connection conn , Statement sm , ResultSet rs){}方法。

示例3: 重用工具类实现

  1. package com.alpaak.part15.demo3;
  2. import java.sql.*;
  3. /**
  4. * 重用性方案
  5. * 获取连接
  6. * 释放资源
  7. */
  8. public class DBUtils {
  9. static {//类加载,执行一次!
  10. try {
  11. Class.forName("com.mysql.jdbc.Driver");
  12. } catch (ClassNotFoundException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16. //1.获取连接
  17. public static Connection getConnection() {
  18. Connection connection = null;
  19. try {
  20. connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/cundao?useSSL=false", "root", "123456");
  21. } catch (SQLException e) {
  22. e.printStackTrace();
  23. }
  24. return connection;
  25. }
  26. //2.释放资源
  27. public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
  28. try {
  29. if (resultSet != null) {
  30. resultSet.close();
  31. }
  32. if (statement != null) {
  33. statement.close();
  34. }
  35. if (connection != null) {
  36. connection.close();
  37. }
  38. } catch (SQLException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }

9.2 跨平台方案

  • 定义public static final Properties prop = new Properties(); //读取配置文件的Map
  • 定义static{
    • //首次使用工具类时,加载驱动
      InputStream is = JDBCUtil.class.getResourceAsStream(“路径”); //通过复用本类自带流,读取jdbc.properties配置文件。classPath = bin
    • prop.load(is); //通过prop对象将流中的配置信息分割成键值对
    • String driverName = prop.getProperty(“driver”); //通过driverName的键获取对应的值(com.mysql.jdbc.Driver)
    • Class.forName(driverName); //加载驱动
      }

9.2.1 跨平台工具类实现

在src 目录下新建 db.properties 文件。

  1. driver=com.mysql.jdbc.Driver
  2. url=jdbc:mysql://localhost:3306/cundao?useSSL=false
  3. username=root
  4. password=123456

工具类的封装。

  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.sql.*;
  4. import java.util.Properties;
  5. public class DBUtils {
  6. private static final Properties PROPERTIES = new Properties();//存储配置文件的map
  7. static {
  8. InputStream is = DBUtils.class.getResourceAsStream("/db.properties");
  9. try {
  10. PROPERTIES.load(is);//通过流,将配置文件内容加载到properties集合
  11. Class.forName(PROPERTIES.getProperty("driver"));
  12. } catch (IOException e) {
  13. e.printStackTrace();
  14. } catch (ClassNotFoundException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. public static Connection getConnection() {
  19. Connection connection = null;
  20. try {
  21. connection = DriverManager.getConnection(PROPERTIES.getProperty("url"), PROPERTIES.getProperty("username"), PROPERTIES.getProperty("password"));
  22. } catch (SQLException e) {
  23. e.printStackTrace();
  24. }
  25. return connection;
  26. }
  27. public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
  28. try {
  29. if (resultSet != null) {
  30. resultSet.close();
  31. }
  32. if (statement != null) {
  33. statement.close();
  34. }
  35. if (connection != null) {
  36. connection.close();
  37. }
  38. } catch (SQLException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }

调用工具类

  1. package com.alpaak.part15.demo4;
  2. import java.sql.Connection;
  3. import java.sql.ResultSet;
  4. import java.sql.SQLException;
  5. import java.sql.Statement;
  6. /**
  7. * 读取文件配置
  8. * db.properties
  9. */
  10. public class Demo4 {
  11. public static void main(String[] args) throws SQLException {
  12. //1.获得连接
  13. Connection conn = DBUtils.getConnection();
  14. //2.获取发送 sql 语句对象
  15. Statement statement = conn.createStatement();
  16. //3.执行 SQL 语句并接收结果集
  17. ResultSet resultSet = statement.executeQuery("select * from student;");
  18. //5 处理结果集
  19. while (resultSet.next()) {
  20. //5.1有数据,依据列名获取数据
  21. int no = resultSet.getInt("no");
  22. String name = resultSet.getString("name");
  23. int age = resultSet.getInt("age");
  24. String sex = resultSet.getString("sex");
  25. System.out.println(no + "\t" + name + "\t" + age + "\t" + sex);
  26. }
  27. DBUtils.closeAll(conn,statement,resultSet);
  28. }
  29. }