16 JDBC
约 6496 字大约 22 分钟
2024-09-01

JDBC:Java 提供的一套用于操作数据库的接口 API。它统一和规范了应用程序与数据库的连接、执行 SQL 语句,并到得到返回结果等各类操作,相关类和接口在
java.sql与javax.sql包中,Java 程序员只需要面向该接口即可连接任何提供了 JDBC 驱动程序的数据库,完成对数据库的各种操作。不同的数据库厂商,需要针对这套接口提供不同的实现。
JDBC 程序编写步骤(示范):
/* [0] 前置操作 */ /* [1] 注册驱动 */ Driver driver = new com.mysql.cj.jdbc.Driver(); /* [2] 获取连接 */ String url = "jdbc:mysql://localhost:3306/melody"; //melody是创建的一个数据库 Properties properties = new Properties(); //user和password的写法是固定的 properties.setProperty("user", "root"); properties.setProperty("password", "******"); Connection connect = driver.connect(url, properties); /* [3] 执行语句 */ String sql = "insert into customer (customer_id,name,card_id) values(0004,'赫尔','00000000000000000O')"; Statement statement = connect.createStatement(); int rows = statement.executeUpdate(sql); System.out.println(rows); /* [4] 释放资源 */ statement.close(); connect.close();
前置操作:将
mysql-connector-java-8.0.27.jar该文件拷贝到项目目录下,选中后右键选择Add as library注册驱动:加载
Driver类这里我导入了
com.mysql.cj.jdbc.Driver。根据前面的jar包的版本不同,应该导入的路径也会不同。旧版本的
jar包可能应该导入com.mysql.jdbc.Driver这个包。获取连接:得到
ConnectionString url = "jdbc:mysql://localhost:3306/melody";这里表示进入
melody这个数据库。执行语句:发送 SQL 命令给 MySQL 执行
int rows = statement.executeUpdate(sql);如果是 dml 语句,这里返回的 rows 是影响的行数。返回 0 表示失败。
释放资源:关闭相关连接(切记!)
23.1 连接数据库的 5 种方式
方式 1:直接创建
Driver,调用driver.connet(url, properties);这个方法就是 [23.0] 示范的方法
Driver driver = new com.mysql.cj.jdbc.Driver(); String url = "jdbc:mysql://localhost:3306/melody"; Properties properties = new Properties(); properties.setProperty("user", "root"); properties.setProperty("password", "******"); Connection connect = driver.connect(url, properties);方式 2:使用反射加载
Driver类这个方式是动态加载,更加灵活,减少依赖性。
Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver"); Driver driver = (Driver) aClass.newInstance(); Properties properties = new Properties(); properties.setProperty("user", "root"); properties.setProperty("password", "******"); Connection connect = driver.connect(url, properties);方式 3:使用
DriverManager替代Driver进行统一管理这个方式扩展性更好,更加清晰
Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver"); Driver driver = (Driver) aClass.newInstance(); String url = "jdbc:mysql://localhost:3306/melody"; String name = "root"; String password = "******"; DriverManager.registerDriver(driver); Connection connection = DriverManager.getConnection(url, name, password);这里,
DriverManager.getConnetion()有三种方法。分别是:getConnetion(url);getConnetion(url, properties);getConnetion(url, name, password);
方式 4:使用
Class.forName()自动完成注册驱动,实际开发中这种方式用的最多,推荐使用Class<?> aClass = Class.forName("com.mysql.cj.jdbc.Driver"); //可以不加引用 String url = "jdbc:mysql://localhost:3306/melody"; String name = "root"; String password = "******"; Connection connection = DriverManager.getConnection(url, name, password);Class.forName()在加载Driver类时,会自动完成注册。原理:
Driver类的源码中有这样一段static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } }这个静态代码块在类加载时会被执行。
另外,其实不执行
Class.forName(...);语句的场合,也能正确获取Connection。这是因为 JDK 1.5 以后使用了 jdbc4。这个场合,系统会自动调用 jar 包下
META-INF\services\java.sql.Driver这个文件中的类名称去注册。……打开上述文件看看,里面赫然写着:
com.mysql.cj.jdbc.Driver即使如此,还是建议写上
Class.forName(...)语句!方式 5(推荐):在方式 4 的基础上,使用配置文件,连接数据库更灵活。
这种方式是实际开发最常用的方式。
> 配置文件(创建 src\mysql.properties):
>
> ```properties
> url=jdbc:mysql://localhost:3306/melody
> user=root
> password=******
> driver=com.mysql.cj.jdbc.DriverPROPERTIES
> ```
>
> 代码:
>
> ```java
/*得到配置文件信息*/
> Properties pro = new Properties();
> pro.load(new FileInputStream("src\\mysql.properties"))
> String url = pro.getProperties("url");
> String user = pro.getProperties("user");
> String password = pro.getProperties("password");
> String driver = pro.getProperties("driver");
/*注册驱动*/
> Class<?> aClass = Class.forName(driver);
> Connection connection = DriverManager.getConnection(url, user, password);
> ```23.2 ReaultSet 结果集
ResultSet:表示数据库结果集的数据表。通常通过执行查询数据库的语句生成。
ResultSet 对象保持一个光标指向其当前的数据行。该光标的初始位置在第一行之前。调用 next 方法将光标下移,移动到末端的场合会返回 false。
如何取出数据(示例):
String sql = "select * from customer";
ResultSet resset = statement.executeQuery(sql);
while (resset.next()) {
int id = resset.getInt(1);
String name = resset.getString(2);
String sex = resset.getString(5);
String card_id = resset.getString(6);
System.out.println(id + "\t" + name + "\t" + sex + "\t" + card_id);
}其中 String sex = resset.getString(5); 表示取出该行数据的第 5 列的字符串数据。
ResultSet 的数据是以类似二维数组的形式保存在对象中。这不难理解,毕竟表格是二维的嘛。
23.3 Statement
Statment对象用于执行静态 SQL 语句,并返回其生成的结果的对象在连接建立后,需要访问数据库、执行命名或 SQL 语句,有如下方式:
Statment(存在 SQL 注入问题,在实际开发中基本不使用)PerparedStatement(预处理)CallableStatement(存储过程)案例:
SQL 注入:利用某些系统没有对用户输入的数据进行充分的检查,故意注入非法的 SQL 语句段或命令,恶意攻击数据库。
使用
PreparedStatement可以防范 SQL 注入
23.3.1 PreparedStatement 预处理
/* [1] */
String sql = "select * from customer where customer_id = ? and name = ?";
PreparedStatement ps = connection.prepareStatement(sql);
/* [2] */
ps.setInt(1, 0);
ps.setString(2, "萝茵");
/* [3] */
ResultSet rs = ps.executeQuery();PreparedStatement执行的 SQL 语句的参数用?表示。调用方法设置 SQL 语句中的参数。
这些方法的两个参数中,第一个是设置的参数的索引,第二个是设置值
调用
executeQuery()返回ResultSet对象。或者调用executeUpdate()执行增删改这里,调用的方法不需要再传入参数 sql 进去,而且如果添加了“?”作为条件,传入参数会导致语法错误。
预处理的好处
- 不再使用拼接语句,增加代码可读性,减少语法错误
- 解决了 SQL 注入问题
- 大大减少了编译次数,效率提高
| 类 / 接口 | 方法 |
|---|---|
| DriverManager 驱动管理类 | getConnection(url, user, pwd) 获取连接 |
| Connection 接口 | createStatement() 创建 Statement 对象 |
| preparedStatement(sql) 生成预处理 PreparedStatement 对象 | |
| Statement 接口 | executeUpdate(sql) 执行 dml 语句,返回影响行数 |
| executeQuery(sql) 执行查询语句,返回 ResultSet,注意不能对 PreparedStatement 或 CallableStatement 调用此方法。 | |
| execute(sql) 执行任意 SQL 语句,返回布尔值 | |
| PreparedStatement 接口 | executeUpdate() 执行 dml 语句,返回影响行数 |
| executeQuery() 执行查询语句,返回 ResultSet | |
| execute() 执行任意 SQL 语句,返回布尔值 | |
| setXXX(int, xxx) 设置 SQL 语句中占位符的值 | |
| setObject(int, xxx) 设置 SQL 语句中占位符的值 | |
| ResultSet 结果集 | next() 向下移动一行。没有下一行的场合返回 false |
| previous() 向上移动一行。没有上一行的场合返回 false | |
| getXXX(int)、getXXX(name) 返回 int 列 / name 列的值 | |
| getObject(int)、getObject(name) 返回 int 列 / name 列的值 |
23.4 JDBCUtils
实际操作中,由于获取连接 和 释放资源 操作经常使用,所以会增加代码的冗余度,可以把这些操作统一封装成工具类 JDBCUtils,这样当需要数据库连接进行数据库操作的时候只需要通过这个 JDBCUtils 就可以获取。

