文章目录
JDBC
意义
-
JDBC 技术,现代开发几乎所有项目都围绕数据库开展
- 任何一门编程语言发展都必须对数据库开发支持
-
Java 最开始就支持数据库开发标准 JDBC:
Java DateBase Connectivity
- JDBC 本质上不是一个技术,而是一种服务
-
Java 为 JDBC 提供专门模块:
java.sql
- JDBC 核心组成
DriverManager
类 及若干接口Connection
Statement
PreparedStatement
ResultSet
- JDBC 核心组成
使用意义
-
为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题
- 使用 jdbc 可连接任何提供了 jdbc 驱动程序的数据库系统,完成对数据库的各种操作
- jdbc 是 Java 提供用于数据库操作的接口API,只需要面向接口编程
- 面向接口编程:降低耦合度,提供程序扩展性
- 不同数据库厂商针对接口提供不同实现
- jdbc 是 Java 提供用于数据库操作的接口API,只需要面向接口编程
- JDBC API 是一系列接口
- 统一和规范了应用程序与数据库的连接、执行 sql 语句并得到返回结果等各类操作
- 相关类和接口在
java.sql
和javax.sql
包
- 使用 jdbc 可连接任何提供了 jdbc 驱动程序的数据库系统,完成对数据库的各种操作
-
JDBC 程序数据库访问四种形式
-
JDBC-ODBC 桥连接
- 利用微软ODBC技术进行数据连接
- 再利用JDBC访问ODBC技术进行数据库开发
- Java 默认支持技术,无需任何额外配置
- 流程:程序 → JDBC → ODBC → 数据库
- 操作性能极差,使用人数极少
- 利用微软ODBC技术进行数据连接
-
JDBC 连接
- 直接利用 JDBC 进行数据库连接处理
- 流程:程序 → JDBC → 数据库
- 一般只连接本地数据库
-
JDBC 网络连接
- 通过特定的网络协议连接指定的数据库服务
- 流程:程序 → JDBC → 网络数据库(IP地址、端口号)
-
JDBC 协议链接
- 自己编写指定协议实现数据库的访问
- 需要对各种协议掌握
- 自己编写指定协议实现数据库的访问
-
使用
-
Java 连接 MySQL 需要驱动包,解压后得到 jar 库文件,在对应的项目中导入该库文件
-
官方下载地址为:mysql-connector-java
-
提供的 jar 包:mysql-connector-java-5.1.39-bin.jar
-
-
确保已经启动数据库的数据监听服务(listene) 和 实例服务(SID)
-
导入 jar 包,连接 MySQL(Oracle)
- 配置数据库驱动程序
- 使用记事本进行程序编写
- 手动配置到 classpath
- 对 Eclipse 等开发工具无效
- Eclipse
- JavaBuilderPath 中配置驱动程序
- IDEA
- 导入 jar 包
- 使用记事本进行程序编写
- 配置数据库驱动程序
- 为什么单独配置数据库驱动
- JDBC 是操作标准,不牵扯具体实现类
- 由数据库厂商实现
- JDBC 是操作标准,不牵扯具体实现类
- MySQL 8.0 以上版本
- 驱动包版本 mysql-connector-java-8.0.16.jar
com.mysql.jdbc.Driver
更换为com.mysql.cj.jdbc.Driver
- 不需要建立 SSL 连接时需要显式关闭
allowPublicKeyRetrieval=true
允许客户端从服务器获取公钥- 还需要设置 CST 时区
jdbc:mysql://localhost:3306/test_demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone= UTC
- MySql 8.0 版本 url 设置
- 显式关闭 SSL连接、允许服务器获取公钥、设置时区
流程
-
注册驱动:加载
Driver
类- 通过反射机制加载数据库驱动程序类
Class aClass = Class.forName(“com.mysql.jdbc.Driver”)
- MySQL 驱动类
com.mysql.jdbc.Driver"
:8.0 之前版本com.mysql.cj.jdbc.Driver
:8.0 及以上版本
- Oracle 驱动类
Oracle.jdbc.driver.OracleDriver
- 通过反射机制加载数据库驱动程序类
-
获取连接:得到
Connection
对象- 通过
DriverManager
类方法获取Connection
对象Connection con = DriverManager. getConnection(url, username, password);
- 传入连接地址、用户名、密码
Driver
类直接获得Connection
对象- 先反射创建
Driver
实例化对象Driver driver = (Driver)aClass.getDeclaredConstructor().newInstance();
- 强制转型
Class
对象为Driver
Connection con = driver.connect("url", Properties)
- 获取连接对象;传入连接地址、配置文件
- 配置文件由
Properties
类load
方法通过IO
流加载- 文件中包括数据库连接信息:用户名、密码
- 先反射创建
- 通过
-
获取数据库操作对象,传入 sql 语句
PreparedStatement pre = con. prepareStatement(sql)
proparedStatement(sql)
或statement()
- 两种数据库操作类
-
执行增删改查:写
sql
语句到数据库执行,再返回结果int rows = pre.executeUpdate()
- 执行 DML 语句
- 返回受影响行数
ResultSet set = pre.executeQuery()
- 执行 DQL 语句
- 返回查询结果集
- 处理查询到的数据记录
-
释放资源:关闭各种连接
- 防止资源占用浪费
- set.close()
- pre.close()
- con.close()
/* // 编写配置文件 Properties pro = new Properties(); pro.setProperty("url","jdbc:mysql://localhost:3306/demo01"); pro.setProperty("user","用户名"); pro.setProperty("password","密码"); pro.setProperty("driver","com.mysql.jdbc.Driver"); pro.store(new FileOutputStream("src\\mysql.properties"),"reduce"); */ Properties pro = new Properties(); // 加载配置文件 pro.load(new FileInputStream("src\\mysql.properties")); // 读取配置文件中的信息 String user = pro.getProperty("user"); String password = pro.getProperty("password"); String driver = pro.getProperty("driver"); String url = pro.getProperty("url"); try { // 反射加载驱动类,可自动加载 Class.forName(driver); // 驱动管理类获取数据库连接对象 Connection con = DriverManager. getConnection(url, user, password); // 编写sql语句 String sql = "insert into new values(1, '张三')"; // 获取操作数据库对象并传入 sql 语句 // 链式编程直接执行 sql 语句 int rows = con.preparedStatement(sql) .executeUpdate(); // 执行 dml 语句返回受影响行数, > 0 表示执行成功 if (rows > 0){ System.out.println("插入成功"); } //关闭释放资源 state.close(); con.close(); } catch (SQLException | ClassNotFoundException e){ e.printStackTrace(); }
-
url:要连接的数据库路径
-
MySQL
-
jdbc:mysql://localhost:3306/demo
-
参数部分 含义 jdbc:mysql://
Java规定的 JDBC 连接 MySQL 的协议 localhos:
要链接的数据库的 IP 地址 3306
数据库端口号 demo
要连接的数据库库名
-
-
数据库连接本质是 socket 连接
-
-
Oracle
-
jdbc:oracle:thin:@localhost:1521:demo
-
参数部分 含义 jdbc:oracle:thin
JDBC 连接 Oracle 协议 @localhost:
要链接的数据库的 IP 地址 1521
端口号 demo 要连接的数据库库名
-
-
-
Properties:包含数据库信息的配置文件
- user 和 password 两个 key 是规定好的
- values 是实际 数据库名 和 密码
-
sql:要执行的 sql 语句
- sql 语句根据 sql 语法编写
连接方式
五种方式
-
直接使用
com.myql.jdbc.Driver
类创建驱动对象-
属于静态加载,灵活性差,依赖性强
-
// 加载Driver类 Driver driver = new Driver(); // 要连接的数据库的 url String url = "jdbc:mysql://localhost:3306/demo"; // 将数据库的用户名、密码写入配置文件 pro Properties pro = new Properties(); pro.setProperty("user","用户名"); pro.setProperty("password","密码"); // 创建连接,将 url 和 pro 传入 Connection con = driver.connect(url, pro); // 编写 sql 语句 String sql = "insert into table_name values()"; // Statement 用于执行静态 sql 语句并返回其执行结果的对象 Statement state = con.createStatement(); // 执行sql语句,并返回受影响行数 int rows = state.executeUpdate(sql); // 关闭释放资源 state.close(); con.close();
-
-
通过反射加载
-
动态加载,减少依赖性
-
Class clazz = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver)clazz.getDeclaredConstructor().newInstance(); // 获得 Driver 对象后操作不变
-
-
使用
DriverManager
替换Driver
-
拓展性更好
-
Class clazz = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver)clazz.newInstance(); String url = "jdbc:mysql://localhost:3306/demo"; // 无需创建 Properties String uesr = "用户名"; String password = "密码"; // 注册driver 驱动 DriverManager.registerDriver(driver); // 获取连接;同时支持多种创建方式,可传入 Properties 配置文件 Connection con = DriverManager.getConnection(url, user, password); // 获取连接对象后操作不变
-
-
使用 Class.forName() 自动完成注册驱动
-
Driver
类中静态代码块已经将驱动注册到DriverManager
- 加载
Driver
类后可直接使用驱动管理类DriverManager
操作
// 反射加载 Driver 类 Class.forName("com.mysql.jdbc.Driver"); //可不写,建议加上 String url = "jdbc:mysql://localhost:3306/demo"; // 无需创建Properties String uesr = "用户名"; String password = "密码"; // 获取 Connection 连接,无需进行驱动注册 Connection con = DriverManager.getConnection(url, user, password); // 获取连接后操作不变
- 加载
-
mysql 驱动 5.1.6 后无需
Class.forName("com.mysql.jdbc.Driver")
- jdk 1.5 后使用
jdbc4
,不再需要显式调用Class.forName()
加载驱动 - 自动调用驱动包下的
META-INF\services\java.sql.Driver
文本中的类名称注册
- jdk 1.5 后使用
-
但建议写全
-
-
优化方式4
-
增加配置文件,连接更加灵活
-
Properties pro = new Properties(); // 加载配置文件 pro.load(new FileInputStream( "src\\mysql.properties")); // 读取配置文件中的信息 String user = pro.getProperty("user"); String password = pro.getProperty( "password"); String driver = pro.getProperty("driver"); String url = pro.getProperty("url"); // 加载驱动;可不写,建议写全 Class.forName(driver); // 使用 DriverManager 获取连接对象,支持直接传入 Properties 对象 Connection con = DriverManager.getConnection(url, user, password); // 得到数据库操作对象并执行查询语句,得到查询结果集 Result set = con.prepareStatement(sql).executeQuery(); // 结果集中存在数据 while(set.next()){ // 根据查询字段的顺序获取值,第一个字段 String name = set.getString(1); // 根据字段名获取值 int age = set.getInt("age"); System.out.println(name + "\t" + age); } //关闭连接,释放资源 set.close(); con.close();
-
读取配置文件
-
ResuorceBundle
读取-
默认读取
classpath
路径下文件- 即
src
目录下文件
- 即
-
不能读取本地文件
-
ResourceBundle re = ResourceBundle.getBundle("文件m"); String driver = re.getString("driver"); String url = re.getString("url"); String user = re.getString("user"); String password = re.getString("password"); Class.forName(driver); Connection con = DriverManager.getConnection(url, user, password);
-
-
Properties
类读取-
Properties pro = new Properties(); pro.load(new FileInputStream("文件路径")); String driver = pro.getProperty("driver"); String url = pro.getProperty("url"); String user = pro.getProperty("user"); String password = pro.getProperty("password"); Class.froName(driver); Connection con = DriverManager.getConnection(url, user, password);
-
JDBC_API
各种接口 API 中常用方法
DriverManager
-
驱动管理类
-
getConnection(String url, String user, String password)
-
getConnection(String url, Properties info)
- 获取连接对象
-
Connection
createStatement()
- 创建
Statement
对象
- 创建
prepareStatement(String sql)
- 生成预处理对象
- 对 sql 语句框架进行预编译处理
Statement
-
java.sql.Statement
是数据库操作接口AutoCloseable
的子接口- 数据库操作后应关闭
Statement
操作 - 即 sql 语句的执行是一个
Statement
接口对象
- 数据库操作后应关闭
-
建立连接后,需要对数据库进行访问,操作数据库:
Statement
:可能有 SQL 注入问题- 操作方法
executeUptate(sql)
- 执行 dml 语句,返回受影响行数
executeQuery(sql)
- 执行查询语句,返回 ResultSet 结果集
execute(sql)
- 执行任意SQL语句,返回布尔值
- 操作方法
PreparedStatement
:预处理CallableStatement
:存储过程
-
Statement
对象用于执行静态sql
语句并返回其生成的结果的对象- 实现数据的更新与查询操作
-
缺陷
Statement
对象执行 SQL 语句,存在 SQL 注入风险- SQL注入:某些系统没有对用户输入的数据进行充分的检查,在用户输入数据中注入非法的 SQL 语句段或命令,恶意攻击数据库
- 防范 SQL 注入只要用
PreparedStatement
取代Statement
即可PreparedStatement
由Statement
扩展而来
- 不能很好描述日期形式
- 需要进行 sql 语句拼接处理
- 编写、维护麻烦
- 敏感字符无法合理拼凑
-
使用场景
- 业务实现要求使用允许 SQL 注入
- 如:升、降序 需要输入 sql 关键字 desc、 asc
PreparedStatement
对关键字也会自动匹配为字符串类型,无法进行灵活排序- 会自动添加双引号
- 业务实现要求使用允许 SQL 注入
PreparedStatement
-
预处理查询
- 原理:预先对 sql 语句框架进行编译处理,然后再传入值
-
con.prepareStatement(sql);
- 获取预处理对象并对 sql 框架进行预编译处理
-
用户传入参数不会参与编译
- 传入关键字也无法进行编译
- 无法传入
desc
、asc
等完成灵活操作
- 无法传入
- MySQL 本身就支持预编译,但是默认关闭
- 传入关键字也无法进行编译
-
sql 语句中的值用占位符
?
来表示setXxx(index, value)
方法来设置值- Xxx:值数据类型
- index:占位符索引
- value:值,自动根据数据类型匹配格式;例如字符串自动添加双引号
-
好处
- 不再需要拼接 sql 语句,减少语法错误
- 有效解决 sql 注入的问题
- 减少编译次数,效率较高
- sql 语句完全相同时不再编译直接执行
PreparedStatement sql
语句结构完全变化- 预编译后传入值直接执行
-
常用方法
executeUptate()
- 执行 dml 语句,返回受影响行数
executeQuery()
- 执行查询语句,返回
ResultSet
结果集
- 执行查询语句,返回
execute()
- 执行任意 SQL 语句,返回布尔值
setXxx(int index, Xxx x)
- 有效解决 sql 注入问题
- Xxx:传入字段的数据类型,例如:setInt(1, “字段”)
setObject(int index, Objcet o)
- 当作对象传入
-
示例
-
try { Properties pro = new Properties(); pro.load(new FileInputStream("src\\mysql.properties")); String user = pro.getProperty("user"); String password = pro.getProperty("password"); String driver = pro.getProperty("driver"); String url = pro.getProperty("url"); //加载驱动;可不写, Class.forName(driver); Connection con = DriverManager.getConnection(url, user, password); String sql = "insert into table_name values()"; String sql1 = "select * from table_name where name = ? and password = ?"; // 执行dml语句,返回值为受影响行数 // 直接填入 sql 语句,之后执行时不写 int rows = con.prepareStatement(sql). executeUpdate(); // 获取操作数据库对象 PreparedStatement pre = con.prepareStatement( sql1); // 设置 ?值,1、2 表示索引,第几个?;传入对应值 pre.setString(1, "用户名"); pre.setString(2, "密码"); // 执行查询语句; 返回ResultSet 对象 ResultSet set = pre.executeQuery(); // 自定义处理结果集数据 // 关闭释放资源 set,close(); pre.close(); con.close(); } catch (IOException | ClassNotFoundException | SQLException e) { e.printStackTrace(); }
-
ResultSet
-
结果集:表示数据库结果集的数据表
- 通常通过执行查询数据库的语句生成
- 查询结果保存在内存中,返回结果过大时可能会使程序崩溃
-
类型
- 创建
Statement
对象时决定createStatement(int, int)
- 第一个参数决定结果集是否滚动
- 第二个参数决定结果集数据是否只读
- 是否可滚动
- 默认可滚动
- 设置第一个参数
ResultSet.TYPE_FORWORD_ONLY
- 不滚动
ResultSet.TYPE_SCROLL_INSENSITIVE
- 可滚动,不敏感
- 结果集数据不随数据库再变化
ResultSet.TYPE_SCROLL_SENSITIVE
- 可滚动,敏感
- 结果集数据会随数据库变化
- 数据库一般不实现该功能,正常不使用
- 不可移动:只能使用
next()
方法移动光标
- 是否只读
- 结果集只读数据
- 不可通过修改结果集影响数据库数据
- 设置第二个参数
ResultSet.CONCUR_READ_ONLY
- 只读
ResultSet.CONCUR_UPDATABLE
- 可更新,修改结果集会影响数据库数据
- 结果集只读数据
- 创建
-
ResultSet
对象保持一个光标指向其当前的数据行-
最初光标在第一行之前
-
next()
方法将光标移动到下一行- 没有更多行时返回
false
- 在
while
循环中遍历结果集
- 在
- 没有更多行时返回
-
previous()
:向上移动一行 -
getXxx()
方法获取对应字段数据- 传入 字段索引 或 字段名,得到对应字段值
- 索引是查询语句中标注的字段的顺序
- sql 中 通过
*
查询时按照表中字段顺序 - sql 语句将字段取别名时使用别名获取数据
- 例如
int id = getInt(1)
:id字段在第一个(查询 语句的顺序)int id = get("id")
:根据字段名 id 获取对应值(常用)
- 传入 字段索引 或 字段名,得到对应字段值
-
getMetaData()
:获取元数据- 返回值:ResultSetMetaData
-
getColumnCount()
- 获取结果集列数
-
getColumnName(int colIndex)
- 获取指定列列名
-
getObject()
- 传入列名 或 索引,接受类型为Object
-
getDate()
、setDate()
日期类型都是java.sql.Date
- 但是使用
java.util.Date
- 要转为
java.sql.Date
- 要转为
- 但是使用
-
-
MySQL 和 Java 日期类型对应
-
date
→java.sql.Date
-
time
→java.sql.Time
-
timestamp
→java.Timestamp
-
在 Java 中使用 java.util.Data 日期类型,对 java.sql.Data 类型需要转型
-
-
java.sql.Data
是java.util.Date
的子类- 无需转型,可直接使用
-
java.util.Date
转为 sql 日期类型需要借助构造器- 将
Date
类型转为毫秒数long time = new java.util.Date().getTimes()
- 用毫秒数创建 sql 包 Date、Time、Timestamp 对象
new java.sql.Date(time)
- 将
JDBC Utils
封装
-
将JDBC对数据库的的连接、关闭 操作封装到JDBCUtils工具类
package demo01; import java.io.FileInputStream; import java.io.IOException; import java.sql.*; import java.util.Properties; public class JDBCUtils { static String url; static String user; static String password; static String driver; // 静态代码块,在加载类时直接读取配置文件。只执行一次 static { try { Properties pro = new Properties(); pro.load(new FileInputStream("src\\mysql.properties")); url = pro.getProperty("url"); user = pro.getProperty("user"); password = pro.getProperty("password"); driver = pro.getProperty("driver"); } catch (IOException e) { // 在开发中将编译异常转成运行时异常抛出,调用者可以选择捕获或者默认处理 throw new RuntimeException(e); } } /** * 获取数据库连接 * @return 返回已连接数据库的对象 */ public static Connection getConnection() { try { Class.forName(driver); return DriverManager.getConnection(url, user, password); } catch (SQLException | ClassNotFoundException e) { throw new RuntimeException(e); } } /** * 关闭 JDBC 相关资源连接 * 传参时可以传入null 表示该项不关闭 * @param resultSet 结果集 * @param statement 操作数据库 * @param connection 数据库连接 */ public static void close(ResultSet resultSet, Statement statement, Connection connection) { try { if (resultSet != null) resultSet.close(); if (statement != null) statement.close(); if (connection != null) connection.close(); } catch (SQLException e) { throw new RuntimeException(e); } } }
使用
package demo01;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Test {
/**
* 执行dml语句示例
* @return 返回受影响行数
*/
public int dml() {
int i = 0; // DML 语句操作受影响行数
Connection connection = null; // 连接对象定义在异常块外,方便在 finally 块中使用
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection(); // 通过工具类获取连接
String sql = "insert into tabel_naem values()"; // 编写 Sql 语句
preparedStatement = connection.prepareStatement(sql); // 预编译
i = preparedStatement.executeUpdate(); // 执行 sql
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
JDBCUtils.close(null, preparedStatement, connection); // 关闭相关资源连接
}
return i;
}
/**
* 示例执行查询语句
*/
public void select(){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
String sql = "select * from table_name where id = ? and name = ?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"id");
preparedStatement.setString(2,"name");
resultSet = preparedStatement.executeQuery();
System.out.println("id\t\tname");
while (resultSet.next()) {
String id = resultSet.getString("id");
String name = resultSet.getString("name");
System.out.println(id + "\t\t" + name);
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
JDBCUtils.close(resultSet, preparedStatement, connection);
}
}
}
事务
-
JDBC 程序中
Connection
对象创建时默认自动提交事务- 每次执行 SQL 语句成功时自动提交数据库,不能回滚
-
JDBC 中使用事务让多个 SQL 语句作为整体执行
- 创建
Connection
对象- 调用
setAutoCommit(false)
取消自动提交setAutoCommit(boolean)
:默认开启自动提交
- 调用
- 操作失败或异常时调用
rollback()
方法回滚 - 所有 SQL 语句都执行成功之后调用
commit()
方法提交事务- 不进行提交所有操作的结果都无法在数据库中可见
- 创建
-
同一事务需要使用同一个连接
-
使用同一个
Connection
对象 进行操作 -
Connection con = null; PreparedStatement pre = null; ResultSet resultSet = null; try { con = JDBCUtils.getConnection(); // 关闭自动提交,在 try 之后直接开启事务 con.setAutoCommit(false); String sql = "select * from table_name where id = ? and name = ?"; // 编写 sql,两个参数占位符 pre = con.prepareStatement(sql); // 预编译 sql pre.setString(1, "id"); // 参数赋值 pre.setString(2, "name"); resultSet = pre.executeQuery(); // 执行 sql while (resultSet.next()) { // 遍历结果集 String id = resultSet.getString("id"); String name = resultSet.getString("name"); System.out.println(id + "\t\t" + name); } con.commit(); // 所有语句正常执行后提交事务,无异常时才执行到此处 } catch (SQLException e) { try { // 出现异常时回滚事务 con.rollback(); // 回退到开始状态,传入指定保存点回退到指定时刻 } catch (SQLException throwables) { throwables.printStackTrace(); } throw new RuntimeException(e); }finally { JDBCUtils.close(resultSet, pre, con); // 无论是否执行成功都要关闭连接 }
-
批处理
-
批量更新机制,允许多条语句一次性提交给数据库批量处理
- 需要批量插入或更新记录时使用
- 通常比单独提交处理更有效
- 需要批量插入或更新记录时使用
-
JDBC 批处理方法
addBatch()
- 添加需要批量处理的SQL语句或参数
- 添加到集合临时保存
executeBatch()
- 执行批处理语句
clearBatch()
- 清空批处理包的语句
-
使用批处理
- MySQL 本身批处理机制默认关闭
- 打开批处理功能
rewriteBatchedStatements=true
- 追加在 url 后面,使用
?
连接参数 jdbc:mysql://localhost:3306/demo?rewriteBatchedStatements=true
- 追加在 url 后面,使用
- 打开批处理功能
- MySQL 本身批处理机制默认关闭
-
批处理和
PreparedStatement
一起使用- 既减少编译次数又减少运行次数
- 效率很大提高
-
Connection connection = null; PreparedStatement preparedStatement = null; try { connection = JDBCUtils.getConnection(); String sql = "insert into demo values(?,?)"; preparedStatement = connection.prepareStatement(sql); int count = 0; // 添加5000条数据 while (count < 5000){ preparedStatement.setInt(1, count + 1); preparedStatement.setString(2, "name"); // 添加批处理参数 preparedStatement.addBatch(); count++; // 满一千条SQL语句 执行一次,提交数据库 if(count % 1000 == 0){ // 批处理执行 preparedStatement.executeBatch(); // 执行完之后清除批处理包中的数据 preparedStatement.clearBatch(); } } /* // 添加完成后统一执行 preparedStatement.executeBatch(); // 执行完之后清除批处理包中的数据 preparedStatement.clearBatch(); */ } catch (SQLException e) { throw new RuntimeException(e); }finally { JDBCUtils.close(null, preparedStatement, connection); }
连接池
传统连接
DriverManager
每次获取连接都要向数据库要求一个- 频繁进行数据库连接操作占用很多系统资源
- 容易造成服务器崩溃
- JDBC 获取连接都要先将
Connection
加载到内存- 再验证 IP、用户名 和 密码(0.05s ~ 1s)
- 每次数据库连接用完之后都要断开
- 若出现异常未能关闭,将导致数据库内存泄漏,最终导致数据库重启
- 频繁进行数据库连接操作占用很多系统资源
- 不能控制创建的连接数量
- 连接过多可能导致内存泄漏、MySQL 崩溃
- 采用数据库连接池技术(connection pool)解决数据库连接问题
连接池
介绍
- 预先在缓冲池中放入一定数量的连接
- 需要建立数据库连接时从缓冲池中取出一个
- 使用完毕后再放回去
- 不再引用该连接,而非关闭连接
- 数据库连接池负责分配、管理 和 释放 数据库连接
- 允许应用程序重复使用一个现有连接,而不是重新建立一个
- 应用程序向连接池请求的连接数超过最大连接数量
- 请求将被加入到等待队列
连接池种类
- JDBC 的数据库连接池实现
javax.sql.DataSource
DateSource
是一个接口,通常由第三方提供实现
- 常用数据库连接池
- C3P0:速度相对较慢,稳定性不错
- DBCP:速度相较 C3P0 快,但不稳定
- Proxool:有监控连接池状态的功能,稳定性比 C3P0 差一点
- BoneCP:速度快
- Druid(德鲁伊):阿里提供的数据库连接池
- 集 DBCP、C3P0、Proxccl 优点于一身
C3P0
- 先导入 jar 包
使用方式1
try {
// 读取配置文件
Properties pro = new Properties();
pro.load(new FileInputStream( "src\\mysql.properties"));
String user = pro.getProperty("user");
String password = pro.getProperty("password");
String driver = pro.getProperty("driver");
String url = pro.getProperty("url");
// 创建连接池对象,并将 drive、url、user、password 写入
ComboPooledDataSource combo = new ComboPooledDataSource();
combo.setDriverClass(driver);
combo.setUser(user);
combo.setPassword(password);
combo.setJdbcUrl(url);
// 设置初始连接数为 10 个
combo.setInitialPoolSize(10);
// 最大连接数为 50 个,超过 50 个则进入排队等待
combo.setMaxPoolSize(50);
// 从连接池得到一个连接
Connection connection = combo.getConnection();
// 取消自动提交
connection.setAutoCommit(false);
String sql = "";
PreparedStatement pre = connection.prepareStatement(sql);
pre.executeUpdate();
connection.commit();
pre.close();
connection.close();
} catch (IOException | PropertyVetoException | SQLException e) {
e.printStackTrace();
}
使用方式2
-
使用 xml 配置文件进行连接池配置
-
<c3p0-config> <!-- 配置文件名字 --> <named-config name="demo"> <!-- 驱动类 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <!-- url,修改为自己使用的数据库 url--> <property name="jdbcUrl">jdbc:mysql://192.168.137.1:3306/demo01</property> <!-- 用户名 --> <property name="user">root</property> <!-- 密码 --> <property name="password">123456</property> <!-- 当前连接数量不够时每次增长次数--> <property name="acquireIncrement">5</property> <!-- 初始的连接数 --> <property name="initialPoolSize">10</property> <!-- 最小连接数,长时间无使用的连接会关闭,只保留最小连接数 --> <property name="minPoolSize">5</property> <!-- 最大连接数 --> <property name="maxPoolSize">10</property> <!-- 可连接的最多的命令对象数 --> <property name="maxStatements">5</property> <!-- 每个连接对象可连接的最多的命令对象数 --> <property name="maxStatementsPerConnection">2</property> </named-config> </c3p0-config>
-
-
导入配置文件,创建连接池对象直接连接
-
try { // 创建连接池对象,传入配置文件名 ComboPooledDataSource combo = new ComboPooledDataSource("demo"); // 直接获取连接,加载 ComboPooledDataSource 时已读取配置文件,数据库连接已创建 Connection con = combo.getConnection(); String sql = ""; PreparedStatement pre = con.prepareStatement(sql); pre.executeUpdate(); pre.close(); con.close(); } catch ( SQLException e) { e.printStackTrace(); }
-
Druid(建议)
使用
-
导入jar包,导入配置文件到src目录
-
修改配置文件
-
# key=value driverClassName=com.mysql.jdbc.Driver # url地址,开启批处理 url=jdbc:mysql://localhost:3306/demo01?rewriteBatchedStatements=true # url=jdbc:mysql://localhost:3306/girls username=root password=123456 # initial connection Size,初始连接数 initialSize=10 # min idle connection size,最小空闲 minIdle=5 # max active connection size,最大连接数 maxActive=20 # max wait time (5000 mil seconds),等待时间 maxWait=5000
-
-
得到连接
-
try { Properties pro = new Properties(); // 加载配置文件 pro.load(new FileInputStream("src\\druid.properties")); // 得到连接池对象 DataSource dataS = DruidDataSourceFactory.createDataSource(pro); // 取出连接 Connection connection = dataS.getConnection(); connection.close(); // 关闭连接:将连接放回连接池,并非直接关闭 } catch (Exception e) { e.printStackTrace(); }
-
DruidUtils
- 编写 Util 工具类,提供获取连接、关闭连接的方法
package demo01;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
public class DruidUtils {
private static DataSource dataSource ;
// 静态加载配置文件,得到连接池对象,仅执行一次
static {
try {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 从连接池得到连接
* @return 返回连接
* @throws SQLException 可能有SQL异常
*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
// 关闭需要关闭的连接
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if (resultSet != null)
resultSet.close();
if (statement != null)
statement.close();
// connection 关闭只是将断掉连接引用放回连接池,而非彻底断掉连接
if (connection != null)
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
Apache-DBUtils
作用
commons-dbutils
是Apache
组织提供的开源 JDBC 工具类库- 对 JDBC 封装,使用
dbutils
可以极大简化 jdbc 编码工作量
- 对 JDBC 封装,使用
DBUtils
QueryRunner
类- 封装了
SQL
的执行,是线程安全的- 可实现增、删、改、查、批处理
public <T> T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params){}
- 查询方法
conn
:连接对象,可支持事务sql
:sql 语句rsh
:想要得到的结果集类型params
:sql 语句参数集
public int update(Connection conn, String sql, Object... params){}
- dml 语句:增删改操作
- 传入连接对象、sql 语句、sql 语句参数
- 封装了
ResultSetHandler
接口实现类- 处理
java.sql.ResultSet
- 将数据按要求转化为另一种形式
ArrayHandler
- 把结果集的第一行数据转换成对象数组
ArrayListHandler
- 把结果集的每一行转换成对象数组,再存放到 List
BeanHandler
- 将结果集的第一行数据封装到对应的 JavaBean 实例
BeanListHandler
- 将结果集的每一行数据都封装到对应JavaBean 实例,存放到 List 中
ColumnListHandler
- 将结果集的某一列数据存放到 List
KeyedHandler(name)
- 将结果集中的每一行数据封装到 Map,根据指定 Key 将 Map 再存到一个 Map
MapHandler
- 将结果集中第一行数据封装到一个Map,Key 是列名,value是对应的值
MapListHandler
- 将结果集中每一行数据都封装到一个Map,再存放到 List
- ScalarHandler
- 单行单列结果,即 单个值
- 用于
select count(*) from demo
等 - 可能得到 Integer、Long、BigInteger 类型
- 使用 Object 接收,强转为需求类型
- 处理
示例
-
执行常规查询
-
/** * 示例执行查询语句 */ public void select() { Connection connection = null; try { connection = JDBCUtils.getConnection(); // 创建 QueryRunner QueryRunner runner = new QueryRunner(); // 查询的 sql 语句,也可查询部分字段,其他字段在封装对象中为 null String sql = "select * from `user` where name = ? and age = ? "; //query() 方法执行 sql 语句,查询多条记录 List<User> query = runner.query(connection, sql, new BeanListHandler<>(User.class), "smith", 15); //参数解释 /* connection、sql: 传入连接和 sql 语句 new BeanListHandler<User>(User.class) 得到 resultSet 取出每一行数据封装为 user 对象,再将 user 对象封装到集合 User.class: 使用反射机制获取 User 类的属性进行封装 "smith", 15: 两个参数,给对应 ? 位置赋值的参数,(可变参数 Object...params) 底层得到的 resultSet 会在query中关闭,PreparedStatement 也会在底层创建并关闭 */ for (User user : query){ System.out.println(user); } } catch (SQLException e) { throw new RuntimeException(e); } finally { //不参与事务使用,直接关闭连接 JDBCUtils.close(null, null, connection); } }
-
-
单条记录
-
// 查询单条记录使用: new BeanHandler<>(User.class) String sql = "select * from `user` where name = ?"; User user = queryRunner.query(connection, sql, new BeanHandler<>(User.class), "smith"); // 查询单行单列记录,返回 object 对象 String sql = "select name from `user` where name = ?"; Object obj = queryRunner.query(connection, sql, new ScalarHandler(), "smith");
-
-
DML 语句
-
// String sql = "insert into user values(?,?,?,?,?)"; String sql = "delete from user where name = ?"; // String sql = "update user set name = ? where name = ?"; // update() 方法执行dml语句,返回受影响行数 int rows = queryRunner.update(connection, sql, "smith");
-
BasicDao
-
增删改查通用方工具类
- 将执行sql 语句的操作封装到一个BasicDao类,让其他业务类继承该基础类使用这些通用基础方法
-
基于泛型方法可以适配不同表的实例类
- 各业务类自身还可以扩展自身需要的业务方法
package demo01;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
public class BasicDao {
/**
* 通用的 dml 方法
* @param sql 传去sql 语句
* @param params 传去sql 语句中要赋值的参数
* @return 返回受影响行数
*/
public int dml(String sql, Object... params) {
Connection connection = null;
try {
connection = DruidUtils.getConnection();
QueryRunner queryRunner = new QueryRunner();
return queryRunner.update(connesql, params);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
DruidUtils.close(null, null, connection);
}
}
/**
* 通用的查询多条记录方法
* @param sql 传入sql 语句
* @param clazz 传入一个类Class对象,User.class
* @param params 传入为sql语句赋值的可变参数
* @return 将查询的结果集,封装为List<T>返回,记录封装为对应类的对象
*/
public <T>List<T> select(String sql, Class<T> clazz, Object... params) {
Connection connection = null;
try {
connection = DruidUtils.getConnection();
QueryRunner queryRunner = new QueryRunner();
return queryRunner.query(connection, sql, new BeanListHandler<>(clazz));
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
DruidUtils.close(null, null, connection);
}
}
/**
* 返回单行记录
* @param sql 传入的sql语句
* @param clazz 传入的类的Class对象
* @param params 传入为sql语句赋值的可变参数
* @return 返回单行记录
*/
public <T>T selectOne(String sql, Class<T> clazz, Object... params) {
Connection connection = null;
try {
connection = DruidUtils.getConnection();
QueryRunner queryRunner = new QueryRunner();
return queryRunner.query(connection, sql, new BeanHandler<>(clazz), params);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
DruidUtils.close(null, null, connection);
}
}
/**
* 查询单行单列记录
* @param sql 传入的sql语句
* @param params 传入为sql语句赋值的可变参数
* @return 返回单行单列记录
*/
public Object selectOneOne(String sql, Object... params) {
Connection connection = null;
try {
connection = DruidUtils.getConnection();
QueryRunner queryRunner = new QueryRunner();
return queryRunner.query(connection, sql, new ScalarHandler(), params);
} catch (SQLException e) {
throw ne w RuntimeException(e);
} finally {
DruidUtils.close(null, null, connection);
}
}
}
JNDI
-
Java Naming and Directory Interface:Java 命名和目录接口
-
作用:在服务器配置资源,通过统一方式获取配置的资源
-
这里配置数据库连接池
-
项目通过统一方式获取连接池对象
-
TomCat 配置
<!-- 配置格式 -->
<?xml version="1.1 encoding="UTF-8?>
<Context>
<Resource name=""
factory="org.apache.naming.factory.BeanFactory"
tyoe="" <!-- 前三项必须配置 -->
...
/>
</Context>
- name:指定资源名称,获取资源时使用
- factory:创建资源的工厂,基本固定
- type:资源类型
- 在此即是连接池类型
- 还要配置资源必须的属性
例如:C3P0 连接池配置
<Context>
<Resource name="jdbc/dataSource"
type="com.mchange.v2.c3p0.ComboPooledDataSource"
factory="org.apache.naming.factory.BeanFactory"
user="root"
password="123456"
classDriver="com.mysql.jdbc.Driver"
jdbcUrl="jdbc:mysql://127.0.0.1/mydb1"
maxPoolSize.="20"
minPoolSize ="5"
initialPoolSize="10"
acquirelncrement="2"
/>
</Context>
获取资源
启动 Tomcat 就可以在项目中通过 JNCI 方式获取资源
Tomcat 文档提供获取格式
- 与上面 Tomcat 文档配置资源一套
//初始化上下文
Context initCtx = new InitialContext();
//查找资源入口(固定名称),得到上下文对象
Context envCtx = (Context)initCtx .lookup("java:comp/env");
//二次查找,查找配置文件资源,获取资源对象
MyBean bean = (MyBean)envCtx.lookup("bean/MyBeanFactory");
/*
MyBean bean = (MyBean)enCtx.lookup("java:comp/env/bean/MyBeanFactory")
//可以直接查找配置文件
*/
- Context:java.naming.Context
- InitialContext:javax.naming.InitialContext
- lookup(String):获取资源的方法
- MyBean:资源对象类型,使用的资源类型
- java:comp/env:资源入口,固定名称
- 获取到 Context,再进一步获取
- bean/MyBeanFactory
- 对应配置中的 name
- 获取资源对象
DAO 模式
-
DAO (DataAccessobjects 数据存取对象):位于业务逻辑和持久化数据之间实现对持久化数据的访问
- 即将数据库操作都封装起来
-
对外提供相应的接口
-
提供访问关系型数据库系统所需操作的接口,将数据访问和业务逻辑分离对上层提供面向对象的数据访问接口
-
在面向对象设计过程中,用于解决特定问题称为模式
-
优势
-
DAO 模式的优势就在于实现了两次隔离
-
隔离了数据访问代码和业务逻辑代码
- 业务逻辑代码直接调用 DAO 方法即可,不用关心其他业务实现
- 数据访问层代码变化不影响业务逻辑代码,符合单一职能原则,降低了藕合性,提高了可复用性
- 业务逻辑代码直接调用 DAO 方法即可,不用关心其他业务实现
-
隔离了不同数据库实现
- 采用面向接口编程
- 底层数据库变化时,如:由 MySQL 变成 Oracle 只要增加 DAO 接口的新实现类即可,原有 MySQ 实现不用修改
- 符合 开-闭 原则,降低了代码的藕合性,提高了代码扩展性和系统的可移植性
- 采用面向接口编程
-
组成
-
典型的 DAO 模式
-
DAO接口: 把对数据库的所有操作定义成抽象方法,可以提供多种实现
-
public interface PetDao { List<Pet> findAllPets() throws Exception; // 查询所有宠物 }
-
-
DAO 实现类: 针对不同数据库给出DAO接口定义方法的具体实现
-
public class PetDaoImpl extends BaseDao implements PetDao { // 查询所有宠物 public List<Pet> findAllPets() throws Exception { Connection conn=BaseDao.getConnection(); String sql="select * from pet"; PreparedStatement stmt= conn.prepareStatement (sql); ResultSet rs= stmt.executeQuery(); List<Pet> petList = new ArrayList<Pet>(); while(rs.next()) { Pet pet = new Pet( rs.getInt("id"), rs.getInt("owner_id"), rs.getInt("store_id"), rs.getString("name"), rs.getString("type_name"), rs.getInt("health"), rs.getInt("love"), rs.getDate("birthday") ); petList.add(pet); } BaseDao.closeAll(conn, stmt, rs); return petList; } }
-
-
实体类:用于存放与传输对象数据
-
public class Pet { private Integer id; private Integer ownerId; //主人ID private Integer storeId; //商店ID private String name; //姓名 private String typeName; //类型 private int health; //健康值 private int love; //爱心值 private Date birthday; //生日 public int getId(){ return id; } ......//get/set方法不再列出 public void setBirthday(Date birthday){ this.birthdat = birthday; } }
-
-
数据库连接和关闭工具类
-
避免了数据库连接和关闭代码的重复使用,方便修改
-
public class BaseDao { private static String driver="com.mysql.jdbc.Driver"; private static String url="jdbc:mysql://127.0.0.1:3306/ epet"; private static String user="root"; private static String password="root"; static { try { Class.forName(driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } public static void closeAll(Connection conn,Statement stmt,ResultSet rs) throws SQLException { if(rs!=null) {rs.close();} if(stmt!=null) {stmt.close();} if(conn!=null) {conn.close();} } public int executeSQL(String preparedSql, Object[] param) throws ClassNotFoundException { Connection conn = null; PreparedStatement pstmt = null; /* 处理SQL,执行SQL */ try { conn = getConnection(); // 得到数据库连接 pstmt = conn.prepareStatement(preparedSql); // 得到PreparedStatement对象 if (param != null) { for (int i = 0; i < param.length; i++) { pstmt.setObject(i + 1, param[i]); // 为预编译sql设置参数 } } ResultSet num = pstmt.executeQuery(); // 执行SQL语句 } catch (SQLException e) { e.printStackTrace(); // 处理SQLException异常 } finally { try { BaseDao.closeAll(conn, pstmt, null); } catch (SQLException e) { e.printStackTrace(); } } return 0; } }
-