-
JDBC
-
JDBC
-
JDBC API
-
JDBC
JDBC
入门
1.1 概述
-
概念
JDBC:Java DataBase Connectivity
,Java
数据库连接JDBC
就是使用Java
语言操作关系型数据库的一套API
。Sun
公司和数据库厂商共同制定的一套连接并操作数据库的统一规范(接口),由数据库厂商负责实现(驱动Jar
包);我们使用的时候只需要导入数据库厂商已经实现好的jar
包即可。 -
好处
因为各个数据库厂商都实现了同一套
JDBC
接口规范。- 我们可以使用同一套
Java
代码操作不同的关系型数据库 - 访问数据库的
Java
代码不改变的前提下,替换不同的驱动Jar
包可以轻松更换数据库
- 我们可以使用同一套
-
我们要怎么做
整体思路:基于
JDBC
定义的规则编码,不用考虑不同数据库之间的差异- 导数对应据库驱动包。
- 基于
JDBC
定义的规则轻松编程。
-
实现步骤
- 导入
Jar
包 - 注册驱动
- 获取连接
- 获取执行者对象
- 执行
SQL
语句,并且接收结果 - 处理结果
- 释放资源
- 导入
-
演示代码
package com.cy.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.Statement; /** * JDBC快速入门 */ public class JDBCDemo { public static void main(String[] args) throws Exception { //1. 注册驱动 建议写上去 Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接 //url jdbc:mysql:///maven == jdbc:mysql://127.0.0.1:3306/maven //jdbc:mysql://ip:数据库端口号/数据库名称?添加自定义参数 String url = "jdbc:mysql://127.0.0.1:3306/maven"; String username = "root"; String password = "root"; //Connection 数据库的连接对象 Connection conn = DriverManager.getConnection(url, username, password); //3. 定义sql String sql = "update account set money = ? where id = ?"; //4. 获取执行sql的对象 Statement //Statement stmt = conn.createStatement(); PreparedStatement pps = conn.prepareStatement(sql); pps.setDouble(1,3000); pps.setInt(2,1); //5. 执行sql //int count = stmt.executeUpdate(sql);//受影响的行数 执行增删改 int count = pps.executeUpdate(); //6. 处理结果 受影响的行数 System.out.println(count); //7. 释放资源 pps.close(); conn.close(); } }
jdbc
常用API
2.1 DriverManager
功能1:注册驱动
- 不需要手动调用
DriverManager
的API
注册,而是保证com.mysql.jdbc.Driver
被加载进内存即可。
查看com.mysql.jdbc.Driver
类的源码
package com.mysql.jdbc;
import java.sql.DriverManager;
import java.sql.SQLException;
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
// 在静态代码块中完成了注册驱动的代码
// 我们只要保证Driver被加载进内存,下面的注册驱动的步骤就会自动完成。
/*
Class.forName("com.mysql.jdbc.Driver");
Driver.class //Driver的字节码对象
new Driver(); // 多一个driver对象
*/
/*
在MySQL5以上的版本,注册驱动不需要我们自己做了。
在mysql-connector-java-5.1.47.jar中META-INF下面services下面的
java.sql.Driver文件中,填写了com.mysql.jdbc.Driver全类名
该类的便会自动被加载进内存
*/
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
功能2:获取连接对象
static Connection getConnection(url, username, password)
//3.获取连接
// 相当于之前使用mysql工具连接数据库
// 完整格式:jdbc:mysql://ip:数据库端口号/数据库名称?添加自定义参数
// jdbc:mysql://就是协议 相当于 http://
// url连本机 jdbc:mysql://localhost:3306/db2 简化写法jdbc:mysql:///db2
// 可选自定义参数:useSSL=false 不是用SSL加密并取消提示
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1","root","root");
2.2 Connection
就像打电话,一个Connection
对象,就是一通电话。
所以连接又称会话,Session
。
功能1:获取执行者对象
该对象会执行SQL
语句
-
获取普通执行者对象:
Statement createStatement();
-
获取预编译执行者对象:
PreparedStatement prepareStatement(String sql);
功能2:控制事务
Connection
中定义了三个方法,用来控制事务。对应MySQL
中的三种操作。
-
开启:
setAutoCommit(boolean autoCommit);
参数为false,则开启事务。 -
提交:
commit();
-
回滚:
rollback();
功能3:释放资源
close();
演示代码
package com.cy.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
/**
* JDBC API 详解:Connection
*/
public class JDBCDemo3_Connection {
public static void main(String[] args) throws Exception {
//1. 注册驱动 建议写上去
Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接,配置连接四要素
//jdbc:mysql://ip:数据库端口号/数据库名称?添加自定义参数
String url = "jdbc:mysql://127.0.0.1:3306/web22_day03_jdbc";
String username = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql1 = "update account set money = money - 500 where id = 1";
String sql2 = "update account set money = money + 500 where id = 2";
//4. 获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
try {
// 开启事务
conn.setAutoCommit(false);
//5. 执行sql
int count1 = stmt.executeUpdate(sql1);//受影响的行数
//6. 处理结果
System.out.println(count1);
int i = 3/0;
//5. 执行sql
int count2 = stmt.executeUpdate(sql2);//受影响的行数
//6. 处理结果
System.out.println(count2);
// 提交事务
conn.commit();
} catch (Exception throwables) {
// 回滚事务,手动开启事务之后,该操作可以省略。
// 没有没有提交,他也不会自动提交,就相当于回滚了。
// conn.rollback();
throwables.printStackTrace();
}
//7. 释放资源
stmt.close();
conn.close();
}
}
2.3 Statement
执行者对象,执行SQL
语句并返回SQL
语句执行的结果/结果集。
-
执行
DML/DDL
语句:修改(增删改)int executeUpdate(String sql);
返回值int:返回影响的行数。
参数sql:
insert
、update
、delete
语句。一般不会在
Java
代码中执行DDL
语句,Java
中主要完成对数据库中表记录的操作。 -
执行
DQL
语句:查询ResultSet executeQuery(String sql);
返回值
ResultSet
:封装查询的结果集。参数
sql
:select
语句。 -
释放资源
立即将执行者对象释放:
void close();
-
演示代码:演示增删改
/** * statement执行dql,并获取结果集 * JDBC API 详解:Statement */ public class JDBCDemo4_Statement { /** * 执行DML语句 * @throws Exception */ @Test public void testDML() throws Exception { //1. 注册驱动 //Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写 String url = "jdbc:mysql:///maven?useSSL=false"; String username = "root"; String password = "root"; Connection conn = DriverManager.getConnection(url, username, password); //3. 定义sql String sql = "update account set money = 4000 where id = 1"; //4. 获取执行sql的对象 Statement Statement stmt = conn.createStatement(); //5. 执行sql int count = stmt.executeUpdate(sql);//执行完DML语句,受影响的行数 //6. 处理结果 if(count > 0){ System.out.println("修改成功~"); }else{ System.out.println("修改失败~"); } //7. 释放资源 stmt.close(); conn.close(); } /** * 执行DDL语句 * @throws Exception */ @Test public void testDDL() throws Exception { //1. 注册驱动 //Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写 String url = "jdbc:mysql:///db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); //3. 定义sql String sql = "drop database db2"; //4. 获取执行sql的对象 Statement Statement stmt = conn.createStatement(); //5. 执行sql int count = stmt.executeUpdate(sql);//执行完DDL语句,可能是0 //6. 处理结果 //System.out.println(count); /* if(count > 0){ System.out.println("修改成功~"); }else{ System.out.println("修改失败~"); }*/ System.out.println(count); //7. 释放资源 stmt.close(); conn.close(); } }
-
演示代码:查询及结果集处理
public class JDBCDemo5_ResultSet { /** * 执行DQL * * @throws Exception */ @Test public void testResultSet() throws Exception { //1. 注册驱动 //Class.forName("com.mysql.jdbc.Driver"); //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写 String url = "jdbc:mysql:///maven?useSSL=false"; String username = "root"; String password = "root"; Connection conn = DriverManager.getConnection(url, username, password); //3. 定义sql String sql = "select * from account"; //4. 获取statement对象 Statement stmt = conn.createStatement(); //5. 执行sql executeQuery ResultSet rs = stmt.executeQuery(sql); // 6.1 光标向下移动一行,并且判断当前行是否有数据 while (rs.next()) { //移动到下一行 //6.2 获取数据 getXxx() int id = rs.getInt("id"); String name = rs.getString("name"); double money = rs.getDouble("money"); System.out.println("id = " + id); System.out.println("name = " + name); System.out.println("money = " + money); } //7. 释放资源 rs.close(); stmt.close(); conn.close(); } }
POJO
对象案例
2.4.1 需求
2.4.2 核心步骤查询account账户表数据,封装为Account对象中,并且存储到
ArrayList
集合中
- 使用上述操作完成查询,查询出结果集。
- 定义实体类
Account
- 查询数据,封装到
Account
对象中 - 将
Account
对象存入ArrayList
集合中
package com.cy.pojo;
public class Account {
private int id;
private String name;
private double money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
/**
* 实现需求:查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中
*/
public class JDBCDemo5_ResultSet {
/**
* 查询account账户表数据,封装为Account对象中,并且存储到ArrayList集合中
* 1. 定义实体类Account
* 2. 查询数据,封装到Account对象中
* 3. 将Account对象存入ArrayList集合中
*
*
* @throws Exception
*/
@Test
public void testResultSet2() throws Exception {
//1. 注册驱动
//Class.forName("com.mysql.jdbc.Driver");
//2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
String url = "jdbc:mysql:///maven?useSSL=false";
String username = "root";
String password = "root";
Connection conn = DriverManager.getConnection(url, username, password);
//3. 定义sql
String sql = "select * from account";
//4. 获取statement对象 不能用(Statement)
//Statement stmt = conn.createStatement();
PreparedStatement pps = conn.prepareStatement(sql);
//5. 执行sql
//ResultSet rs = stmt.executeQuery(sql);
ResultSet rs = pps.executeQuery();
// 创建集合
List<Account> list = new ArrayList<>();
// 6.1 光标向下移动一行,并且判断当前行是否有数据
while (rs.next()){
Account account = new Account();
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money1");
account.setId(id);
account.setName(name);
account.setMoney(money);
// 存入集合
list.add(account);
}
System.out.println(list);
//7. 释放资源
rs.close();
pps.close();
conn.close();
}
}
2.5 PreparedStatement
2.5.1概述
预编译的执行者对象,执行SQL
语句并返回SQL
语句执行的结果集。
相对于Statement
来说,PreparedStatement
有额外两个作用:
- 通过转义字符避免
SQL
注入问题。 - 开启预编译后,通过预编译可以提高
SQL
语句的执行效率。
API
-
获取该对象
// 这里的SQL语句,不需要指定参数,而是通过?作为占位符,后面再通过setString方法把参数设置进来 PreparedStatement connection.prepareStatement(sql)
-
设置参数
// index 从1 开始 pst.setString(int index, String paramter)
-
执行
DML
语句:修改(增删改)int executeUpdate();
返回值int:返回影响的行数。
执行
DQL
语句:查询ResultSet executeQuery();
返回值
ResultSet
:封装查询的结果集。 -
释放资源
立即将执行者对象释放:
void close();
-
演示代码
/** * * @throws Exception */ @Test public void testPreparedStatement() throws Exception { //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写 String url = "jdbc:mysql:///db1?useSSL=false"; String username = "root"; String password = "1234"; Connection conn = DriverManager.getConnection(url, username, password); // 接收用户输入 用户名和密码 String name = "zhangsan"; String pwd = "' or '1' = '1"; // 定义sql 预编译sql ? 占位符 String sql = "select * from tb_user where username = ? and password = ?"; // 获取pstmt对象 PreparedStatement pstmt = conn.prepareStatement(sql); // 设置?的值 pstmt.setString(1,name); pstmt.setString(2,pwd); // 执行sql ResultSet rs = pstmt.executeQuery(); // 判断登录是否成功 if(rs.next()){ System.out.println("登录成功~"); }else{ System.out.println("登录失败~"); } //7. 释放资源 rs.close(); pstmt.close(); conn.close(); }
可以通过日志看到其运行原理及效果。
-
准备工作1:开启预编译
想要看小预编译执行者对象的执行效果,需要先在
url
中开启预编译,添加如下自定义参数即可useServerPrepStmts=true
添加该参数后,完整的
URL
链接如下:jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true
-
准备工作2:打开日志,查看预编译等相关效果
在
my.ini
文件末尾,添加如下内容,并重启MySQL
服务。log-output=FILE general-log=1 general_log_file="D:\mysql.log" slow-query-log=1 slow_query_log_file="D:\mysql_slow.log" long_query_time=2
-
重启
MySQL
,方式1:可以打开服务窗口,手动重启
MySQL
服务。方式2:在以管理员身份打开的命令行中,键入以下命令
net stop mysql # 关闭mysql服务 net start mysql # 开启mysql服务
-
结论
通过修改查询相关代码,并查看日志,可以得出如下结论:
- 在
URL
中开启预编译后,相同模板的SQL
语句在创建预编译执行者对象的时候,就已经发送到SQL
,并完成了语法检查和编译。 - 预编译执行者对象,在往
SQL
模板中设置参数的时候,会对参数中的特殊符号添加转义字符\
,避免SQL
注入 - 使用相同模板的多条不同参数的
SQL
语句,只会在第一次创建预编译执行者对象的时候编译一次,从而提高执行效率。
- 在
-
演示代码
/** * PreparedStatement原理 * @throws Exception */ @Test public void testPreparedStatement2() throws Exception { //2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写 // useServerPrepStmts=true 参数开启预编译功能 String url = "jdbc:mysql:///db1?useSSL=false&useServerPrepStmts=true"; String username = "root"; String password = "root"; Connection conn = DriverManager.getConnection(url, username, password); // 接收用户输入 用户名和密码 String name = "zhangsan"; String pwd = "' or '1' = '1"; // 定义sql String sql = "select * from tb_user where username = ? and password = ?"; // 获取pstmt对象 PreparedStatement pstmt = conn.prepareStatement(sql); Thread.sleep(10000); // 设置?的值 pstmt.setString(1,name); pstmt.setString(2,pwd); ResultSet rs = null; // 执行sql rs = pstmt.executeQuery(); // 设置?的值 pstmt.setString(1,"aaa"); pstmt.setString(2,"bbb"); // 执行sql rs = pstmt.executeQuery(); // 判断登录是否成功 if(rs.next()){ System.out.println("登录成功~"); }else{ System.out.println("登录失败~"); } //7. 释放资源 rs.close(); pstmt.close(); conn.close(); }
使用任意用户名并配合下面的密码,登录
' or '1' = '1
普通执行者对象拼接后的SQL
语句,变成了:
select * from tb_user where username = 'xxxx' and password = '' or '1' = '1'
普通的statement
只是把用户输入的用户名和密码进行了简单的拼接,拼接到了SQL
语句中。
这样,用户名或密码中特殊的单子或符号就被识别成了SQL
语句中的关键字或者是特殊符号。
思路:仅把' or 1=1 --
当做普通的字符串来处理,使用转义符号\
,然后面的符号表示其本来的意思(现原形)。
PreparedStatement
就是使用这个思路解决的。
PreparedStatement
中的方法public void setString(int parameterIndex, String x) throws SQLException
// 提前准备好长度变量,不用每次调用length()方法,效率高
int stringLength = x.length();
for(int i = 0; i < stringLength; ++i) {
// 获取参数的每一个字符
char c = x.charAt(i);
// 判断是否是特殊符号,如果是就在前面拼一个 \ 转义,让这个特殊符号表示自己原来的意思。
switch(c) {
case '\u0000':
buf.append('\\');
buf.append('0');
break;
case '\n':
buf.append('\\');
buf.append('n');
break;
case '\r':
buf.append('\\');
buf.append('r');
break;
case '\u001a':
buf.append('\\');
buf.append('Z');
break;
case '"':
if (this.usingAnsiMode) {
buf.append('\\');
}
buf.append('"');
break;
case '\'':
buf.append('\\');
buf.append('\'');
break;
case '\\':
buf.append('\\');
buf.append('\\');
break;
case '¥':
case '₩':
if (this.charsetEncoder != null) {
CharBuffer cbuf = CharBuffer.allocate(1);
ByteBuffer bbuf = ByteBuffer.allocate(1);
cbuf.put(c);
cbuf.position(0);
this.charsetEncoder.encode(cbuf, bbuf, true);
if (bbuf.get(0) == 92) {
buf.append('\\');
}
}
buf.append(c);
break;
default:
buf.append(c);
}
}
4. 连接池
4.1 概念
初始化并维护多个连接对象,当其他地方需要数据库连接时,从连接池获取;用完之后,归还到连接池。
以此实现连接的复用,提高效率。
数据源:DataSource
,内部维护了一个连接池,还提供了操作连接池的一些功能。
一般情况下,会把数据源和连接池混为一谈,把数据源喊做连接池。
常见的数据源有:C3P0
、Druid。我们学习Druid
。
以空间换时间的做法。
游戏背包、新闻客户端。
- 提供更好的使用体验
- 对资源的消耗会更少。不会频繁的创建和销毁对象。

Druid
使用
4.3.1 导包
// 导包并添加到添加了模块类库
druid-1.1.12.jar
4.3.2 配置文件
properties
配置文件(名字任意),但是配置文件中key的值固定。常见参数如下:
# 连接四要素
# 数据库驱动全类名
driverClassName=com.mysql.jdbc.Driver
# URL
url=jdbc:mysql:///maven?useSSL=false&useServerPrepStmts=true
#用户名
username=root
#密码
password=root
# 其他参数
# 初始化连接数
initialSize=5
# 最大连接数
maxActive=10
# 超时时间
maxWait=3000
完整参数介绍:
PerConnectionSize 每个连接最多缓存多少个SQL 20 filters 这里配置的是插件,常用的插件有: stat,wall,slf4j 监控统计: filter:stat 日志监控: filter:log4j 或者 slf4j 防御SQL注入: filter:wall connectProperties 连接属性。比如设置一些连接池统计方面的配置。 druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000 比如设置一些数据库连接属性: 4.3.3 代码
public class DruidDemo {
public static void main(String[] args) throws Exception {
//1.导入jar包
//2.定义配置文件
//3. 加载配置文件 map集合 key-value
Properties prop = new Properties();
prop.load(new FileInputStream("web_day03_jdbc-demo/src/druid.properties"));
//4. 获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//5. 获取数据库连接 Connection
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 这里可以调用connection的close方法
// Druid充了connection的close方法,实现逻辑变成了回收连接,而非关闭连接
connection.close();
// 获取当前代码的运行路径,可以运行下面一行代码
//System.out.println(System.getProperty("user.dir"));
}
}