JDBCUtils.java
package com.hspedu.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; //驱动名
//在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) {
//在实际开发中,我们可以这样处理
//1. 将编译异常转成 运行异常
//2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e); //[1]
}
}
//连接数据库, 返回Connection
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
//1. 将编译异常转成 运行异常
//2. 调用者,可以选择捕获该异常,也可以选择默认处理该异常,比较方便.
throw new RuntimeException(e); //[1]
}
}
//关闭相关资源
/*
1. ResultSet 结果集
2. Statement 或者 PreparedStatement
3. Connection
4. 如果需要关闭资源,就传入对象,否则传入 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); //[1]
}
}
}- 实际开发中,可以把编译异常转换为运行异常。这样,调用者可以自行选择捕获异常还是默认处理。
23.5 事务
事务:JDBC 程序中,当一个
Connection对象创建时,默认情况下会自动提交事务。为了让多个 SQL 语句一体执行,需要使用事务。
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
connection.setAutoCommit(false); //[1] 取消自动提交事务
String sql = "insert into actor values(9, '赫尔萝茵')";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate();
sql = "delete from actor where name = '萝茵' and id > (select * from (select min(id) from actor where name = '萝茵') a)";
//如果在第一件语句执行成功,而在第二句前因为异常导致没有成功,就会导致数据发生错乱
int i = 1/0;//产生一个异常,导致后面一句执行不成功
//[2]
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate();
connection.commit(); //[3] 提交事务
System.out.println("complete");
} catch (SQLException e) {
System.out.println("fail");
System.out.println(e);
connection.rollback(); //[4] 回滚事务
} finally {
connection.close(); //[5] 关闭连接
}- 取消自动提交事务:
```
connection.setAutoCommit(false);
```这是一个 SQL 语句。表示在 name = 萝茵 的数据中只保留 id 最小的一条。
delete from actor where name = '萝茵' and id > (select * from (select min(id) from actor where name = '萝茵') a) MYSQL正常写法会提示不能同表查询(在 MySQL 中,不能在同一语句中先 select 出同一表中的某些值,再 update 这个表)。使用一个额外的 select 过渡就解决了这个问题。特此记录。
提交事务:
```
connection.commit();
```- 回滚事务:
```
connection.rollback();
```
写在 catch 里。这样,语句错误抛出异常的场合会执行到这句话。- 关闭连接:
```
connection.close(); //可以传参,参数为回滚点,不传参数默认回滚到开始的状态
```
写在 finally 里。这样,执行完语句总会关闭连接。这很好。23.6 批处理
批处理:把多条语句一次性提交给数据库进行批量处理。这样做比单独提交更有效率。
要使用批处理功能,需要在 url 中加入 ?rewriteBatchedStatements=true,即在配置文件中写成:
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
批处理往往和 PerparedStatement 搭配使用,既减少编译次数,又减少运行次数,岂不美哉?
示例:
//[0] 事前准备 Connection connection = JDBCUtils.getConnection(); String sql = "insert into test1 values(?)"; PreparedStatement ps = connection.prepareStatement(sql); for (int i = 0; i < 1000; i++) { ps.setInt(1,i); ps.addBatch(); //[1] } ps.executeBatch(); //[2] JDBCUtils.close(connection, ps, null);
- 修改配置文件
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true ...
- 添加需要批量处理的 SQL 语句
preparedStatement.addBatch();
- 执行批量处理语句
preparedStatement.executeBatch();
- (前面的例子里没有这句)清空批处理包的语句
preparedStatement.clearBatch();
23.6.1 说明
- 第一次添加批处理语句时,创建一个 ArrayList。
> ```
> ...
> if (this.batchedArgs == null) {
> this.batchedArgs = new ArrayList();
> }
> ...
> ```
……会在其中放置批处理语句(指令的数据)该数组满后,按照 1.5 倍扩容
达到指定的值后,执行 executeBatch 吧
批处理能减少编译次数,也能减少发送 SQL 语句的网络开销。
23.7 数据库连接池
传统获取 Connection 方法的问题:
- 传统的 JDBC 数据库连接使用 DriverManager 获取,每次建立连接都会把 Connection 载入内存,再进行身份验证。每次连接都会重复验证请求,这样会占用过多系统资源,容易造成服务器崩溃。
- 每次连接,使用完毕后必须断开。如果不断开操作,会致使数据库内存泄漏,最终不得不重启数据库。
- 传统连接方式不能控制创建的连接数量。连接数量过多的场合,也可能导致内存泄漏,MySQL 崩溃
因此,需要使用连接池技术:
- 预先在缓冲池放入一定数量的连接。需要建立数据库连接时,从缓冲池中取出一个连接。使用完后,把该连接放回缓冲池。
- 数据库连接池负责分配、管理和释放数据库连接。其允许应用程序重复使用一个现有的数据库连接,而非建立新的连接。
- 当请求连接的数量超过最大数量,这些连接请求会被加入等待队列。
数据库连接池种类:
JDBC 数据库连接池使用 javax.sql.DataSource 表示,称为数据源,DataSource 是一个接口,通常由第三方提供实现(提供 jar 包)
- C3P0:速度稍慢,稳定性好……但是速度再慢也不是传统 JDBC 能比得上的,不自量力!
- DBCP:速度稍快,稳定性差
- Proxool:可以监控连接池状态,稳定性稍差
- BoneCP:速度快
- Druid:阿里提供的数据库连接池,集 DBCP、C3P0、Proxool 优点于一身
23.7.1 C3P0
写在前面:C3P0 连接时跳一堆红字,不一定是报错……他就这样。
使用 C3P0 的前置工作:
注意 0.9.2 版本及之后要同时在 libs 文件夹导入 c3p0 和 c3p0-oracle 这两个 jar 包
……和前面一样,把 jar 包装载好。
为了正常运行程序,这里(上面连接下载的) jar 包(似乎)至少是:
mchange-commons-java-0.2.19.jar
c3p0-0.9.5.5.jar
csdn 上说 mchange-commons-java-0.2.19.jar 这是c3p0 数据库连接池的辅助包,如果没有这个包系统启动时会报 classnotfoundexception,这是更新 c3p0-0.9.2 版本后分离出来的包,0.9.1 的时候还是只是一个包。
方式一:在程序中指定相关参数
/* [1] */ ComboPooledDataSource cpds = new ComboPooledDataSource(); /* [2] */ cpds.setDriverClass(dirver); cpds.setUser(user); cpds.setPassword(password); cpds.serJdbcUrl(url); /* [3] */ cpds.setInitialPoolSize(10); /* [4] */ cpds.setMaxPoolSize(50); /* [5] */ Connection connection = cpds.getConnection(); ... connection.close();创建数据源对象(dataSource)
ComboPooledDataSource cpds = new ComboPooledDataSource();设置参数
cpds.setDriverClass(String dirver); cpds.setUser(String user); cpds.setPassword(String password); cpds.serJdbcUrl(String url);……上面输入的形参
user是字符串"root"。其他同理。设置初始化连接数
cpds.setInitialPoolSize(10);初始化连接数,就是指连接池创建时,初始持有的连接数
设置最大连接数
cpds.setMaxPoolSize(50);获取一个连接
Connection connection = cpds.getConnection();
方式二:使用配置文件模板完成
/* [0] 前置操作 */ /* [1] 获取数据源对象 */ ComboPooledDataSource cpds = new ComboPooledDataSource("Heruin"); /* [2] 获取连接 */ Connection connection = cpds.getConnection(); ... connection.close();加入配置文件
c3p0-config.xml:配置文件,文件名固定,放在
src/c3p0-config.xml这个位置
<c3p0-config> <!-- 数据源(连接池)名称 --> <named-config name="Heruin"> <!-- 驱动类 --> <property name="driverClass">com.mysql.cj.jdbc.Driver</property>//com.mysql.jdbc.Driver 是 mysql-connector-java 5中的, com.mysql.cj.jdbc.Driver 是 mysql-connector-java 6以及以上中的 <!-- url --> <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property> <!-- 用户名 --> <property name="user">root</property> <!-- 密码 --> <property name="password">******</property> <!-- 初始化连接数 --> <property name="initialPoolSize">10</property> <!-- 最大空闲时间 --> <property name="maxIdleTime">30</property> <!-- 最大连接数 --> <property name="maxPoolSize">100</property> <!-- 最小连接数 --> <property name="minPoolSize">10</property> <!-- 每次增长的连接数 --> <property name="acquireIncrement">10</property> <!-- 可连接的最多的命令对象数 --> <property name="maxStatements">10</property> <!-- 每个连接可连接的最多的命令对象数 --> <property name="maxStatementsPerConnection">5</property> </named-config> </c3p0-config>获取数据源对象
ComboPooledDataSource cpds = new ComboPooledDataSource(String name);这里的
name就是配置文件中的数据源名称获取连接
23.7.2 Druid(德鲁伊)连接池
有的人因为看见而相信,有的人因为相信而看见!
使用 Druid 的前置工作:
……这个链接打不开的话,试试 这个
配置 jar 包
加入配置文件
durid.properties:配置文件,文件名任意,放在
src/druid.propertiesdriverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true username=root password=720mel\im initialSize=10 minIdle=5 maxActive=20 maxWait=5000PROPERTIES
使用 Druid:
/* [1] 加载配置文件 */ Properties properties = new Properties(); properties.load(new FileInputStream("src\\Druid.properties")); /* [2] 初始化数据池 */ DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); /* [3] 获取连接 */ Connection connection = dataSource.getConnection(); ... connection.close();初始化数据池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);……没什么好说的,但要说一下这个:
connection.close();
Connection是一个接口,对于方法close(),不同供应商有不同实现方法。 原生的MySQL的实现方法是关闭连接,而这些连接池的实现方法是取消引用(放回连接池)。
23.8 Apache - DbUtils
commons-DbUtils:是 Apache (阿帕奇)组织提供的一个开源的 JDBC 工具库。它是对 JDBC 的封装。使用 dbutils 能极大简化 JDBC 编码的工作量DbUtils 的常用类:
QueryRunner类:封装了 SQL 的执行。是线程安全的,可以实现增、删、改、查、批处理操作ResultSetHandler接口:用于处理ResultSet,按照需求将数据转化为其他形式
JDBC 传统方法的不足:
- 结果集和连接是关联的。在连接关闭后,就不能使用结果集。
- 结果集只能使用一次,这样不便于数据管理
- 使用返回信息不方便
解决方案:
土方法(只是演示,实际还是用 Dbutils):创建一个类(这个类统称为 JavaBean/POJO/Domain/entity),其属性与表格的列一一对应。将数据遍历并读取到一个个类对象中,再将这些类对象放置到集合中。这样,就得到了一个与表格数据关联的数据集合。
拓展:为什么属性类型最好使用包装类型
数据类,根据需要创建:
+------+------------+------+ | id | date | name | +------+------------+------+ | 1 | 2003-11-20 | jack | +------+------------+------+ //对应上表 public class SQL_Data { private Integer id; //最好使用包装类 private Date date; private String name; /* 一定要有无参构造器。这是因为后面会用到反射 */ public SQL_Data(){} public SQL_Data(Integer id, Date date, String name) { this.id = id; this.date = date; this.name = name; } /* Geter & Seter 笔记里就省略不记了。光占地方又没技术含量 */ ... }……然后遍历 ResultSet,存放。就不写了。
- 使用
DbUtils:
/* [0] 前置操作 */ String sql = "select * from account where ? = ?"; /* [1] 创建 QueryRunner */ QueryRunner qr = new QueryRunner(); /* [2] 执行相关方法 */ List<SQL_Data> list = qr.query(connection, sql, new BeanListHandler<>(SQL_Data.class), "id", 1); ... /* [3] 释放资源*/ connection.close();前置操作:
获取 jar 包,并完成配置
得到连接
Connection connection,创建数据类(这里是SQL_Data)执行相关方法,返回结果集
List<SQL_Data> list = queryRunner.query(connection, sql, new BeanListHandler<>(SQL_Data.class), ...);queryRunner.query方法:执行 SQL 语句,把得到的ResultSet封装到 List 集合。这个方法会自动关闭获得的ResultSet,所以不会造成资源泄漏connection:前置操作中得到的连接sql:SQL 语句new BeanListHander<>(SQL_Data.class):利用反射机制,将数据封装到SQL_Data对象中"id", 1:给 SQL 语句中的?赋值。因为是可变参数,可以写多个,如:queryRunner.query(connection, sql, new BeanListHandler<>(SQL_Data.class), "id", 1, "name", "识之律者");SQL_Data:是数据库当中的表映射的 javabean。这种语法或者本身就要避免,是不合法的。
此时只需要关闭
Connectionquery()方法已经关闭了ResultSet和PreparedStatement
- 使用
23.8.1 Apache-DbUtils 常用方法
queryRunner.update(connection, sql, ...):DML 语句。增删改。后面传入可变参数,用于给 SQL 语句中的
?赋值返回值 int 代表被影响的行数。
queryRunner.query(connection, sql, ResultSetHandler, ...):查询。后面传入可变参数,用于给 SQL 语句中的
?赋值关于
ResultSetHandler:- 传入
new BeanListHander<>(SQL_Data.class):返回多行多列。这个场合,用List接收返回值。 - 传入
new BeanHandler<>(SQL_Data.class):返回单行。这个场合,用SQL_Data data接收返回值。SQL_Data data表中的某一行数据映射的对象。
- 传入
new ScalarHander<>():返回单行单列。这个场合,用Object接收返回值。注意没有参数
- 传入
23.8.2 表和 JavaBean/POJO 的对应关系
| 表类型 | JavaBean 类型 |
|---|---|
| int、samllint…… | Integer |
| char、varchar…… | String |
| double | Double |
| date | java.util.Date/String |
24. BasicDAO
项目设计的模式:
具体各个层的作用参考:什么是 Dao 层、Entity 层、Service 层、Servlet 层、Utils 层?-CSDN 博客

