Lazy loaded image
学习思考
Lazy loaded imageJavaSE——JDBC与网络编程
字数 16630阅读时长 42 分钟
2025-2-21
2025-3-22
type
status
date
slug
summary
tags
category
icon
password

JDBC

概述

JDBC(Java Data Base Connectivity,Java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成,是Java访问数据库的标准规范。
JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
JDBC需要连接驱动,驱动是两个设备要进行通信,满足一定通信数据格式,数据格式由设备提供商规定,设备提供商为设备提供驱动软件,通过软件可以与该设备进行通信。

原理

Java提供访问数据库规范称为JDBC,而生产厂商提供规范的实现类称为驱动。
JDBC是接口,驱动是接口的实现,没有驱动将无法完成数据库连接,从而不能操作数据库!
每个数据库厂商都需要提供自己的驱动,用来连接自己公司的数据库,也就是说驱动一般都由数据库生成厂商提供。

常用接口

Driver 接口

Driver接口由数据库厂家提供,作为java开发人员,只需要使用Driver接口就可以了。在编程中要连接数据库,必须先装载特定厂商的数据库驱动程序,不同的数据库有不同的装载方法。如:

Connection 接口

Connection与特定数据库的连接(会话),在连接上下文中执行SQL语句并返回结果。
常用方法:
方法
解释
createStatement()
创建向数据库发送sql的statement对象
prepareStatement(sql)
创建向数据库发送预编译sql的PrepareSatement对象
prepareCall(sql)
创建执行存储过程的callableStatement对象
setAutoCommit(boolean autoCommit)
设置事务是否自动提交
commit()
在链接上提交事务
rollback()
在此链接上回滚事务

Statement接口

用于执行静态SQL语句并返回它所生成结果的对象。

三种Statement类

  • Statement:由createStatement创建,用于发送简单的SQL语句(不带参数)
  • PreparedStatement:继承自Statement接口,由preparedStatement创建,用于发送含有一个或多个参数的SQL语句。PreparedStatement对象比Statement对象的效率更高,并且可以防止SQL注入,所以我们一般都使用PreparedStatement
  • CallableStatement:继承自PreparedStatement接口,由方法prepareCall创建,用于调用存储过程
当不直接使用SQL语句,而是调用数据库中的存储过程时,要用到Callable Statement。

常用Statement方法

方法
解释
execute(String sql)
运行语句,返回是否有结果集
executeQuery(String sql)
运行select语句,返回ResultSet结果集
executeUpdate(String sql)
运行insert/update/delete操作,返回更新的行数
addBatch(String sql)
把多条sql语句放到一个批处理中
executeBatch()
向数据库发送一批sql语句执行

ResultSet接口

ResultSet提供检索不同类型字段的方法。

常用方法

方法
解释
getString(int index)、getString(String columnName)
获得在数据库里是varchar、char等类型的数据对象
getFloat(int index)、getFloat(String columnName)
获得在数据库里是Float类型的数据对象
getDate(int index)、getDate(String columnName)
获得在数据库里是Date类型的数据
getBoolean(int index)、getBoolean(String columnName)
获得在数据库里是Boolean类型的数据
getObject(int index)、getObject(String columnName)
获取在数据库里任意类型的数据

对结果集进行滚动的方法

方法
解释
next()
移动到下一行
Previous()
移动到前一行
absolute(int row)
移动到指定行
beforeFirst()
移动resultSet的最前面
afterLast()
移动到resultSet的最后面
使用后依次关闭对象及连接:ResultSet → Statement → Connection。

步骤

加载JDBC驱动程序 → 建立数据库连接Connection → 创建执行SQL的语句Statement → 处理执行结果ResultSet → 释放资源。

注册驱动 (只做一次)

  1. Class.forName(“com.MySQL.jdbc.Driver”);,推荐这种方式,不会对具体的驱动类产生依赖
  1. DriverManager.registerDriver(com.mysql.jdbc.Driver);,会造成DriverManager中产生两个一样的驱动,并会对具体的驱动类产生依赖

建立连接

URL用于标识数据库的位置,通过URL地址告诉JDBC程序连接哪个数据库。
其他参数如:useUnicode=true&characterEncoding=utf8。

创建执行SQL语句的statement

处理执行结果(ResultSet)

释放资源

案例分析

准备数据

  1. 创建数据库和表结构
    1. 向表中插入数据

      JDBC的开发步骤

      1. 注册驱动:告知JVM使用的是哪一个数据库的驱动
      1. 获得连接:使用JDBC中的类,完成对MySQL数据库的连接
      1. 获得语句执行平台:通过连接对象获取对SQL语句的执行者对象
      1. 执行SQL语句:使用执行者对象,向数据库执行SQL语句,获取到数据库的执行后的结果
      1. 处理结果
      1. 释放资源:一堆close()方法

      注册数据库驱动程序

      JDBC规范定义驱动接口:java.sql.Driver,MySql驱动包提供了实现类:com.mysql.jdbc.Driver,DriverManager工具类,提供注册驱动的方法 registerDriver(),方法的参数是java.sql.Driver,所以可以通过如下语句进行注册:
      以上代码不推荐使用,存在两方面不足:
      1. 硬编码,后期不易于程序扩展和维护
      1. 驱动被注册两次
      通常开发时使用Class.forName()加载一个使用字符串描述的驱动类;如果使用Class.forName()将类加载到内存,该类的静态代码将自动执行;通过查询com.mysql.jdbc.Driver源码,可以发现Driver类“主动”将自己进行注册。

      获取数据库的连接对象

      获取连接需要方法DriverManager.getConnection(url,username,password),三个参数分别为:url是需要连接数据库的位置(网址) 、user是用户名 、password是密码。
      url比较复杂,下面是MySQL的url:
      JDBC规定url的格式由三部分组成,每个部分中间使用冒号分隔:
      • 第一部分是jdbc,这是固定的;
      • 第二部分是数据库名称,那么连接mysql数据库,第二部分当然是mysql
      • 第三部分是由数据库厂商规定的,需要了解每个数据库厂商的要求,mysql的第三部分由数据库服务器的IP地址(localhost)、端口号(3306),以及DATABASE名称(mydb)组成

      获取SQL语句的执行对象对象

      常用方法:
      • int executeUpdate(String sql):执行insert、update、delete语句
      • ResultSet executeQuery(String sql):执行select语句
      • boolean execute(String sql):执行select返回true,执行其他的语句返回false

      执行insert语句获取结果集

      执行select语句获取结果集

      ResultSet实际上就是一张二维的表格,可以调用其boolean next()方法指向某行记录,当第一次调用next()方法时,便指向第一行记录的位置,这时就可以使用ResultSet提供的getXXX(int col)方法(与索引从0开始不同,列从1开始)来获取指定列的数据:
      常用方法:
      • Object getObject(int index) / Object getObject(String name):获得任意对象
      • String getString(int index) / Object getObject(String name):获得字符串
      • int getInt(int index) / Object getObject(String name):获得整型
      • double getDouble(int index) / Object getObject(String name):获得双精度浮点型

      SQL注入攻击

      注入问题

      假设有登录案例SQL语句如下:
      此时,当用户输入正确的账号与密码后,查询到信息则让用户登录。
      但是当用户输入的账号为XXX密码为'XXX’ OR ‘a’=’a时,则真正执行的代码变为:
      此时,上述查询语句时永远可以查询出结果的,那么用户就直接登录成功了,显然是不希望看到这样的结果,这就是SQL注入问题。

      案例演示

      SQL注入攻击用户登录案例

      PrepareStatement接口预编译SQL语句

      预处理对象

      使用PreparedStatement预处理对象时,建议每条sql语句所有的实际参数,都使用逗号分隔。
      PreparedStatement预处理对象代码:
      执行SQL语句的方法:
      • int executeUpdate():执行insert、update、delete语句
      • ResultSet executeQuery():执行select语句
      • boolean execute():执行select返回true,执行其他的语句返回false
      设置实际参数:
      • void setXxx(int index, Xxx xx):将指定参数设置为给定Java的xx值,在将此值发送到数据库时,驱动程序将它转换成一个 SQL Xxx类型值,例如setString(2, "家用电器"),会把SQL语句中第2个位置的占位符?替换成实际参数"家用电器"

      PrepareStatement接口预编译SQL语句执行修改

      PrepareStatement接口预编译SQL语句执行查询

      PreparedStatement的局限性

      为了防止SQL注入攻击,PreparedStatement不允许一个占位符(?)有多个值,在执行有IN子句查询的时候这个问题变得棘手起来。下面这个SQL查询使用PreparedStatement就不会返回任何结果:

      解决办法

      JDBC的工具类和测试

      数据表数据存储对象

      事务

      概述

      在数据库中,所谓事务是指一组逻辑操作单元,使数据从一种状态变换到另一种状态。
      为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。
      事务的操作:先定义开始一个事务,然后对数据作修改操作,这时如果提交(COMMIT),这些修改就永久地保存下来,如果回退(ROLLBACK),数据库管理系统将放弃您所作的所有修改而回到开始事务时的状态。

      事务的ACID属性

      原子性(Atomicity)

      原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

      一致性(Consistency)

      事务必须使数据库从一个一致性状态变换到另外一个一致性状态(数据不被破坏)。

      隔离性(Isolation)

      事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

      持久性(Durability)

      持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。

      JDBC事务处理

      在JDBC中,事务默认是自动提交的,每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
      为了让多个SQL语句作为一个事务执行,需调用Connection对象的setAutoCommit(false);以取消自动提交事务:
      在所有的SQL语句都成功执行后,调用commit();方法提交事务。
      在出现异常时,调用rollback();方法回滚事务,一般再catch模块中执行回滚操作。
      可以通过Connection的getAutoCommit()方法来获得当前事务的提交方式。
      注意:在MySQL中的数据库存储引擎InnoDB支持事务,MyISAM不支持事务。
      JDBC支持事务的方法:
      • java.sql.Connection.setAutoCommit(boolean)默认为true,所有的SQL执行完之后自动提交事务
      • java.sql.Connection.commit()手动提交事务
      • java.sql.Connection.rollback()回滚事务

      示例

      properties配置文件

      开发中获得连接的4个参数(驱动、URL、用户名、密码)通常都存在配置文件中,方便后期维护,程序如果需要更换数据库,只需要修改配置文件即可。
      通常情况下,习惯使用properties文件,对于此文件将做如下要求:
      • 文件位置:任意,建议src下
      • 文件名称:任意,扩展名为properties
      • 文件内容:一行一组数据,格式是“key=value”.
        • key命名自定义,如果是多个单词,习惯使用点分隔,例如:jdbc.driver
        • value值不支持中文,如果需要使用非英文字符,将进行unicode转换

      properties文件的创建和编写

      properties文件的创建:src路径下建立database.properties(其实就是一个文本文件)。
      properties文件的编写,内容如下:

      加载配置文件

      通过配置文件连接数据库

      读取配置文件的工具类

      测试工具类

      DBUtils

      概述

      DBUtils是Java编程中的数据库操作实用工具,小巧简单实用;它是JDBC的简化开发工具包,项目需要导入commons-dbutils.jar才能够正常使用DBUtils工具;其中封装了对JDBC的操作,简化了JDBC操作,可以少写代码。
      Dbutils三个核心功能介绍:
      • QueryRunner中提供对SQL语句操作的API:
        • update(Connection conn, String sql, Object... params):用来完成表数据的增加、删除、更新操作
        • query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params):用来完成表数据的查询操作
      • ResultSetHandler接口,用于定义select操作后,怎样封装结果集.
      • DbUtils就是一个工具类,定义了关闭资源与事务处理的方法

      QueryRunner类update方法

      update(Connection conn, String sql, Object... params)用来完成表数据的增加、删除、更新操作,Object...param是可变参数,Object类型,替换SQL语句中的?占位符。

      QueryRunner类实现insert添加数据

      QueryRunner类实现update修改数据

      QueryRunner类实现delete删除数据

      JavaBean类

      JavaBean就是一个类,在开发中常用来封装数据,具有如下特性:
      • 需要实现接口java.io.Serializable ,通常实现接口这步骤省略不会影响程序
      • 提供私有字段:private 类型 字段名;
      • 提供getter/setter方法
      • 提供无参构造

      DBUtils工具类结果集处理的方式

      QueryRunner实现查询操作:
      • query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params)用来完成表数据的查询操作
      ResultSetHandler结果集处理类:
      • ArrayHandler将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值
      • ArrayListHandler将结果集中的每一条记录都封装到一个Object[]数组中,将这些数组在封装到List集合中
      • BeanHandler将结果集中第一条记录封装到一个指定的JavaBean中
      • BeanListHandler将结果集中每一条记录封装到指定的JavaBean中,将这些JavaBean在封装到List集合中
      • ColumnListHandler将结果集中指定的列的字段值,封装到一个List集合中
      • ScalarHandler用于单数据,例如select count(*) from 表操作
      • MapHandler将结果集第一行封装到Map集合中,Key是列名, Value是该列数据
      • MapListHandler将结果集第一行封装到Map集合中,Key是列名, Value是该列数据,Map集合存储到List集合

      QueryRunner类query方法

      QueryRunner数据查询操作调用QueryRunner类方法query(Connection con,String sql,ResultSetHandler r, Object..params),ResultSetHandler r是结果集的处理方式,传递ResultSetHandler接口实现类,Object..params是SQL语句中的?占位符。
      注意:query方法返回值返回的是泛型,具体返回值类型随结果集处理方式变化。

      结果集处理ArrayHandler

      结果集处理ArrayListHandler

      结果集处理BeanHandler

      结果集处理BeanListHandler

      结果集处理ColumnListHandler

      结果集处理ScalarHandler

      结果集处理MapHandler

      结果集处理MapListHandler

      连接池

      概述

      连接池实际上就是存放连接的池子(容器)。
      在开发中“获得连接”或“释放资源”是非常消耗系统资源的两个过程,为了解决此类性能问题,通常会采用连接池技术,来共享连接Connection,这样就不需要每次都创建连接、释放连接了,这些操作都交给连接池。

      概念规范

      • 用池来管理Connection,这样可以重复使用Connection
      • 不用自己来创建Connection,而是通过池来获取Connection对象
      • 使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池
      • 连接池技术可以完成Connection对象的再次利用

      DataSource接口

      • Java为数据库连接池提供了公共的接口javax.sql.DataSource
      • 各个厂商需要让自己的连接池实现这个接口,这样应用程序可以方便的切换不同厂商的连接池
      • 常见的连接池:DBCP、C3P0

      DBCP连接池

      DBCP是一个开源的连接池,是Apache Common成员之一,在企业开发中也比较常见,是Tomcat内置的连接池。

      BasicDataSource类的使用

      BasicDataSource类的常见配置

      分类
      属性
      描述
      必须项
      driverClassName
      数据库驱动名称
      url
      数据库的地址
      username
      用户名
      password
      密码
      基本项(扩展)
      maxActive
      最大连接数量
      minIdle
      最小空闲连接
      maxIdle
      最大空闲连接
      initialSize
      初始化连接

      实现数据库连接池工具类

      测试工具类

      网络通信协议

      网络模型

      TCP/IP协议中的四层分别是应用层、传输层、网络层和链路层,每层分别负责不同的通信功能,接下来针对这四层进行详细地讲解。
      • 链路层:链路层是用于定义物理传输通道,通常是对某些网络连接设备的驱动协议,例如针对光纤、网线提供的驱动
      • 网络层:网络层是整个TCP/IP协议的核心,它主要用于将传输的数据进行分组,将分组数据发送到目标计算机或者网络
      • 传输层:主要使网络程序进行通信,在进行网络通信时,可以采用TCP协议,也可以采用UDP协议
      • 应用层:主要负责应用程序的协议,例如HTTP协议、FTP协议等

      IP地址

      在TCP/IP协议中,这个标识号就是IP地址,它可以唯一标识一台计算机。
      目前,IP地址广泛使用的版本是IPv4,它是由4个字节大小的二进制数来表示,如:00001010000000000000000000000001;由于二进制形式表示的IP地址非常不便记忆和处理,因此通常会将IP地址写成十进制的形式,每个字节用一个十进制数字(0-255)表示,数字间用符号“.”分开,如 “192.168.1.100”
      127.0.0.1 为本地主机地址(本地回环地址)。

      端口号

      通过IP地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号;在计算机中,不同的应用程序是通过端口号区分的;端口号是用两个字节(16位的二进制数)表示的,它的取值范围是0-65535,其中0-1023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。

      InetAddress类

      该类用于封装一个IP地址,并提供了一系列与IP地址相关的方法。

      UDP与TCP协议

      UDP协议

      UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。
      简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
      UDP协议特点:
      由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议都使用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

      TCP协议

      TCP协议是面向连接的通信协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。
      在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”:
      • 第一次握手,客户端向服务器端发出连接请求,等待服务器确认
      • 第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求
      • 第三次握手,客户端再次向服务器端发送确认信息,确认连接

      UDP通信

      数据包和发送对象

      DatagramPacket数据包的作用就如同是“集装箱”,可以将发送端或者接收端的数据封装起来,然而运输货物只有“集装箱”是不够的,还需要有码头;在程序中需要实现通信只有DatagramPacket数据包也同样不行,为此JDK中提供的一个DatagramSocket类;DatagramSocket类的作用就类似于码头,使用这个类的实例对象就可以发送和接收DatagramPacket数据包。
      在创建发送端和接收端的DatagramPacket对象时,使用的构造方法有所不同,接收端的构造方法只需要接收一个字节数组来存放接收到的数据,而发送端的构造方法不但要接收存放了发送数据的字节数组,还需要指定发送端IP地址和端口号。
      • DatagramPacket:封装数据
        • 构造方法
          • 在创建DatagramPacket对象时,指定了封装数据的字节数组和数据的大小,没有指定IP地址和端口号;很明显,这样的对象只能用于接收端,不能用于发送端,因为发送端一定要明确指出数据的目的地(ip地址和端口号),而接收端不需要明确知道数据的来源,只需要接收到数据即可
          • 在创建DatagramPacket对象时,不仅指定了封装数据的字节数组和数据的大小,还指定了数据包的目标IP地址(addr)和端口号(port);该对象通常用于发送端,因为在发送数据时必须指定接收端的IP地址和端口号,就好像发送货物的集装箱上面必须标明接收人的地址一样
      • DatagramSocket:发送DatagramPacket
        • 构造方法
          • 创建发送端的DatagramSocket对象,在创建DatagramSocket对象时,并没有指定端口号,此时,系统会分配一个没有被其它网络程序所使用的端口号
          • 用于创建接收端的DatagramSocket对象,又可以创建发送端的DatagramSocket对象,在创建接收端的DatagramSocket对象时,必须要指定一个端口号,这样就可以监听指定的端口

      UDP发送端

      UDP接收端

      UDP接收端的拆包

      键盘输入聊天案例

      TCP通信

      TCP的客户端和服务器

      TCP通信同UDP通信一样,都能实现两台计算机之间的通信,通信的两端都需要创建socket对象;区别在于,UDP中只有发送端和接收端,不区分客户端与服务器端,计算机之间可以任意地发送数据,而TCP通信是严格区分客户端与服务器端的,在通信时,必须先由客户端去连接服务器端才能实现通信,服务器端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户端的连接。
      在JDK中提供了两个类用于实现TCP程序,一个是ServerSocket类,用于表示服务器端,一个是Socket类,用于表示客户端;通信时,首先创建代表服务器端的ServerSocket对象,该对象相当于开启一个服务,并等待客户端的连接,然后创建代表客户端的Socket对象向服务器端发出连接请求,服务器端响应请求,两者建立连接开始通信。
      • ServerSocket构造方法:在创建ServerSocket对象时,就可以将其绑定到一个指定的端口号上(参数port就是端口号)
      • Socket构造方法:在创建Socket对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数host接收的是一个字符串类型的IP地址

      TCP的客户端程序

      TCP的服务器程序accept方法

      TCP的服务器程序读取客户端数据

      TCP的服务器和客户端的数据交换

      TCP上传文件

      TCP上传客户端

      TCP上传服务器

      TCP图片上传问题解决

      TCP上传文件名

      多线程上传案例

       
      上一篇
      JavaSE——多线程
      下一篇
      JavaEE——XML与Properties

      评论
      Loading...
      目录