一、JDBC
1. JDBC概述
1)什么是JDBC
- JDBC(java Data Base Connectivity),java数据库连接。是一种用于执行SQL语句的java API,可以为多种关系数据库提供统一访问。它由一组用java语言编写的类和接口组成。
- JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够填写数据库应用程序,同时JDBC也是个商品名。
2)什么是数据库驱动
- 驱动:两个设备(应用)之间通信的桥梁
3)为什么要有JDBC
- 假如没有JDBC,在开发一套系统时,使用java连接mysql数据库,程序员就要了解mysql驱动API,如果要使用Oracle数据库,那么程序员就要了解Oracle驱动API。
- SUN公司提供了一套统一的规范(接口),然后各个数据库生产商提供这套接口的实现,这套规范(接口)就是JDBC的规范。
2. JDBC的使用
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//获取链接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/dbname","root", "password");
//基本操作
//获取执行SQL语句的对象
stmt = conn.createStatement();
//编写SQL语句
String sql = "select * from user";
//执行SQL
rs = statement.executeQuery(sql);
//遍历结果集
while(rs.next()){
System.out.println(rs.getInt("id"));
System.out.println(rs.getString("username"));
System.out.println(rs.getString("password"));
}
} catch(Exception e){
e.printStackTrace();
} finally{
//it is a good idea to release
//resources in a finally() block
//in reverse-order of their creation
//if they are no-longer needed
if(rs != null){
try{
rs.close();
}catch(SQLException sqlEx){
//ignore
}
rs = null;
}
if(stmt != null){
try{
stmt.close();
}catch(SQLException sqlEx){
//ignore
sqlEx.printStackTrace();
}
stmt = null;
}
if(conn != null){
try{
conn.close();
}catch(SQLException sqlEx){
//ignore
sqlEx.printStackTrace();
}
conn = null;
}
}
3. JDBC的API
1)DriverManager:驱动管理类
作用一:注册驱动
修饰符 | 方法 | 描述 |
---|---|---|
static void | registerDriver(Driver driver, DriverAction da) | 向DriverManager注册给定驱动程序。 |
作用二:获得连接
修饰符 | 方法 | 描述 |
---|---|---|
static Connection | getConnection(String url, String user, String password) | 尝试建立与给定数据库URL的连接。 |
-
url写法:
jdbc:mysql://[ip_address]:3306/[db_name]?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
其中:
- jdbc:连接数据库的协议
- mysql:jdbc的子协议
- useUnicode:是否使用Unicode字符集,如果参数characterEncoding为GB2312或GBK,则该参数必须为true
- characterEncoding:当useUnicode为true时,指定字符编码。
- serverTimserezone:设置时区
- autoReconnect:当数据库异常中断时,是否自动重连
- autoReconnectForPools:是否使用针对数据库连接池的重连策略
- failOverReadOnly:自动重连后,连接是否设置为只读
- maxReconnects:自动重连的次数
- initialTimeout:两次重连的时间间隔,单位:秒
- connectTimeout:和数据库服务器建立socket连接时的超时,单位:毫秒,0表示永不超时
- socketTimeout:socket操作(读写)超时,单位:毫秒,0表示永不超时
- rewriteBatchedStatements:是否开启批处理
2)Connection:与数据库连接对象
作用一:创建执行SQL语句的对象
修饰符 | 方法 | 描述 |
---|---|---|
Statement | createStatement() | 创建一个Statement对象,用于将SQL语句发送到数据库。 |
CallableStatement | prepareCall(String sql | 创建一个调用数据库存储过程的CallableStatement对象。 |
PreparedStatement | prepareStatement(String sql) | 创建一个PreparedStatement对象,用于将参数化的SQL语句发送到数据库。 |
执行SQL语句的对象:
- Statement :执行SQL
- CallableStatement :执行数据库中的存储过程
- PreparedStatement :执行SQL对SQL进行预处理,解决SQL注入漏洞
作用二:管理事务
修饰符 | 方法 | 描述 |
---|---|---|
boolean | getAutoCommit() | 检索此Connection对象的当前自动提交模式。 |
void | setAutoCommit(boolean autoCommit) | 将此连接的自动提交模式设置为给定状态。 |
void | commit() | Connection发布此Connection对象的数据库和JDBC资源,而不是等待他们自动释放。 |
void | rollback() | 撤销在当前事务中所作的更改,并释放此Connection对象当前持有的任何数据库锁。 |
3)Statement:执行SQL
作用一:执行SQL
修饰符 | 方法 | 描述 |
---|---|---|
boolean | execute(String sql) | 执行给定的SQL语句,这可能会返回多个结果。 |
ResultSet | executeQuery(String sql) | 执行给定的SQL语句,该语句返回单个ResultSet对象。 |
int | executeUpdate(String sql) | 执行给定的SQL语句。这可能是INSERT、UODATE或DELETE语句,或者不返回任何内容,如SQL DDL语句的SQL语句。 |
执行SQL的方法:
-
boolean execute(String sql)
执行查询、修改、添加、删除的SQL语句,如果为查询,返回true,否则返回false
-
ResultSetexecuteQuery(String sql)
执行查询(select)
-
int executeUpdate(String sql)
执行修改、添加、删除的SQL语句,返回影响行数
作用二:执行批处理
修饰符 | 方法 | 描述 |
---|---|---|
void | addBatch(String sql) | 将给定的SQL命令添加到此Statement对象的当前命令列表中。 |
void | clearBatch() | 清除此Statement对象的dangqianSQL命令列表。 |
init[] | executeBatch() | 将一批命令提交到数据库以执行,并且所有命令都执行成功,返回一个更新计数的数组。 |
4)ResultSet:结果集
通过select语句查询的结果。
结果集的遍历
修饰符 | 方法 | 描述 |
---|---|---|
boolean | next() | 将光标从当前位置向前移动一行。next的光标默认在第一行的前一行,当第一次使用时指向数据的第一行。 |
结果集的获取
修饰符 | 方法 | 描述 |
---|---|---|
int | getInt(String columnLabel) | 以java语言中的int此ResultSet对象的当前行中指定列的值。 |
String | getString(String columnLabel) | 这个检索的当前行中指定列的值 ResultSet对象为 String的Java编程语言。 |
Object | getObject(String columnLabel) | 获取此的当前行中指定列的值ResultSet作为对象Object在java语言。 |
- 这里的方法就是将输出的值转化为原本的数据类型
- 这样的方法通常都有重载,其中的参数可以是列名或列号
- 其中Object可以获取任意类型
4. JDBC的资源释放
JDBC程序执行结束后,将与数据库进行交互的对象释放掉,通常是ResultSet, Statement, Connection。
这几个对象中尤其是Connection对象是非常稀有的。这个对象一定要做到尽量 **晚创建,早释放。**
-
资源释放的代码通常写入到finnally的代码块中。
-
标准的资源释放代码
//it is a good idea to release //resources in a finally() block //in reverse-order of their creation //if they are no-longer needed if(rs != null){ try{ rs.close(); }catch(SQLException sqlEx){ //ignore } rs = null; } if(stmt != null){ try{ stmt.close(); }catch(SQLException sqlEx){ //ignore sqlEx.printStackTrace(); } stmt = null; } if(conn != null){ try{ conn.close(); }catch(SQLException sqlEx){ //ignore sqlEx.printStackTrace(); } conn = null; }
5. JDBC工具类的抽取
1)传统的JDBC的开发
流程:
- 注册驱动(重复)
- 获得连接(重复)
- 基本操作
- 释放资源(重复)
由于四个步骤中有三个步骤都是重复的,因此可以将其封装后使用。
2)重复步骤的提取
package com.yancey;
import java.sql.*;
/**
* @author YanceyGao
* @date 2020/11/13 21:18
* @ClassName JDBCUtils
* @Description JDBC工具类
* @program JDBCUtils.java
*/
public class JDBCUtils {
private static final String DRIVER_CLASS;
private static final String CODING_SET;
private static String url = null;
private static String ip = null;
private static String dataBase = null;
private static String userName = null;
private static String password = null;
static {
DRIVER_CLASS = "com.mysql.jdbc.Driver";
url = "jdbc:mysql://";
CODING_SET = "?useUnicode=true&characterEncoding=UTF-8&serverTimserezone=UTC";
}
/**
* @author YanceyGao
* @date 2020/11/13 21:37
* @methodName setUsername
* @Description 设置连接的用户名
* @param {userName}
* @return void
*/
public static void setUsername(String userName){
JDBCUtils.userName = userName;
}
/**
* @author YanceyGao
* @date 2020/11/13 21:39
* @methodName setPassword
* @Description 设置密码
* @param {password}
* @return void
*/
public static void setPassword(String password){
JDBCUtils.password = password;
}
/**
* @author YanceyGao
* @date 2020/11/13 21:38
* @methodName setIp
* @Description 设置连接的ip地址
* @param {ip}
* @return void
*/
public static void setIp(String ip){
JDBCUtils.ip = ip;
}
/**
* @author YanceyGao
* @date 2020/11/13 21:38
* @methodName setdataBase
* @Description 设置连接的数据库
* @param {db}
* @return void
*/
public static void setdataBase(String db){
JDBCUtils.dataBase = db;
}
/**
* @author YanceyGao
* @date 2020/11/13 21:45
* @methodName initConnectionData
* @Description 初始化连接数据
* @param {}
* @return void
*/
private static void initConnectionData(){
JDBCUtils.url = JDBCUtils.url + JDBCUtils.ip + "/" + JDBCUtils.dataBase + JDBCUtils.CODING_SET;
}
/**
* @author YanceyGao
* @date 2020/11/13 21:20
* @methodName loadDriver
* @Description 驱动加载方法
* @param {}
* @return void
*/
private static void loadDriver(){
try {
Class.forName(DRIVER_CLASS);
initConnectionData();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* @author YanceyGao
* @date 2020/11/13 21:25
* @methodName getConnection
* @Description 获取连接
* @param {}
* @return java.sql.Connection
*/
public static Connection getConnection() throws SQLException {
Connection conn = null;
try{
loadDriver();
conn = DriverManager.getConnection(JDBCUtils.url, JDBCUtils.userName, JDBCUtils.password);
} catch (Exception e){
e.printStackTrace();
}
return conn;
}
/**
* @author YanceyGao
* @date 2020/11/13 21:27
* @methodName release
* @Description 释放资源
* @param {stmt}, {conn}
* @return void
*/
public static void release(Statement stmt, Connection conn){
//it is a good idea to release
//resources in a finally() block
//in reverse-order of their creation
//if they are no-longer needed
if(stmt != null){
try{
stmt.close();
}catch(SQLException sqlEx){
//ignore
sqlEx.printStackTrace();
}
stmt = null;
}
if(conn != null){
try{
conn.close();
}catch(SQLException sqlEx){
//ignore
sqlEx.printStackTrace();
}
conn = null;
}
}
/**
* @author YanceyGao
* @date 2020/11/13 21:27
* @methodName release
* @Description 释放资源
* @param {rs}, {stmt}, {conn}
* @return void
*/
public static void release(ResultSet rs, Statement stmt, Connection conn){
//it is a good idea to release
//resources in a finally() block
//in reverse-order of their creation
//if they are no-longer needed
if(rs != null){
try{
rs.close();
}catch(SQLException sqlEx){
//ignore
}
rs = null;
}
if(stmt != null){
try{
stmt.close();
}catch(SQLException sqlEx){
//ignore
sqlEx.printStackTrace();
}
stmt = null;
}
if(conn != null){
try{
conn.close();
}catch(SQLException sqlEx){
//ignore
sqlEx.printStackTrace();
}
conn = null;
}
}
}
3)配置信息的提取
配置文件有以下几种:
- 属性文件(建议使用方式)
- 后缀:.properties
- 内容:key=value
- XML文件
属性的配置:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://[ip_address]:3306/[db_name]?useUnicode=true&characterEncoding=UTF-8&serverTimserezone=UTC
userName=root
password=1021
配置文件的解析:
//获取属性文件中的内容
Properties properties = new Properties();
properties.load(new FileInputStream("src/main/java/com.yancey/db.properties"));
driverClassName = properties.getProperty("driverClassName");
url = properties.getProperty("url");
userName = properties.getProperty("userName");
password = properties.getProperty("password");
6. JDBC的SQL注入漏洞
1)什么是SQL注入漏洞
在早期的互联网上SQL注入漏洞普遍存在,通过输入特定的用户名或者密码格式实现更改数据库的查询操作来进行非常规登录。具体操作有以下几种方法:
-- 方法一,修改用户名的输入,原为[username]
-- 实现原理,通过修改校验的查询操作,是出现或等的情况致使返回结果为true
[username]' or '1=1
-- 方法二,修改用户名的输入,原为[username]
-- 实现原理,通过将查询密码注释的方式使得返回结果为true
[username]' --
-- 方法三,修改密码的输入,原为[password]
(select password from user where username='[username]');
2)如何防止SQL注入问题
采用preparedStatement
对象,这个对象将sql预先进行编译,使用?
作为占位符。?
所代表的内容是SQL所固定的,其输入的任何变量都将最为参数使用,而不会解析称为关键字。
Connection conn = JDBCUtils.getConncetion();
PreparedStatement pstmt = null;
String sql = "select * from user where username = ? and password = ?;";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username); //第一个参数表示sql中参数的位置。第二个表示传入值
pstmt.setString(2, password);
ResultSet rs = pstmt.excuteQuery();
7. JDBC的批处理操作
1)基本批处理操作
//创建连接
Connection con = JDBCUtils.getConnection();
//创建批处理对象
Statement stmt = conn.createStatement();
//sql语句
String sql1 = "create database test;";
String sql2 = "use test;";
String sql3 = "create table user(id int primary key auto_increment, name varchar(20));";
String sql4 = "insert into user values(null, 'liMing')";
String sql5 = "insert into user values(null, 'liHua')";
String sql6 = "insert into user values(null, 'liGang')";
//添加到批处理
stmt.addBatch(sql1);
stmt.addBatch(sql2);
stmt.addBatch(sql3);
stmt.addBatch(sql4);
stmt.addBatch(sql5);
stmt.addBatch(sql6);
//执行批处理
stmt.executeBatch(); //别忘了释放资源
2)批量插入(使用PreparedStatement)
//默认情况下mysql批处理是没有开启的,需要在url后面拼接一个参数: rewriteBatchedStatements=true
//创建连接
Connection con = JDBCUtils.getConnection();
//创建批处理对象
PreparedStatement pstmt = null;
//sql语句
String sql = "insert into user values(null, ?);";
//预编译
pstmt = con.prepareStatement(sql);
for(int i = 1; i <= 1000; ++i){
pstmt.setString(1, "name" + i);
//添加到批处理
pstmt.addBatch();
//执行批处理
if(i % 1000 == 0){
//执行
pstmt.executeBatch();
//清空
pstmt.clearBatch();
}
}
//别忘了释放资源
Q.E.D.