实际写代码是先完成下层的,再完成上层的。
一般开发中关于 dao 里的方法是否应该是 static 的,stackoverflow 上有讨论,搜关键词 dao method static 就行,主要完成的是 service 层,从下往上分析。,映射的 javabean 应该有无参构造器,,因为底层反射是使用无参构造器构造 javabean 对象的。而且属性名要以数据库为准,底层是用 setName()来初始化属性的(会根据列名对应方法,比如列名为 name,如果 javabean 中写的是 name2,他就找不到应该为 setName()的方法(此时 javabean 中的是 setName2()方法,它匹配不到),这种场合就需要在 sql 语句起别名,将列名置为 name2,这样就可以找到 setName2()了),,前提是每个 sql 语句都取别名
数据库会根据自己的字段找到对应实体类的属性,通过 set 方法对属性进行注入。 如果实体类的属性名 name2 和数据库的字段名 name 不相同,则没有通过 set 方法进行注入,就会将 name2 置为 null。
Apache - DbUtils + Druid 简化了 JDBC 开发,但还有不足。
- SQL 语句固定,不能通过传入参数控制,通用性不好。
- select 操作的返回类型未知,需要使用泛型。
- 将来的表很多,业务需求复杂,不可能只靠一个 Java 类完成
DAO:数据访问对象(data access object)
我们把通用的和数据库交互的操作封装到一个通用类中,称为 BasicDAO。
在 BasicDAO 基础上,每张表对应一个特化的 DAO(继承 BasicDAO),从而更好地完成功能。
比如:Data 表 -> Data.java 类(JavaBean)-> DataDAO.java
BasicDAO.java(示例)
public class BasicDAO<T> {
private QueryRunner qr = new QueryRunner();
/*这个方法完成插入和修改的操作,在底层是使用executeUpdate执行sql语句*/
//可以利用这个方法检查是否插入成功,返回值<0则表明插入失败,返回值>0表明插入成功
public int update(String sql, Object... parameters){
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
return qr.update(connection, sql, parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtils.close(connection, null, null);
}
}
/*因为返回的是list集合 要用到反射,所以用到class对象作为参数*/
public List<T> queryMulti(String sql, Class<T> tClass, Object... parameters){
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
return qr.query(connection, sql, new BeanListHandler<T>(tClass), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtils.close(connection, null, null);
}
}
public T querySingle(String sql, Class<T> tClass, Object... parameters){
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
return qr.query(connection, sql, new BeanHandler<T>(tClass), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtils.close(connection, null, null);
}
}
/*注意这里查询单个数据不需要用到class*/
public Object queryScalar(String sql, Object... parameters){
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
return qr.query(connection, sql, new ScalarHandler<>(), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtils.close(connection, null, null);
}
}
}DBUtils 数据库连接池多表连接查询
方案一:
老韩的思路:单独写一个 javabean 映射多表查询的结果,它包含其他 javabean 的属性(将这些属性重复组合放在这个单独的 javabean 中,但不是将其他 javabean 作为它的属性)。它也有自己的 Dao,也可以有 service,具体实现根据业务完成。 缺点:每次增加业务时,就要在这个 javabean 添加相应的字段。
随着业务增加,字段越来越多怎么办?
解决方案:

