开始

JDBC 是 java 操作数据库的一组 API

JDBC 只是 java 操作数据库的接口,具体的实现由数据库厂商实现各自的驱动

数据库连接

使用数据库首先要与数据库进行连接,JDBC 有多种方式可以连接到数据库

Driver

通过 java 的 Driver 抽象类连接,使用具体数据库驱动的 driver 对象实例化

传入 url,properties,调用 connect 返回连接对象

url 格式:jdbc:mysql://ip:3306/database_name

properties 对象调用 setProperties(),传入账号密码的键值对

反射连接

使用反射将连接过程封装

1
2
Class<?> cl = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) cl.getDeclaredConstructor().newInstance();

DriverManager

DriverManager 可以管理多个 Driver 对象,可以通过 DriverManager 获取连接

1
2
DriverManager.registerDriver(driver);
Connection connection = DriverManager.getConnection(url, properties);

类加载

driver 对象在类加载时,会自动注册

1
2
Class.forname("com.mysql.cj.jdbc.Driver"); // 加载类
Connection connection = DriverManager.getConnection(url, properties); // 直接获取连接

操作数据库

jdbc 中有三个接口定义了数据库的调用

  • Statement:执行静态的 SQL 语句,容易产生 SQL 注入问题
  • PreparedStatement:将 SQL 语句预编译后存储在对象中,可用于多次高效地调用
  • CallableStatement:用于执行 SQL 存储过程

ORM 思想

  • 一张表对应一个 Java 类
  • 一条记录对应一个 Java 对象
  • 一个字段对应一个 Java 对象的属性

PreparedStatement

connection.preparedStatement(sql) 获取 preparedStatement 对象

传入的 sql 语句支持动态参数,使用?占位

调用 preparedStatement.setXXX(index, value) 设置动态参数,index 从 1 开始,再调用 execute() 执行

在 sql 语句中若表名为关键字,则使用`` 着重符将表名括起来

查询结果操作

调用 executeQuery() 返回一个 ResultSet 对象

处理结果集

  • resultSet.next():判断下一条是否有数据
  • resultSet.getXXX(index):获取字段
  • resultSet.getMetaData():返回结果集的元数据,可以获取列数、列名

对于一个任意字段数量的对象的查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public static Book queryBook(String sql, Object... args) {
Book book = null;
try {
// 设置查询
PreparedStatement statement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
statement.setObject(i + 1, args[i]);
}

// 查询并处理结果集
ResultSet resultSet = statement.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int col = metaData.getColumnCount();
if (resultSet.next()) {
book = new Book(); // 不知道book的字段
for (int i = 0; i < col; i++) {
Object value = resultSet.getObject(i + 1);

// 数据库列名与book字段名相同
// 若不相同,在查询的SQL语句中使用as给查询的字段取别名
// 在获取列名时,调用getColumnLabel()返回别名
// String label = metaData.getColumnName(i + 1);
String label = metaData.getColumnLabel(i + 1);
Field field = Book.class.getDeclaredField(label);
field.setAccessible(true);
field.set(book, value);
}

}
} catch (SQLException | NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return book;
}

操作 BLOB 类型字段

BLOB 类型为二进制长文本数据

mysql 支持四种容量的 BLOB

  • TinyBlob:255B
  • Blob:65KB
  • MediumBlob:16MB
  • LongBlob:4GB

Blob 类型的参数需要传入一个 InputStream 对象,即需要存入的图片的输入流

读取 Blob 类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
String type = metaData.getColumnTypeName(i + 1);
// 在通用类型中判断Blob类型
if ("BLOB".equals(type) ||
"MEDIUMBLOB".equals(type) ||
"TINYBLOB".equals(type)) {

// 构造文件名并向对象的filename字段写入文件名
int id = resultSet.getInt("id");
String filename = "p" + id + ".jpg";
Field field = cl.getDeclaredField("filename");
field.setAccessible(true);
field.set(t, filename);

// 读取文件,下载到本地
Blob value = resultSet.getBlob(i + 1);
InputStream inputStream = value.getBinaryStream();

// 使用Lombok注解@Cleanup
try (FileOutputStream outputStream = new FileOutputStream(filename)) {
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
}
}

批量插入

mysql 默认不支持批处理,需要在 URL 中传入参数开启批处理

1
jdbc:mysql://localhost:3306/db?rewriteBatchedStatements=true

批处理相当于构造多条语句并执行

操作流程

  • 在构造完一条语句后 (设置完动态参数后),调用 preparedStatement.addBatch() 将该语句存入一批中
  • 调用 preparedStatement.executeBatch() 执行当前批
  • 调用 preparedStatement.clearBatch() 清空当前批
  • 在循环中可以控制批数
  • 在数据库层面可以关闭自动提交,构成一层缓存,调用 connection.setAutoCommit(false)
  • 在语句全部执行完后,调用 connection.commit() 提交到数据库

