封候非我意,但愿海波平---------戚继光
1 JDBC简介
JDBC(Java DataBase Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序,java具有坚固、安全、易于使用、易于理解和可从网络上自动下载等特性,是编写数据库应用程序的杰出语言。所需要的只是 Java应用程序与各种不同数据库之间进行对话的方法。
JDBC可以在各种平台上使用Java,如Windows,Mac OS和各种版本的UNIX。
JDBC库包括通常与数据库使用相关的下面提到的每个任务的API。
- 连接数据库。
- 创建SQL或MySQL语句。
- 在数据库中执行SQL或MySQL查询。
- 查看和修改生成的记录。
2 JDBC体系结构
JDBC API支持用于数据库访问的两层和三层处理模型,但通常,JDBC体系结构由两层组成:
-
JDBC API:这提供了应用程序到JDBC管理器连接。
-
JDBC驱动程序API:这支持JDBC管理器到驱动程序连接。
JDBC API使用驱动程序管理器和特定于数据库的驱动程序来提供与异构数据库的透明连接。
3 JDBC核心组件
DriverManager: 此类管理数据库驱动程序列表。使用通信子协议将来自java应用程序的连接请求与适当的数据库驱动程序匹配。
Driver: 此接口处理与数据库服务器的通信,我们很少会直接与Driver对象进行交互。而是使用DriverManager对象来管理这种类型的对象。
Connection: 该界面具有用于联系数据库的所有方法。连接对象表示通信上下文,即与数据库的所有通信仅通过连接对象通信。
Statement: 使用从此接口创建的对象将SQL语句提交到数据库。除了执行存储过程之外,一些派生接口还接受参数。
ResultSet: 在使用Statement对象执行SQL查询后,这些对象保存从数据库检索的数据。它作为一个迭代器,允许我们移动其数据。
SQLException: 此类处理数据库应用程序中发生的任何错误。
4 CRUD语法介绍
SQL 是一种标准化的语言(结构化查询语言(Structured Query Language)),其允许你在数据库上执行操作,如创建项目,查询内容,更新内容,删除条目等操作。
Create(增加/创建), Retrieve(查询/检索), Update(修改/更新), and Delete(删除) 通常称为CRUD操作(传说中的增查改删)。
5使用步骤
构建JDBC应用程序涉及以下六个步骤:
-
导入包: 需要包含数据库编程所需的JDBC类的包。大多数情况下,使用import java.sql.* 就足够了。
-
注册JDBC驱动程序: 要求使用者初始化驱动程序,以便可以打开与数据库的通信通道。
-
打开连接: 需要使用DriverManager.getConnection() 方法创建一个Connection对象,该对象表示与数据库的物理连接。
-
执行查询: 需要使用类型为Statement(或PreparedStatement)的对象来构建和提交SQL语句到数据库。
-
从结果集中提取数据: 需要使用相应的ResultSet.getXXX() 方法从结果集中检索数据。
-
释放资源: 需要明确地关闭所有数据库资源,而不依赖于JVM的垃圾收集。
6 JDBC连接步骤
建立JDBC连接所涉及的编程相当简单。这是简单的四个步骤
-
导入JDBC包: 将Java语言的 import 语句添加到Java代码中导入所需的类。
-
注册JDBC驱动程序: 此步骤将使JVM将所需的驱动程序实现加载到内存中,以便它可以满足使用者的JDBC请求。
-
数据库URL配置: 这是为了创建一个格式正确的地址,指向要连接到的数据库。
-
创建连接对象: 最后,调用DriverManager对象的getConnection() 方法来建立实际的数据库连接。
注册驱动程序最常见的方法是使用Java的Class.forName() 方法,将驱动程序的类文件动态加载到内存中,并将其自动注册。
6.1 JDBC执行SQL语句
一旦获得了连接,我们可以与数据库进行交互。JDBC Statement和PreparedStatement接口定义了使使用者能够发送SQL命令并从数据库接收数据的方法和属性。
接口 | 推荐使用 |
---|---|
Statement | 用于对数据库进行通用访问,在运行时使用静态SQL语句时很有用,Statement接口不能接受参数。 |
PreparedStatement | 当计划多次使用SQL语句时使用,PreparedStatement接口在运行时接受输入参数。 |
6.2 Statement(状态通道)
创建语句对象
在使用Statement对象执行SQL语句之前,需要使用Connection对象的createStatement()方法创建一个对象(Statement是个接口),如下例所示:
Statement statement = null;
try {
statement = connection.createStatement( );
. . .
}catch (SQLException e) {
. . .
}finally {
. . .
}
创建Statement对象后,可以使用其来执行一个SQL语句,其有三个执行方法。
-
boolean execute(String SQL):如果可以检索到ResultSet对象,则返回一个布尔值true; 否则返回false。使用此方法执行SQL DDL语句或需要使用真正的动态SQL时。
-
int executeUpdate(String SQL):返回受SQL语句执行影响的行数。使用此方法执行预期会影响多个行的SQL语句,例如INSERT,UPDATE或DELETE语句。
-
ResultSet executeQuery(String SQL):返回一个ResultSet对象。当希望获得结果集时,请使用此方法,就像使用SELECT语句一样。
关闭Statement对象
就像我们关闭一个Connection对象以保存数据库资源一样,由于同样的原因,还应该关闭Statement对象。
调用close()方法即可,如果先关闭Connection对象,其也会关闭Statement对象。但是,应始终显式关闭Statement对象,以确保正确清理。
Statement statement = null;
try {
statement = connection.createStatement( );
. . .
}catch (SQLException e) {
. . .
}finally {
statement.close();
}
6.3 SQL注入
就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。 具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。比如先前的很多影视网站泄露VIP会员密码大多就是通过WEB表单递交查询字符暴出的,这类表单特别容易受到SQL注入式攻击。
String username ="root";
String password=" 'root' or 1=1 "; //因为1=1恒成立换成2=2也行
String sql="select * from users where username= '"+username+"' and password= "+password;
6.4 PreparedStatement(预状态通道)
//接口的定义
public interface Statement extends Wrapper, AutoCloseable{}
public interface PreparedStatement extends Statement{}
该PreparedStatement的接口扩展了Statement接口,它为使用者提供了一个通用的Statement对象。
动态地提供参数。
PreparedStatement preparedStatement = null;
try {
String SQL = "Update Employees SET age = ? WHERE id = ?";
preparedStatement = connection.prepareStatement(SQL);
. . .
}catch (SQLException e) {
. . .
}finally {
. . .
}
JDBC中的所有参数都由?符号(英文问号)代替(不加双引号的,字符串类型也不加),这被称为参数标记。在执行SQL语句之前,必须为每个参数提供值。
所述的setXXX()方法将值绑定到所述参数,其中XXX代表要绑定到输入参数的值的Java数据类型。如果忘记提供值,将收到一个SQLException。
每个参数标记由其顺序位置引用。第一个标记表示位置1,下一个位置2等等。该方法与Java数组索引从0开始不同。
关闭PreparedStatement对象
就像关闭Statement对象一样,由于同样的原因,还应该关闭PreparedStatement对象。
一个简单的调用close()方法将执行该作业。如果先关闭Connection对象,其也会关闭PreparedStatement对象。但是,应始终显式关闭PreparedStatement对象,以确保正确清理。
对比statement和PreparedStatement
(1)statement属于状态通道,PreparedStatement属于预状态通道
(2)预状态通道会先编译sql语句,再去执行,比statement执行效率高
(3)预状态通道支持占位符?,给占位符赋值的时候,位置从1(就是从1开始)开始
(4)预状态通道可以防止sql注入,原因:预状态通道在处理值的时候以字符串的方式处理
7. 连接池(只写Druid)
1.自定义连接池
数据连接池原理
连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
最小连接数: minldle(0)
是数据库一直保持的数据库连接数,所以如果应用程序对数据库连接的使用量不大,将有大量的数据库资源被浪费。
初始化连接数:initialSize(0)
连接池启动时创建的初始化数据库连接数量。
最大连接数: maxActive(8) 缺省为8个
是连接池能申请的最大连接数,如果数据库连接请求超过此数,后面的数据库连接请求被加入到等待队列中。
最大等待时间:maxWait(毫秒)
当没有可用连接时,连接池等待连接被归还的最大时间,超过时间则抛出异常,可设置参数为0或者负数使得无限等待(根据不同连接池配置)。
数据库连接池在初始化的时候会创建initialSize个连接,当有数据库操作时,会从池中取出一个连接。如果当前池中正在使用的连接数等于maxActive,则会等待一段时间,等待其他操作释放掉某一个连接,如果这个等待时间超过了maxWait,则会报错;如果当前正在使用的连接数没有达到maxActive,则判断当前是否空闲连接,如果有则直接使用空闲连接,如果没有则新建立一个连接。在连接使用完毕后,不是将其物理连接关闭,而是将其放入池中等待其他操作复用。
8. Druid(德鲁伊)连接池
阿里出品,淘宝和支付宝专用数据库连接池,但它不仅仅是一个数据库连接池,它还包含一个ProxyDriver(代理驱动),一系列内置的JDBC组件库,一个SQL Parser(sql解析器)。支持所有JDBC兼容的数据库,包括Oracle、MySql、Derby、Postgresql、SQL Server、H2等等。
Druid针对Oracle和MySql做了特别优化,比如Oracle的PS Cache内存占用优化,MySql的ping检测优化。
Druid提供了MySql、Oracle、Postgresql、SQL-92的SQL的完整支持,这是一个手写的高性能SQL Parser,支持Visitor模式,使得分析SQL的抽象语法树很方便。
简单SQL语句用时10微秒以内,复杂SQL用时30微秒。
通过Druid提供的SQL Parser可以在JDBC层拦截SQL做相应处理,比如说分库分表、审计等。Druid防御SQL注入攻击的WallFilter就是通过Druid的SQL Parser分析语义实现的。
Druid 是目前比较流行的高性能的,分布式列存储的OLAP框架(具体来说是MOLAP)。它有如下几个特点:
一. 亚秒级查询
druid提供了快速的聚合能力以及亚秒级的OLAP查询能力,多租户的设计,是面向用户分析应用的理想方式。
二.实时数据注入
druid支持流数据的注入,并提供了数据的事件驱动,保证在实时和离线环境下事件的实效性和统一性。
三.可扩展的PB级存储
druid集群可以很方便的扩容到PB的数据量,每秒百万级别的数据注入。即便在加大数据规模的情况下,也能保证时其效性。
四.多环境部署
druid既可以运行在商业的硬件上,也可以运行在云上。它可以从多种数据系统中注入数据,包hadoop,spark,kafka,storm和samza等。
五.丰富的社区
druid拥有丰富的社区,供大家学习。
Druid工具类
/*** 阿里的数据库连接池
* 性能好
* Druid
* */
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.ResourceBundle;
public class DBUtils {
//1.定义变量
private Connection connection;
private PreparedStatement pps;
//protected 子类也能访问,后面方便使用
protected ResultSet resultSet;
private int count;//存储受影响的行数
private static String userName;
private static String userPass;
private static String url;
private static String driverName;
//druid 德鲁伊
private static DruidDataSource druidDataSource = new DruidDataSource();
static {
//db是配置文件名
/*此注释为配置文件内容
driverClass=com.mysql.cj.jdbc.Driver//mysql8版本多了.cj这一级
userName=root//自己的用户名
userPass=123456//自己的密码
//localhost也可改成127.0.0.1,默认端口为3306
url=jdbc:mysql://localhost:3306/要使用的数据库名?serverTimezone=UTC*/
ResourceBundle bundle = ResourceBundle.getBundle("db");
driverName = bundle.getString("driverClass");
url = bundle.getString("url");
userName = bundle.getString("userName");
userPass = bundle.getString("userPass");
druidDataSource.setUsername(userName);
druidDataSource.setPassword(userPass);
druidDataSource.setUrl(url);
druidDataSource.setDriverClassName(driverName);
//druidDataSource.setInitialSize(5);
}
//3.获得连接
protected Connection getConnection() {
try {
connection = druidDataSource.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
//4.得到预状态通道
protected PreparedStatement getPps(String sql) {
try {
pps = getConnection().prepareStatement(sql);
} catch (SQLException e) {
e.printStackTrace();
}
return pps;
}
//5.绑定参数 List 保存的是给占位符所赋的值
protected void param(List list) {
if (list != null && list.size() > 0) {
for (int i = 0; i < list.size(); i++) {
try {
//从1开始
pps.setObject(i+1, list.get(i));
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
//6.执行操作(增删改+查询)
protected int update(String sql, List list) {
getPps(sql);
param(list);
try {
//返回的是受影响的行数
count = pps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
return count;
}
//7. 查询
protected ResultSet query(String sql, List list) {
getPps(sql);
param(list);
try {
resultSet = pps.executeQuery();
} catch (SQLException e) {
e.printStackTrace();
}
return resultSet;
}
//8.关闭资源
protected void closeAll() {
try {
if (connection != null) {
connection.close();
}
if (pps != null) {
pps.close();
}
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
注:在Druid连接池的配置中,driverClassName可配可不配,如果不配置会根据url自动识别dbType(数据库类型),然后选择相应的driverClassName。