文章目录
JDBC为访问不同的数据库提供了统一的接口,为使用者屏蔽了细节问题。
Java程序员使用JDBC,可以连接任何提供了JDBC驱动程序的数据库系统,从而完成对数据库的各种操作。
JDBC是Java提供一套用于数据库操作的接口APl,java程序员只需要面向这套接口编程即可。不同的数据库厂商,要针对这套接口,需提供不同实现。
JDBC API
JDBC AP是一系列的接口,它统一和规范了应用程序与数据库的连接、执行SQL语句,并到得到返回结果等各类操作,相关类和接口在java.sql与javax.sql包中
JDBC程序编写步骤
前置工作:
下载mysql对应的驱动包【mysql-connector-java-5.1.37.jar】https://wwc.lanzoul.com/isbbB0a577pa
密码:dia1在项目下创建一个文件夹比如 libs
将 mysql-connector-java.jar 拷贝到该目录下, 点击 add as library 添加到项目中
- 注册驱动: 加载Driver类
- 获取连接: 得到Connection
- 执行增删改查:发送SQL给mysql执行
- 释放资源: 关闭相关连接
入门代码
package com.xiaolu.jdbc;
import com.mysql.jdbc.Driver;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @author 林小鹿
* @version 1.0
*/
public class Jdbc01 {
public static void main(String[] args) throws SQLException {
// 前置工作:在项目下创建一个文件夹比如 libs
// 将 mysql.jar 拷贝到该目录下, 点击 add as library 添加到项目中
// 1. 注册驱动: 加载Driver类
Driver driver = new Driver();
// 2. 获取连接: 得到Connection
//(1)jdbc:mysql:// 规定好表示协议,通过jdbc的方式连接mysql
//(2)localhost 主机,也可以是ip地址
//(3)3306 表示mysql监听的端口
//(4)db01 连接到mysql dbms 的哪个数据库
// (5) mysql的连接本质就是socket连接
String url = "jdbc:mysql://Localhost:3306/db01";
// 将用户名和密码放入到 Properties 对象中
Properties properties = new Properties();
properties.setProperty("user", "root"); // 用户
properties.setProperty("password", "123456"); // 密码
Connection connect = driver.connect(url, properties);
// 3. 执行增删改查:发送SQL给mysql执行
String sql = "insert into actor values(null, '林小鹿', '男', '2001-01-01', '110')";
// Statement 用于执行静态SQL语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
int rows = statement.executeUpdate(sql); // 如果是 dml 语句,返回的就是影响行数
System.out.println(rows > 0 ? "成功": "失败");
// 4. 释放资源: 关闭相关连接
statement.close();
connect.close();
}
}
获取数据库连接的五种方式
方式①
//获取Driver?实现类对象
Driver driver = new com.mysql.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/jdbc_db";
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","123");
Connection conn = driver.connect(url,info);
System.out.println(conn);
方式②
//方式1会直接使用com.mysql.jdbc.Driver(),属于静态加载,灵活性差,依赖强
//-推出–>方式2 通过反射实现动态加载
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)clazz.newlnstance();
String url "jdbc:mysql://localhost:3306/jdbc_db";
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","abc123");
Connection conn = driver.connect(url,info);
System.out.println(conn);
方式③
// 使用**DriverManager【驱动管理类】**替换Driver
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)clazz.newlnstance();
String url = "jdbc:mysql://localhost:3306/jdbc_db";
String user = "root";
String password = "123";
DriverManager.registerDriver(driver); // 注册Driver驱动
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
方式④ - 推荐使用
// 使用Class.forName自动完成注册驱动,简化代码=>分析源码
提示:
- mysqL驱动5.1.6可以无需 CLass.forName(“com.mysql.jdbc.Driver"); 去注册
- 从jdk1.5以后使用了jdbc4,不再需要显示调用class.forName()注册驱动,而是自动调用驱动jar包下META-INF\services\java.sql.Driver文本中的类名称去注册
- 但是还是建议写上 CLass.forName(“com.mysql.jdbc.Driver"); 使之更加明确
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/jdbc_db";
String user = "root";
String password = "123";
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
方式⑤ - 推荐使用
// 在方式4的基础上改进,增加配置properties文件,使连接mysql更加灵活
在配置文件中定义和配置用户、密码、链接和驱动
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
// 获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
// 加载驱动
Class.forName(driver);
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
ResultSet[结果集]
- 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成
- ResultSet对象保持一个光标指向其当前的数据行。最初,光标位于第一行之前
- next方法将光标移动到下一行,并且由于在ResultSet对象中没有更多行时返回false,因此可以在while循环中使用循环来遍历结果集
代码
package com.xiaolu.jdbc;
import com.mysql.jdbc.Driver;
import org.junit.jupiter.api.Test;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
* @author 林小鹿
* @version 1.0
* 通过配置文件连接mysql
*/
public class Jdbc02 {
@Test
public void connect() throws IOException, ClassNotFoundException, SQLException {
// 通过配置文件连接mysql
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
// 获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
// 加载驱动
// 1、注册驱动
Class.forName(driver);
// 2、得到链接
Connection connection = DriverManager.getConnection(url, user, password);
// 3、得到 Statement
Statement statement = connection.createStatement();
// 4.组织sql 语句
String sql = "select id, name, sex, borndate from actor";
// 执行给定的SQL语句,该语句返回单个 ResultSet 对象
ResultSet resultSet = statement.executeQuery(sql);
// 5.使用 while取出数据
/* 原表:
+----+-----------+-----+---------------------+--------+
| id | name | sex | borndate | phone |
+----+-----------+-----+---------------------+--------+
| 1 | 林小鹿 | 男 | 2001-01-01 00:00:00 | 110 |
| 2 | 张三 | 男 | 2000-05-28 00:00:00 | 133192 |
| 3 | 小李 | 女 | 2100-05-29 00:00:00 | 192 |
+----+-----------+-----+---------------------+--------+
*/
while (resultSet.next()) {
int id = resultSet.getInt(1);
// int id = resultSet.getInt("id"); // 通过列名来获取
String name = resultSet.getString(2);
String sex = resultSet.getString(3);
String date = resultSet.getString(4);
System.out.println(id + "\t"+name + "\t" + sex + "\t" + date);
}
// 6.关闭连接
resultSet.close();
statement.close();
connection.close();
}
}
Statement
Statement对象 用于执行静态SQL语句并返回其生成的结果对象
-
在连接建立后,需要对数据库进行访问,执行 命令或是SQL语句,可以通过
- Statement 【存在SQL注入】
- PreparedStatement 【预处理】
- CallableStatement 【存储过程】
-
Statement对象执行SQL 语句,存在SQL注入风险
-
SQL注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的SQL语句段或命令,恶意攻击数据库。
-
-- SQL -- 输入用户名为1' or -- 输入万能密码为 or '1'= '1 SELECT FROM admin WHERE NAME ='1' OR'AND pwd = 'OR '1'= '1'
-
package com.xiaolu.jdbc; import java.io.FileInputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; import java.util.Properties; import java.util.Scanner; /** * @author 林小鹿 * @version 1.0 * SQL注入问题 */ public class Jdbc03 { /* -- SQL -- 输入用户名为1' or -- 输入万能密码为or '1'= '1 */ public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); System.out.print("请输入管理员的名字:"); // next():当接收到 空格或者'时,表示结束 String admin_name = scanner.nextLine(); // 如果希望看到SQL注入,这里需要用nextLine,此时回车表示结束 System.out.print("请输入管理员的密码:"); String admin_pwd = scanner.nextLine(); Properties properties = new Properties(); properties.load(new FileInputStream("src\\mysql.properties")); // 获取相关的值 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String url = properties.getProperty("url"); String driver = properties.getProperty("driver"); // 1、注册驱动 Class<?> aClass = Class.forName(driver); // 2、得到连接 Connection connection = DriverManager.getConnection(url, user, password); // 3、执行sql Statement statement = connection.createStatement(); String sql = "select name, pwd from admin where name = '" +admin_name+"' and pwd = '"+ admin_pwd +"'"; ResultSet resultSet = statement.executeQuery(sql); if (resultSet.next()) { System.out.println("登录成功"); } else { System.out.println("登录失败"); } // 4、关闭 resultSet.close(); statement.close(); connection.close(); } }
-
-
要防范SQL注入,只要用 PreparedStatement(从Statement扩展而来)取代Statement就可以了
PreparedStatement【接口】
- PreparedStatement执行的SQL语句中的参数用问号(?)来表示,调用PreparedStatement对象的setXxx()方法来设置这些参数。setXxx()方法有两个参数,第一个参数是要设置的SQL语句中的参数的索引(从1开始),第二个是设置的SQL语句中的参数的值
- 调用executeQuery(),返回ResultSet对象
- 调用executeUpdate():执行更新,包括增、删、修改
- 预处理好处
- 不再使用 + 拼接sql语句,减少语法错误
- 有效的解决了sql注入问题
- 大大减少了编译次数,效率较高
代码
package com.xiaolu.jdbc.preparedstatement_;
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
import java.util.Scanner;
/**
* @author 林小鹿
* @version 1.0
*/
public class PreState {
public static void main(String[] args) throws Exception{
Scanner scanner = new Scanner(System.in);
System.out.print("请输入管理员的名字:"); // next():当接收到 空格或者'时,表示结束
String admin_name = scanner.nextLine(); // 如果希望看到SQL注入,这里需要用nextLine,此时回车表示结束
System.out.print("请输入管理员的密码:");
String admin_pwd = scanner.nextLine();
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
// 获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
// 1、注册驱动
Class<?> aClass = Class.forName(driver);
// 2、得到连接
Connection connection = DriverManager.getConnection(url, user, password);
// 3、执行sql
// 这里的 ? 相当于占位符
String sql = "select name, pwd from admin where name = ? and pwd = ?";
// preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 给 ? 赋值
preparedStatement.setString(1,admin_name);
preparedStatement.setString(2,admin_pwd);
// 执行 select 语句使用 executeQuery
// 如果 执行 dml(update,insert,delete) 语句使用 executeUpdate
// 这里在执行executeQuery时,不用再把sql语句传入
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
System.out.println("登录成功");
} else {
System.out.println("登录失败");
}
// 4、关闭
resultSet.close();
preparedStatement.close();
connection.close();
}
}
JDBC API总结
封装JDBCUtils
在jdbc 操作中,获取连接和释放资源 是经常使用到,因此可以将其封装JDBC连接的工具类JDBCUtils
代码
properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mvc
user=xxx
password=xxx
JDBCUtils工具类1
package com.xiaolu.tools;
import java.sql.*;
import java.util.ResourceBundle;
/**
* @author 林小鹿
* @version 1.0
* @create 2023/2/5 15:21
* @Description
*/
public class JDBCUtils {
private static ResourceBundle bundle = ResourceBundle.getBundle("resources/jdbc"); // 主目录下resources目录下的jdbc.properties文件
private static String user = bundle.getString("user");
private static String password = bundle.getString("password");
private static String url = bundle.getString("url");
private static String driver = bundle.getString("driver");
private JDBCUtils() {
}
static {
try {
// 注册驱动
Class.forName("com.mysql.jdbc.Driver");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 获取连接
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// 关闭相关资源
public static void close(ResultSet set, Statement statement, Connection connection) {
try {
if (set != null) {
set.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
JDBCUtils工具类2
package com.xiaolu.jdbc.utils;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.*;
import java.util.Properties;
/**
* @author 林小鹿
* @version 1.0
* 这是一个工具类,完成 mysql的连接和关闭资源
*/
public class JDBCUtils {
// 定义相关的属性(4个),因为只需要一份,因此做成static
private static String user; // 用户名
private static String password; // 密码
private static String url; // url
private static String driver; // 驱动名
// 构造方法私有化 => 不让创建对象
private JDBCUtils(){}
// 在static代码块中初始化
static {
try {
Properties properties = new Properties();
properties.load(new FileInputStream("src:\\mysql.properties"));
// 读取相关的属性值
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
} catch (IOException e) {
// 将 编译异常 转成 运行异常
// 这时调用者可以选择捕获该异常,也可以选择默认的处理方式,比较方便
throw new RuntimeException(e);
}
}
// 连接数据库,返回Connection
public static Connection getConnection() {
try {
return DriverManager.getConnection(url,user, password);
} catch (SQLException e) {
// 将 编译异常 转成 运行异常
throw new RuntimeException(e);
}
}
// 关闭相关资源
/*
1.ResultSet 结果集
2.Statement 或 PreparedStatement
3. Connection
如果需要关闭资源,就传入对象,否则传入 null 即可
*/
public static void close(ResultSet set, Statement statement, Connection connection) {
// 判断是否为null
try {
if (set != null) {
set.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
// 将 编译异常 转成 运行异常
throw new RuntimeException(e);
}
}
}
案列演示
@Test
public void testDML() {// insert, update, delete
// 1、得到连接
Connection connection = null;
// 2、组织sql语句
String sql = "update actor set name = ? where id = ?";
PreparedStatement preparedStatement = null;
try {
// 连接
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "凯哥");
// preparedStatement.setString(1, "小李");
preparedStatement.setInt(2, 3);
// 执行
preparedStatement.executeUpdate();
} catch (SQLException e) {
// 捕获、打印异常
e.printStackTrace();
} finally {
JDBCUtils.close(null, preparedStatement, connection);
}
}
事务
- JDBC程序中当一个Connection对象创建时,默认情况下是自动提交事务:每次执行一个SQL语句时,如果执行成功,就会向数据库自动提交,而不能回滚。
- JDBC程序中为了让多个SQL语句作为一个整体执行,需要使用事务
- 调用Connection 的 setAutoCommit(false)可以取消自动提交事务
- 在所有的SQL语句都成功执行后,调用Connection 的 commit(); 方法提交事务
- 在其中某个操作失败或出现异常时,调用Connection 的 rollback():方法回滚事务
代码
package com.xiaolu.jdbc.transaction_;
import com.xiaolu.jdbc.utils.JDBCUtils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author 林小鹿
* @version 1.0
* 演示jdbc 中使用mysql事务
*/
public class Transaction_ {
@Test
public void useTransaction() {
// 1、得到连接
Connection connection = null;
// 2、组织sql语句
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
PreparedStatement preparedStatement = null;
try {
// 连接
connection = JDBCUtils.getConnection();
// 将connection 设置为不自动提交,即 开启事务
connection.setAutoCommit(false);
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate(); // 执行第一条
int i = 1 / 0; // 抛出异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第二条
// 如果没有发生异常,提交事务
connection.commit();
} catch (SQLException e) {
// 抛出异常后,在这里有机会进行回滚,即撤销执行的SQL
// rollback() 默认回滚到事务开始的状态
System.out.println("发生了异常,撤销执行的sql...");
try {
connection.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
JDBCUtils.close(null, preparedStatement, connection);
}
}
}
批处理
-
当需要成批插入或者更新记录时。可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率。
-
JDBC的批量处理语句包括下面方法:
- addBatch():添加需要批量处理的SQL语句或参数
- executeBatch():执行批量处理语句
- clearBatch0:清空批处理包的语句
-
**注意:**JDBC连接MySQL时,如果要使用批处理功能,需要在url中加参数
?rewriteBatchedStatements=true -
批处理往往和PreparedStatement一起搭配使用,可以既减少编译次数,又减少运行次数,大大的提高了效率
-
for(inti=0;i<5000;1++){//5000执行 preparedStatement.setstring(1,"jack"i); preparedstatement.setstring(2,"666"); //将sqL语句加入到批处理包中->看源码 preparedstatement.addBatch(); //当有1000条记录时,在批量执行 1f((i+1) % 1000 == 0){// 每满1000条sq1 preparedstatement.executeBatch(); //清空一把 preparedstatement.clearBatch(); } }
底层
- 第一就创建ArrayList - elementData => Object[]
- elementData => Object[] 就会存放我们预处理的sql语句
- 当elementData满后,就按照1.5扩容
- 当添加到指定的值后,就executeBatch
- 批量处理会减少我们发送SqL语句的网络开销,而且减少编译次数,因此效率提高
数据库连接池
c3p0和德鲁伊下载
链接:https://pan.baidu.com/s/1VdUSzs2ZT-i8ve97iaelng
提取码:pbkb
传统的方式获取Connection连接问题分析
- 传统的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将Connection 加载到内存中,再验证IP地址,用户名和密码(0.05s~1s时间)。需要数据库连接的时候,就向数据库要求一个,频繁的进行数据库连接操作将占用很多的系统资源,容易造成服务器崩溃。
- 每一次数据库连接,使用完后都得断开,如果程序出现异常而未能关闭,将导致数据库内存泄漏,最终将导致重启数据库。
- 传统获取连接的方式,不能控制创建的连接数量,如连接过多,也可能导致内存泄漏,MySQL崩溃。
- 解决传统开发中的数据库连接问题,可以采用数据库连接池技术(connection pool)
数据库连接池基本介绍
- 预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中
取出一个,使用完毕之后再放回去。 - 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个
- 当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中
数据库连接池种类
注意:在数据库连接池技术中,close不是真的断掉连接,而是把使用的Connection对象放回连接池
- JDBC的数据库连接池使用javax.sql.DataSource来表示,DataSource只是一个接口,该接口通常由第三方提供实现
- C3P0 数据库连接池(老牌连接池),速度相对较慢,稳定性不错(hibernate,spring)
- DBCP 数据库连接池,速度相对c3p0较快,但不稳定
- Proxool 数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
- BoneCP 数据库连接池,速度快
- Druid(德鲁伊) 是阿里提供的数据库连接池(推荐项目使用),集DBCP、C3PO、Proxool 优点于—身的数据库连接池
C3P0 数据库连接池的使用
-
下载相关的包
-
将 c3p0-0.9.5.2.jar和mchange-commons-java-0.2.11.jar导入到项目中
-
编写 c3p0-config.xml 配置文件,并导入到项目的 src 下
-
<?xml version="1.0" encoding="utf-8"?> <c3p0-config> <default-config> <!-- 驱动类 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <!-- url --> <property name="jdbcUrl">jdbc:mysql://localhost:3306/db01</property> <!-- 用户名 --> <property name="user">root</property> <!-- 密码 --> <property name="password">123456</property> <!-- 每次增长的连接数 --> <property name="acquireIncrement">5</property> <!-- 初始的连接数 --> <property name="initialPoolSize">5</property> <!-- 最小连接数 --> <property name="minPoolSize">5</property> <!-- 最大连接数 --> <property name="maxPoolSize">50</property> <!-- 可连接的最多命令对象数 --> <property name="maxStatements">5</property> <!-- 每个连接对象可连接的最多的命令对象数 --> <property name="maxStatementsPerConnection">2</property> <!-- 超时等待时间 --> <property name="checkoutTimeout">3000</property> </default-config> <!-- 数据源名称 代表连接池 --> <named-config name="xiaoLu_db"> </named-config> </c3p0-config>
-
-
-
使用
-
方式一
-
public void test01() throws Exception{ // 1.创建一个数据源对象 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); // 2.通过配置文件获取相关连接的信息 Properties properties = new Properties(); properties.load(new FileInputStream("src\\mysql.properties")); // 读取相关的属性值 String user = properties.getProperty("user"); String password = properties.getProperty("password"); String url = properties.getProperty("url"); String driver = properties.getProperty("driver"); // 给数据源(连接池) comboPooledDataSource 设置相关的参数 // 注意:连接管理是由 comboPooledDataSource 来管理 comboPooledDataSource.setDriverClass(driver); comboPooledDataSource.setJdbcUrl(url); comboPooledDataSource.setUser(user); comboPooledDataSource.setPassword(password); // 初始化连接数 comboPooledDataSource.setInitialPoolSize(10); // 最大连接数 comboPooledDataSource.setMaxPoolSize(50); long start = System.currentTimeMillis(); for (int i = 0; i < 5000; i++) { Connection connection = comboPooledDataSource.getConnection(); // 这个方法就是从 DataSource 接口实现的 // System.out.println("连接成功"); connection.close(); } long end = System.currentTimeMillis(); System.out.println("c3p0 5000次连接mysql 耗时=" + (end - start)); // 235 }
-
-
方式二(使用配置文件) — 推荐使用
-
public void test02() throws SQLException { // 使用连接池的名称创建对象 ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("xiaoLu_db"); // 连接 Connection connection = comboPooledDataSource.getConnection(); System.out.println("连接成功"); // 关闭连接 connection.close(); } }
-
-
Druid(德鲁伊) 数据库连接池的使用
-
下载相关的包
-
加入 Druid的 jar 包
-
加入druid.properties配置文件,将配置文件加入到项目的 src 目录下
-
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://127.0.0.1:3306/db01?rewriteBatchedStatements=true username=root password=123456 #initialSize connection Size initialSize=10 #max active connection size maxActive=50 #max wait time maxWait=5000 #min idle connection size minIdle=5
-
-
-
使用
-
public void test() throws Exception { //1.加入Druid jar包 //2.加入配置文件druid.properties,将该文件拷贝项目的src目录 //3.创建Properties,对象,读取配置文件 Properties properties = new Properties(); properties.load(new FileInputStream("src\\druid.properties")); // 4.创建一个指定参数的数据库连接池,Druid连接池,并返回一个数据源 DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); Connection connection = dataSource.getConnection(); System.out.println("连接成功"); // 5. 关闭连接 connection.close(); } }
-
将Druid集成工具类JDBCUtils
package com.xiaolu.jdbc.utils;
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;
/**
* @author 林小鹿
* @version 1.0
* 给予druid数据库连接池的工具类
*/
public class JDBCUtilsByDruid {
private static DataSource ds;
// 在静态代码块完成 ds初始化
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src\\druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
// 编写getConnection方法
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//在数据库连接池技术中,close不是真的断掉连接
//而是把使用的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);
}
}
}
使用案例
@Test
public void test() {
System.out.println("使用 druid 的方式来完成");
// 1、得到连接
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
// 2、组织sql语句
String sql = "select id, name, sex, borndate from actor";
try {
// 连接
connection = JDBCUtilsByDruid.getConnection();
statement = connection.createStatement();
resultSet = statement.executeQuery(sql);
while (resultSet.next()) {
int id = resultSet.getInt(1);
// int id = resultSet.getInt("id"); // 通过列名来获取
String name = resultSet.getString(2);
String sex = resultSet.getString(3);
String date = resultSet.getString(4);
System.out.println(id + "\t"+name + "\t" + sex + "\t" + date);
}
} catch (SQLException e) {
// 捕获异常
e.printStackTrace();
} finally {
JDBCUtilsByDruid.close(resultSet, statement, connection);
}
}
Apache — DBUtils
解决resultSet的问题
- 结果集和connection 是关联的,即如果关闭连接,就不能使用结果集
- 结果集不利于数据管理【只能使用一次】
- 使用返回信息也不方便
土方法
resultSet → 数据封装到类 → 封装到集合
基本介绍
- commons-dbutils 是 Apache 组织提供的一个开源JDBC工具类库,它是对JDBC的封装,使用dbutilsi能极大简化 jdbc 编码的工作量。
- DbUtils类
- QueryRunner类:该类封装了SQL的执行,是线程安全的。可以实现增、删、改、查、批处理
- 使用QueryRunner类实现查询
- ResultSetHandler接口:该接口用于处理java.sql.ResultSet,将数据按要求转换为另一种形式
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的 JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的 JavaBean实例中,存放到List里
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler(name):将结果集中的每行数据都封装到Map里,再把这些map再存到一个map里,其key为指定的key
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
使用
- 前期配置:使用DBUtils 类和接口,需先引入DBUtils 相关的jar 包【此处使用的是commons-dbutils-1.7.jar】,并加入到项目中
- 使用
- 得到连接 (druid)
- 创建QueryRunner
- 执行相关的方法,返回arrayList 结果集
- 释放资源(放回连接)
使用案例代码
package com.xiaolu.jdbc.datasource;
import com.xiaolu.jdbc.utils.JDBCUtilsByDruid;
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 org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
/**
* @author 林小鹿
* @version 1.0
* 使用apache-DBUtils 工具类 + druid 完成对表的 crud操作
*/
public class DBUtils_USE {
// apache-dbutils + druid 完成 返回的结果是全部记录 --> 返回的结果为ArrayList
@Test
public void testQueryMany() throws SQLException {// 返回结果是多行的情况
// 1、得到连接 (druid)
Connection connection = JDBCUtilsByDruid.getConnection();
// 2、使用DBUtils 类和接口,需先引入DBUtils 相关的jar 包,并加入到项目中
// 3、创建QueryRunner
QueryRunner queryRunner = new QueryRunner();
// 4、执行相关的方法,返回arrayList 结果集
String sql = "select * from actor where id >= ?";
//(1)query 方法就是执行sql语句,得到resultSet ---> 封装到 --> ArrayList集合中
//(2)返回集合
//(3)connection:连接
//(4)sql:执行的sgL语句
//(5)new BeanListHandler<>(Actor.class):在将resultSet -> Actor对象 -> 封装到ArrayList
// 底层使用反射机制去获取Actor下类的属性,然后进行封装
//(6)此处的 2 就是给sql 语句中的? 赋值,因为是可变参数,可以有多个值
List<Actor> list =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 2);
System.out.println("遍历集合的信息");
for (Actor actor : list) {
System.out.print(actor);
}
//(7)释放资源(放回连接)
// 底层会得到resultSet,会在 query 中关闭 resultSet 和 PrepareStatement
JDBCUtilsByDruid.close(null, null, connection);
}
// apache-dbutils + druid 完成 返回的结果是单行记录 --> 返回的结果为 T【对应的domain类型】
@Test
public void testQuerySingle() throws SQLException {
Connection connection = JDBCUtilsByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
String sql = "select * from actor where id = ?";
//因为我们返回的单行记录<-->单个对象,使用的 Handler 是BeanHandler
Actor actor = queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 1);
System.out.println(actor);
// 释放资源
JDBCUtilsByDruid.close(null, null, connection);
}
// apache-dbutils + druid 完成 返回的结果是单行单列 --> 返回的结果为object
@Test
public void testScalar() throws SQLException {
Connection connection = JDBCUtilsByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
// 执行相关的方法,返回单行单列,返回的就是object
String sql = "select name from actor where id = ?";
Object obj = queryRunner.query(connection, sql, new ScalarHandler<>(), 1);
System.out.println(obj);
JDBCUtilsByDruid.close(null, null, connection);
}
// apache-dbutils + druid 完成 dml (update, insert, delete)
@Test
public void testDML() throws SQLException {
Connection connection = JDBCUtilsByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
String sql = "update actor set name = ? where id = ?";
//String sql = "insert into actor values(null, ?, ?, ?, ?)";
//String sql = "delete from actor where id = ?";
// 执行dml 语句的操作都是 queryRunner.update(),返回的值是受影响的行数
int affectedRow = queryRunner.update(connection, sql, "小米", 2);
System.out.println(affectedRow > 0 ? "影响行数:" + affectedRow : "未影响到表");
JDBCUtilsByDruid.close(null, null, connection);
}
}
DAO和增删改查通用方法-BasicDao
apache-dbutils+Druid简化了JDBC开发,但还有不足:
- SQL语句是固定,不能通过参数传入,通用性不好,需要进行改进,更方便执行 增删改查
- 对于select操作,如果有返回值,返回类型不能固定,需要使用泛型
- 将来的表很多,业务需求复杂,不可能只靠一个Java类完成
基本说明
- DAO:data access object 数据访问对象
- 这样的通用类,称为 BasicDao,是专门和数据库交互的,即完成对数据库(表)的crud操作。
- 在BaiscDao的基础上,实现一张表对应一个Dao,更好的完成功能,比如
Customer表 — Customer.java(javabean) — CustomerDao.java
实现步骤
-
设计
- com.xiaolu.dao_
- com.xiaolu.dao_.utils //工具类
- com.xiaolu.dao_.domain //javabean
- domain中的属性名一定要和表的列名保持一致
- 底层是通过MySQL查询得到的结果所在列来查询domain中的属性名,再根据列名调用相应的set方法来赋值
- com.xiaolu.dao_.dao //存放XxxDAO和BasicDAO
- com.xiaolu.dao_.test //写测试类
- com.xiaolu.dao_
-
BasicBAO代码
-
package com.xiaolu.dao_.dao; import com.xiaolu.dao_.utils.JDBCUtilsByDruid; 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; /** * @author 林小鹿 * @version 1.0 * 其他DAO的父类 */ public class BasicDAO<T> { // 泛型指定具体类型 private QueryRunner qr = new QueryRunner(); // 开发通用的dml方法,针对任意的表 public int update(String sql, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); int update = qr.update(connection, sql, parameters); return update; } catch (SQLException e) { throw new RuntimeException(e); // 将编译异常 -> 运行异常抛出 } finally { JDBCUtilsByDruid.close(null, null, connection); } } // 返回多个对象(即查询的结果是多行),针对任意表 /** * * @param sql sql 语句,可以有 ? * @param clazz 传入一个类的Class对象 比如 Actor.class * @param parameters 传入 ? 的具体的值,可以是多个 * @return */ public List<T> queryMulti(String sql, Class<T> clazz, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters); } catch (SQLException e) { throw new RuntimeException(e); } finally { JDBCUtilsByDruid.close(null, null, connection); } } // 查询单行结果的通用方法 public T querySingle(String sql, Class<T> clazz, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new BeanHandler<T>(clazz), parameters); } catch (SQLException e) { throw new RuntimeException(e); } finally { JDBCUtilsByDruid.close(null, null, connection); } } // 查询单行单列的方法,即返回单值的方法 public Object queryScalar(String sql, Object... parameters) { Connection connection = null; try { connection = JDBCUtilsByDruid.getConnection(); return qr.query(connection, sql, new ScalarHandler<>(), parameters); } catch (SQLException e) { throw new RuntimeException(e); } finally { JDBCUtilsByDruid.close(null, null, connection); } } }
-
-
ActorDAO
-
package com.xiaolu.dao_.dao; import com.xiaolu.dao_.domain.Actor; /** * @author 林小鹿 * @version 1.0 * 执行Actor表的操作 * 可以根据业务需求,编写特有的方法 */ public class ActorDAO extends BasicDAO<Actor>{ }
-
-
JDBCUtilsByDruid
-
package com.xiaolu.dao_.utils; 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; /** * @author 林小鹿 * @version 1.0 * 给予druid数据库连接池的工具类 */ public class JDBCUtilsByDruid { private static DataSource ds; // 在静态代码块完成 ds初始化 static { Properties properties = new Properties(); try { properties.load(new FileInputStream("src\\druid.properties")); ds = DruidDataSourceFactory.createDataSource(properties); } catch (Exception e) { e.printStackTrace(); } } // 编写getConnection方法 public static Connection getConnection() throws SQLException { return ds.getConnection(); } //在数据库连接池技术中,close不是真的断掉连接 //而是把使用的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); } } }
-
-
Actor
-
package com.xiaolu.dao_.domain; import java.util.Date; /** * @author 林小鹿 * @version 1.0 * Actor 对象和 actor 表的记录对应 */ public class Actor {// Javabean, POJO, Domain 对象 private Integer id; private String name; private String sex; private Date borndate; private String phone; public Actor() {// 一定要给个无参构造器【反射时需要】 } public Actor(Integer id, String name, String sex, Date borndate, String phone) { this.id = id; this.name = name; this.sex = sex; this.borndate = borndate; this.phone = phone; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public Date getBorndate() { return borndate; } public void setBorndate(Date borndate) { this.borndate = borndate; } public String getPhone() { return phone; } public void setPhone(String phone) { this.phone = phone; } @Override public String toString() { return "\nActor{" + "id=" + id + ", name='" + name + '\'' + ", sex='" + sex + '\'' + ", borndate=" + borndate + ", phone='" + phone + '\'' + '}'; } }
-
-
TestDAO
-
package com.xiaolu.dao_.test; import com.xiaolu.dao_.dao.ActorDAO; import com.xiaolu.dao_.domain.Actor; import org.junit.jupiter.api.Test; import java.util.List; /** * @author 林小鹿 * @version 1.0 * 测试 */ public class TestDAO { // 测试ActorDAO 对actor表crud操作 @Test public void testActorDAO() { ActorDAO actorDAO = new ActorDAO(); // 查询 List<Actor> actors = actorDAO.queryMulti("select * from actor where id >= ?", Actor.class, 2); for (Actor actor : actors) { System.out.print(actor); } // 查询单行记录 System.out.println("\n=====查询单行记录====="); Actor actor = actorDAO.querySingle("select * from actor where id = ?", Actor.class, 1); System.out.println(actor); // 查询单行单列 System.out.println("====查询单行单列===="); Object o = actorDAO.queryScalar("select name from actor where id = ?", 1); System.out.println(o); // dml操作 // int update = actorDAO.update("insert into actor values(null, ?, ?, ?, ?)", "张三", "男", "2000-11-22", "991"); int update = actorDAO.update("delete from actor where id = ?", 5); System.out.println(update > 0 ? "执行成功" : "未受影响"); } }
-