java进阶-第9章-JDBC

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
云数据库 RDS PostgreSQL,高可用系列 2核4GB
简介: 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();
        }
    }
}


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。   相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情: https://www.aliyun.com/product/rds/mysql 
相关文章
|
7月前
|
SQL Java 中间件
【YashanDB知识库】yasdb jdbc驱动集成BeetISQL中间件,业务(java)报autoAssignKey failure异常
在BeetISQL 2.13.8版本中,客户使用batch insert向yashandb表插入数据并尝试获取自动生成的sequence id时,出现类型转换异常。原因是beetlsql在prepareStatement时未指定返回列,导致yashan JDBC驱动返回rowid(字符串),与Java Bean中的数字类型tid不匹配。此问题影响业务流程,使无法正确获取sequence id。解决方法包括:1) 在batchInsert时不返回自动生成的sequence id;2) 升级至BeetISQL 3,其已修正该问题。
【YashanDB知识库】yasdb jdbc驱动集成BeetISQL中间件,业务(java)报autoAssignKey failure异常
|
Java 关系型数据库 数据库连接
JDBC:Java与数据库的“黄金搭档”,为何它如此重要?
JDBC:Java与数据库的“黄金搭档”,为何它如此重要?
110 8
|
Java 数据库连接 API
JDBC:Java数据库连接的“黑科技”大揭秘
JDBC:Java数据库连接的“黑科技”大揭秘
85 7
|
SQL Java 数据库连接
为何JDBC是Java开发者的“心头好”?原因竟然这么简单!
为何JDBC是Java开发者的“心头好”?原因竟然这么简单!
95 3
|
7月前
|
SQL druid Oracle
【YashanDB知识库】yasdb jdbc驱动集成druid连接池,业务(java)日志中有token IDENTIFIER start异常
客户Java日志中出现异常,影响Druid的merge SQL功能(将SQL字面量替换为绑定变量以统计性能),但不影响正常业务流程。原因是Druid在merge SQL时传入null作为dbType,导致无法解析递归查询中的`start`关键字。
|
10月前
|
Java 数据库连接 数据库
springboot java.lang.ClassNotFoundException: dm.jdbc.driver.DmDriver应该如何解决
通过上述步骤,可以有效解决Spring Boot项目中遇到的 `java.lang.ClassNotFoundException: dm.jdbc.driver.DmDriver`问题。确保在项目中正确添加达梦数据库的JDBC驱动依赖,并在配置文件中正确配置数据源信息,是解决此问题的关键。通过这些方法,可以确保Spring Boot项目能够正确连接达梦数据库并正常运行。
1810 31
|
9月前
|
SQL Java 关系型数据库
使用 JDBC 实现 Java 数据库操作
JDBC(Java Database Connectivity)是 Java 提供的数据库访问技术,允许通过 SQL 语句与数据库交互。本文详细介绍了 JDBC 的使用方法,包括环境准备、编程步骤和完整示例。
795 7
|
9月前
|
SQL Java 数据库连接
【潜意识Java】Java中JDBC过时方法的替代方案以及JDBC为什么过时详细分析
本文介绍了JDBC中一些常见过时方法及其替代方案。
173 5
|
SQL Java 关系型数据库
【前端学java】JDBC快速入门
【8月更文挑战第12天】JDBC快速入门
129 2
【前端学java】JDBC快速入门
|
Java 数据库连接 网络安全
JDBC数据库编程(java实训报告)
这篇文章是关于JDBC数据库编程的实训报告,涵盖了实验要求、实验环境、实验内容和总结。文中详细介绍了如何使用Java JDBC技术连接数据库,并进行增删改查等基本操作。实验内容包括建立数据库连接、查询、添加、删除和修改数据,每个部分都提供了相应的Java代码示例和操作测试结果截图。作者在总结中分享了在实验过程中遇到的问题和解决方案,以及对Java与数据库连接操作的掌握情况。
JDBC数据库编程(java实训报告)