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 → 释放资源。
注册驱动 (只做一次)
- Class.forName(“com.MySQL.jdbc.Driver”);,推荐这种方式,不会对具体的驱动类产生依赖
- DriverManager.registerDriver(com.mysql.jdbc.Driver);,会造成DriverManager中产生两个一样的驱动,并会对具体的驱动类产生依赖
建立连接
URL用于标识数据库的位置,通过URL地址告诉JDBC程序连接哪个数据库。
其他参数如:useUnicode=true&characterEncoding=utf8。
创建执行SQL语句的statement
处理执行结果(ResultSet)
释放资源
案例分析
准备数据
- 创建数据库和表结构
- 向表中插入数据
JDBC的开发步骤
- 注册驱动:告知JVM使用的是哪一个数据库的驱动
- 获得连接:使用JDBC中的类,完成对MySQL数据库的连接
- 获得语句执行平台:通过连接对象获取对SQL语句的执行者对象
- 执行SQL语句:使用执行者对象,向数据库执行SQL语句,获取到数据库的执行后的结果
- 处理结果
- 释放资源:一堆close()方法
注册数据库驱动程序
JDBC规范定义驱动接口:java.sql.Driver,MySql驱动包提供了实现类:com.mysql.jdbc.Driver,DriverManager工具类,提供注册驱动的方法 registerDriver(),方法的参数是java.sql.Driver,所以可以通过如下语句进行注册:
以上代码不推荐使用,存在两方面不足:
- 硬编码,后期不易于程序扩展和维护
- 驱动被注册两次
通常开发时使用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上传文件名
多线程上传案例
- 作者:青山🌊
- 链接:http://cxuan.me/article/java-se-jdbc-net
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。