方案二:
MapListHandler 的使用(具体使用方式查看官方文档http://commons.apache.org/proper/commons-dbutils/apidocs/index.html) key——sql 语句查询的字段 value——数据库中查询得到的结果 出现的问题描述 对于多表连接查询,如果使用BeanListHandler,则会出现空指针异常。 例如 employee 和 department 分类封装为 Javabean。employee 中声明一个 department 的对象。如下图所示:

解决方案 使用 MapListHandler 最终返回 List<map<String,Object>>,使用迭代的方式通过查询字段取出 value,首先根据 Department 类中的 key 从 map 中取出对应的 value,并将其封装为 Department 对象。最终再封装为 Employee 对象,这样就解决了使用 BeanListHandler 出现空指针异常的现象。 具体实现代码如下图

ResultSetHandler 实现类介绍(由 DbUtils 框架提供)
备注:DbUtils给我们提供了10个ResultSetHandler实现类,分别是:
①ArrayHandler: 将查询结果的第一行数据,保存到Object数组中
②ArrayListHandler 将查询的结果,每一行先封装到Object数组中,然后将数据存入List集合
③BeanHandler 将查询结果的第一行数据,封装到user对象
④BeanListHandler 将查询结果的每一行封装到user对象,然后再存入List集合
⑤ColumnListHandler 将查询结果的指定列的数据封装到List集合中
⑥MapHandler 将查询结果的第一行数据封装到map结合(key==列名,value==列值)
⑦MapListHandler 将查询结果的每一行封装到map集合(key==列名,value==列值),再将map集合存入List集合
⑧BeanMapHandler 将查询结果的每一行数据,封装到User对象,再存入mao集合中(key==列名,value==列值)
⑨KeyedHandler 将查询的结果的每一行数据,封装到map1(key==列名,value==列值 ),然后将map1集合(有多个)存入map2集合(只有一个)
⑩ScalarHandler 封装类似count、avg、max、min、sum......函数的执行结果更新日志
fde06-docs: 添加 doc 技术文档于

