java进阶-第9章-JDBC

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: Java 中定义了访问数据库的接口,可以为多种关系型数据库提供统一的访问方式。由数据库厂商提供驱动实现类(Driver 数据库驱动)。

一、引言

数据库访问层

该层所做事务直接操作数据库,针对数据的增添、删除、修改、更新、查找等。

简单的说法就是实现对数据表的Select,Insert,Update,Delete的操作。如果要加入ORM的元素,那么就会包括对象和数据表之间的mapping,以及对象实体的持久化。


优点

1、开发人员可以只关注整个结构中的其中某一层;

2、可以很容易的用新的实现来替换原有层次的实现;

3、可以降低层与层之间的依赖;

4、有利于标准化;

5、利于各层逻辑的复用


缺点

1、降低了系统的性能。这是不言而喻的。如果不采用分层式结构,很多业务可以直接造访数据库,以此获取相应的数据,如今却必须通过中间层来完成。

2、有时会导致级联的修改。这种修改尤其体现在自上而下的方向。如果在表示层中需要增加一个功能,为保证其设计符合分层式结构,可能需要在相应的业务逻辑层和数据访问层中都增加相应的代码。


1.1 如何操作数据库

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

ea280b9860606adea5601605eec52c6b_6b4d43c192adc05e6b8d58e1ce83bc53.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 数据库驱动)。


5873cfbc101376c2657606b1b48c7dcd_948a6bb149399b049fc6d7a6d208639a.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 中。

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

3.2 连接数据库

通过 DriverManager.getConnection(url,user,password) 获取数据库连接对象

URL:jdbc:mysql://localhost:3306/database

username:root

password:1234

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/tuling?useUnicode=true&characterEncoding=utf8", "root","1234");

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

3.3 获取发送 SQL 的对象

通过 Connection 对象获得 Statement 对象,用于对数据库进行通用访问。


Statement statement = conn.createStatement();

3.4 执行SQL 语句

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


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

注意:在编写 DML 语句时,一定要注意字符串参数的符号是单引号 ‘值’

DML 语句:增删改时,返回受影响行数(int 类型)。

DQL 语句:查询时,返回结果数据(ResultSet 结果集)。

3.5 处理结果

接受处理操作结果。


if(result == 1){
  System.out.println("Success");
}

受影响行数:逻辑判断、方法返回。

查询结果集:迭代、依次获取。

3.6 释放资源

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

statement.close();
conn.close();

3.7 综合案例

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


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class DeleteJdbc {
    public static void main(String[] args) throws Exception{
        //1.加载驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2.获得连接对象
        Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb","root","1234");
        //3.获得执行SQL的对象
        Statement statement = connection.createStatement();
        //4.执行SQL语句,并接收结果
        int result = statement.executeUpdate("delete from t_jobs where job_id = 'H5_mgr';");
        //5.处理结果
        if(result==1){
            System.out.println("删除成功!");
        }else{
            System.out.println("删除失败!");
        }
        //6.释放资源
        statement.close();
        connection.close();
    }
}

四、ResultSet(结果集)

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


4.1 接收结果集

ResultSet rs = statement.executeQuery(sql);

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

4.2 遍历 ResultSet 中的数据

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


数据行指针:初始位置在第一行数据前,每调用一次 boolean next()方法ResultSet 的指针向下移动一行,结果为 true,表示当前行有数据。

rs.getXxx(整数);代表根据列的编号顺序获得,从 1 开始。

rs.getXxx(“列名”);代表根据列名获得。

boolean next() throws SQLException  //判断 rs 结果集中下一行是否存在数据

4.2.1 遍历方法

int getInt(int columnIndex) throws SQLException //获得当前行第N列的int值
int getInt(String columnLabel) throws SQLException //获得当前行columnLabel列的int值
double getDouble(int columnIndex) throws SQLException //获得当前行第N列的double值
double getDouble(String columnLabel) throws SQLException //获得当前行columnLabel列的double值
String getString(int columnIndex) throws SQLException //获得当前行第N列的String值
String getString(String columnLabel) throws SQLException //获得当前行columnLabel列的String值
......

注意:列的编号从 1 开始。

4.3 综合案例

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


4.3.1 根据列的名称获取

import java.sql.*;
public class JobsQuery {
    public static void main(String[] args) {
            //1.加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.获取数据库连接对象
            Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/product", "root", "1234");
            //3.获取发送 sql 语句对象
            Statement statement = connection.createStatement();
            //4.执行 SQL 语句并接收结果集
            ResultSet resultSet = statement.executeQuery("select  * from t_jobs");
            //5 处理结果集
            while(resultSet.next()){
                //5.1有数据,依据列名获取数据
                String job_id = resultSet.getString("job_id");
                String job_title = resultSet.getString("job_title");
                int min_salary = resultSet.getInt("min_salary");
                int max_salary = resultSet.getInt("max_salary");
                System.out.println(job_id+"\t"+job_title+"\t"+min_salary+"\t"+max_salary);
            }
                //6.释放资源
               rs.close();
           statement.close();
           connection.close();
        }
    }
    }
}

4.3.2 根据列的编号获取数据

 