事务

基本概念

事务是数据库的一组操作,具有 ACID 属性

  • 原子性:事务要么全部操作成功,要么全部失败
  • 一致性:事务提交前后,数据库的状态必须保持一致
  • 隔离性:多个事务并发时,事务操作之间互不干扰
  • 持久性:一旦事务提交,事务造成的数据库变化是持久的

数据库自动提交

  • DDL:对表结构的增删改,一定会提交
  • DML:对记录的增删改,默认自动提交,可设置 autocommit = false 取消自动提交
  • 默认在关闭连接时自动提交

设置事务

  1. 取消自动提交,connection.setAutoCommit(false);
  2. 在捕获异常中调用 connection.rollback(); 回滚
  3. 在所有操作执行成功后调用 connection.commit(); 提交
  4. 在操作结束后,若连接没有关闭则应该重新设置自动提交 connection.setAutoCommit(true);

基本流程

1
2
3
4
5
6
7
connection.setAutoCommit(false);
try {
executeStatements;
connection.commit();
} catch (Exception e) {
connection.rollback();
}

数据库并发

设两个事务 T1,T2

  • 脏读:T1 读取了 T2 已经更新但未提交的数据,此时 T2 回滚,T1 读取的数据无效 (脏数据)
  • 不可重复读:T1 读取了一个字段,此时 T2 更新了该字段,T1 再读取该字段,前后读取数据不一致
  • 幻读:T1 读取一个表,此时 T2 向该表插入了记录,T1 再读取该表,结果多了几条记录

隔离级别

  • READ UNCOMMITTED:允许读取未被其他事物提交的变更,安全性最低
  • READ COMMITTED:只允许事物读取其他事物已经提交的变更
  • REPEATABLE READ:在事务执行期间,禁止其他事务对当前事务读取的字段进行更新
  • SERIALIZABLE:类似同步,性能最低

相关 API

  • 调用 connection.getTransactionIsolation() 获取隔离级别
  • 调用 connection.setTransactionIsolation() 设置隔离级别

DAO

DAO 是 Data Access Object,数据库操作对象,里面封装了操作数据库的方法

DAO 操作规范

  • 定义抽象父类 BaseDAO,实现一个通用的 DAO 对象
  • 对于具体的表,定义相应的 DAO 接口,指明需要实现的方法
  • 实现相应的 DAO 接口并继承 BaseDAO,将对象转换到 SQL 并调用 BaseDAO

中间的实现类 DAO 层将对象与 SQL 语句隔离开,调用方只考虑操作对象,BaseDAO 只考虑操作 SQL

数据库连接池

为所有数据库连接建立一个缓冲池,需要建立连接时,从连接池中取出连接使用,允许程序重复使用连接,降低建立连接开销

Druid 连接池

配置文件 jdbc.properties

1
2
3
4
5
6
url=jdbc:mysql://localhost:3306/maxdb?rewriteBatchedStatements=true&useUnicode=true&characterEncoding=utf8
username=root
password=020411
driverClassName=com.mysql.cj.jdbc.Driver

initialSize=5

在 JDBCUtils 中创建连接池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static DataSource source;

static {
Properties properties = new Properties();
try {
@Cleanup
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
properties.load(is);
source = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}

Connection connection = source.getConnection();

Apache-DBUtils

Apache 封装的数据库操作库

###QueryRunner

QueryRunner 中一共有 6 种方法

  • execute(执行 SQL 语句)
  • batch(批量处理语句)
  • insert(执行 INSERT 语句)
  • insertBatch(批量处理 INSERT 语句)
  • query(SQL 中 SELECT 语句)
  • update(SQL 中 INSERT,UPDATE 或 DELETE 语句)

ResultSetHandler

ResultSetHandler 是处理查询结果的处理器

  • BeanHandler:将一条结果封装为对象,若查询结果有多条,则返回第一条记录的对象
  • BeanListHandler:将多条结果封装为对象列表
  • ArrayHandler:将一条结果的字段作为数组元素,一条结果作为一个数组返回
  • ArrayListHandler:将多条结果封装为数组列表
  • MapHandler:将一条结果的标签作为 key,字段值作为 value,返回一条结果的构成的 map
  • MapListHandler:返回多条结果的 map 列表
  • ScalerHandler:用于封装特殊值,如 count,max

关闭资源

1
2
3
4
5
6
7
8
9
10
11
12
13
DBUtils.close(connection);
// 等效于
if (connection != null) {
connection.close();
}

DBUtils.closeQuietly(connection);
// 等效于
try {
DBUtils.close(connection);
} catch(SQLException e) {
// quiet
}