//。。。。与上无异
            while(resultSet.next()){
                //5.2有数据,依据列名获取数据
                String job_id = resultSet.getString(1);
                String job_title = resultSet.getString(2);
                int min_salary = resultSet.getInt(3);
                int max_salary = resultSet.getInt(4);
                System.out.println(job_id+"\t"+job_title+"\t"+min_salary+"\t"+max_salary);
            }
      //释放资源

五、 常见错误

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语句。


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

如果应用中传入:


》请输入用户名:
test1
》请输入密码:
abc' or '1'='1
username="test1"
password="abc' or '1'='1"

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

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.预编译 SQL 语句
PreparedStatement pstmt = conn.prepareStatement("select * from user where username=? and password=?");

注意:JDBC中的所有参数都由 ?符号占位,这被称为参数标记。在执行SQL语句之前,必须为每个参数提供值。

简化SQL语句的编写,提高SQL语句执行效率,提高安全性。

8.1.2 动态参数绑定

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

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


九、封装工具类

在实际JDBC的使用中,存在着大量的重复代码:例如连接数据库、关闭数据库等这些操作!

我们需要把传统的JDBC代码进行重构,抽取出通用的JDBC工具类!以后连接任何数据库、释放资源都可以使用这个工具类。

9.1 重用性方案

封装获取连接、释放资源两个方法。

提供public static Connection getConnection(){}方法。

提供public static void closeAll(Connection conn , Statement sm , ResultSet rs){}方法。

9.1.1 重用工具类实现

import java.sql.*;
/**
 * 重用性方案
 * 获取连接
 * 释放资源
 */
public class DBUtils {
    static {//类加载,执行一次!
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    //1.获取连接
    public static Connection getConnection() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/companydb", "root", "1234");
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }
    //2.释放资源
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

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 文件。


driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mydb3
user=root
password=1234

工具类的封装。

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class DBUtils {
    private static final Properties PROPERTIES = new Properties();//存储配置文件的map
    static {
        InputStream is = DBUtils.class.getResourceAsStream("/db.properties");
        try {
            PROPERTIES.load(is);//通过流,将配置文件内容加载到properties集合
            Class.forName(PROPERTIES.getProperty("driver"));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    public static Connection getConnection() {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(PROPERTIES.getProperty("url"), PROPERTIES.getProperty("username"), PROPERTIES.getProperty("password"));
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return connection;
    }
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if (resultSet != null) {
                resultSet.close();
            }
            if (statement != null) {
                statement.close();
            }
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}


相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2天前
|
SQL Java 数据库连接
java使用jdbc操作数据库
java使用jdbc操作数据库
|
2天前
|
缓存 安全 Java
Java并发编程进阶:深入理解Java内存模型
【4月更文挑战第6天】Java内存模型(JMM)是多线程编程的关键,定义了线程间共享变量读写的规则,确保数据一致性和可见性。主要包括原子性、可见性和有序性三大特性。Happens-Before原则规定操作顺序,内存屏障和锁则保障这些原则的实施。理解JMM和相关机制对于编写线程安全、高性能的Java并发程序至关重要。
|
2天前
|
SQL Java 数据库连接
Java一分钟之-JDBC:Java数据库连接基础
【5月更文挑战第14天】JDBC是Java与关系数据库交互的API,常见问题包括资源管理、SQL注入和性能优化。易错点涉及驱动加载、空指针异常和事务管理。避免这些问题的方法有使用try-with-resources自动关闭资源、预编译PreparedStatement以防止SQL注入,以及正确管理事务。示例代码展示了基本的JDBC连接和查询。在实际开发中,推荐使用ORM框架如Hibernate或JPA来简化数据库操作。
13 1
|
2天前
|
SQL Java 关系型数据库
零基础轻松入门Java数据库连接(JDBC)
零基础轻松入门Java数据库连接(JDBC)
12 0
|
2天前
|
存储 安全 Java
Java一分钟之-集合框架进阶:Set接口与HashSet
【5月更文挑战第10天】本文介绍了Java集合框架中的`Set`接口和`HashSet`类。`Set`接口继承自`Collection`,特征是不允许重复元素,顺序不确定。`HashSet`是`Set`的实现,基于哈希表,提供快速添加、删除和查找操作,但无序且非线程安全。文章讨论了`HashSet`的特性、常见问题(如元素比较规则、非唯一性和线程安全性)以及如何避免这些问题,并提供了代码示例展示基本操作和自定义对象的使用。理解这些概念和注意事项能提升代码效率和可维护性。
12 0
|
2天前
|
SQL Java 数据库连接
JDBC Java标准库提供的一些api(类+方法) 统一各种数据库提供的api
JDBC Java标准库提供的一些api(类+方法) 统一各种数据库提供的api
11 0
|
2天前
|
Java 关系型数据库 MySQL
【JDBC编程】基于MySql的Java应用程序中访问数据库与交互数据的技术
【JDBC编程】基于MySql的Java应用程序中访问数据库与交互数据的技术
|
2天前
|
Java API 调度
[AIGC] 深入理解Java并发编程:从入门到进阶
[AIGC] 深入理解Java并发编程:从入门到进阶
|
2天前
|
SQL Java 数据库连接
Java从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
ava从入门到精通:2.3.1数据库编程——学习JDBC技术,掌握Java与数据库的交互
|
2天前
|
存储 Java
java面向对象进阶
java面向对象进阶