JDBC Basics

什么是JDBC

  • Java Database Connectivity
  • 让Java程序操作关系型数据库
  • JDBC基于驱动程序实现与数据库的连接与操作
  • 驱动程序实现了JDBC API,所以Java程序可以调用JABC API(也就是调用底层的MySQL驱动程序的实现)来对数据库进行访问

JDBC的优点

  • 统一的API,提供一指的开发过程
  • 易于学习,容易上手,代码结构稳定
  • 功能强大,执行效率高,可处理大量数据

JDBC开发流程

  1. 加载并注册JDBC驱动
  2. 创建数据库链接
  3. 创建Statement对象
  4. 遍历查询结果
  5. 关闭链接,释放资源

Class.forName的作用

  • 用于加载指定的JDBC驱动类
  • 本质是通知JDBC注册这个驱动类

DriverManager

  • 用于注册,管理JDBC驱动程序
  • DriverManager.getConnection()
  • 返回值Connection对象,对应数据库的物理网络连接

Connection对象

  • Connection对象用于JDBC与数据库的网络通信对象
  • java.sql.Connection是一个接口,具体实现由驱动厂商负责
  • 所有数据库的操作都建立在Connection基础上

链接字符串

  • jdbc:mysql://localhost:port_number/database_name
  • 之后可以添加额外参数
  • 参数列表采用URL格式:name=value&name2=value2&…

MySQL连接字符串常用参数

参数名 建议参数值 说明
useSSL true/false 是否禁用SSL
useUnicode true 启用unicode编码传输数据
characterEncoding UTF-8 使用UTF-8编码
serverTimezone Australia/Sydney timezone
allowPublicKeyRetrieval true 允许从客户端获取公钥加密传输
  • 如果数据库的设置已经是使用澳洲时区,那么连接字符串的参数可以省略

SQL注入攻击

  • '单引号没有被特殊处理,被当成SQL query的一部分
  • 利用SQL漏洞越权获取数据的黑客行为
  • 根源是未对原始SQL中的敏感字符做特殊处理 (转译escape)
  • 解决方法:放弃Statement改用PreparedStatement处理SQL

PrepareStatement

  • 是Statement的子接口
  • 对SQL进行参数化,预防SQL注入攻击
  • 比Statement执行效率更好

错误的使用方式

  • select * from employee where ? = 'abc';
  • select * from employee where salary = ? + 100;
  • select * from employee where ename = ?;

封装数据库连接

  • 每次对数据库操作时都需要打开和关闭,我们可以将这个重复的步骤封装到一个Class中,需要时直接调用
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
35
36
37
38
39
40
41
/**
* 封装数据库链接
*/
public class DbUtils {
/**
* 创建新的数据库链接
* @return 新的Connection对象
* @throws SQLException
* @throws ClassNotFoundException
*/
public static Connection getConnection() throws SQLException, ClassNotFoundException {
//1. 加载并注册JDBC驱动
Class.forName("com.mysql.cj.jdbc.Driver");

//2. 创建数据库链接
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Australia/Sydney",
"yuan",
"1111"
);

return conn;
}

/**
* 关闭链接,释放资源
* @param rs 结果集对象
* @param statement Statement对象
* @param conn Connection对象
*/
public static void closeConnection(ResultSet rs, Statement statement, Connection conn) {
// 5. 关闭链接,释放资源
try {
if (rs != null) conn.close();
if (statement != null) statement.close();
if (conn != null && !conn.isClosed()) conn.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

JDBC执行INSERT, UPDATE, DELETE语句

  • statement.executeUpdate()
  • 返回受影响得行数

JDBC事务管理

  • 事务依赖于数据库的实现,MySQL通过事务区作为数据缓冲地带
  • 全部成功或者全部失败

自动提交事务模式

  • 没执行一次写操作SQL,自动提交事务
  • conn.setAutoCommit(true)
  • 默认的事务模式
  • 无法保证多数据一致性

手动提交事务模式

  • 显示调用commit()rollback()方法管理事务
  • conn.setAutoCommit(false)
  • 可保证数据一致性,但必须手动调用提交、回滚方法

JDBC中Date日期对象的处理

  • JDBC获取日期使用java.sql.Date,它继承自java.util.Date
  • 所以当获取MySQL中的日期时,两者互相兼容

Date日期的提取

  • 因为sql.Date继承自util.Date所以可以直接提取

Date日期的插入

  • 需要先将用户输入String转换成java.util.Date
    • String -> java.util.Date
  • 再将java.util.Date转换成java.sql.Date
    • java.util.Date -> java.sql.Date
1
2
3
4
5
6
7
8
9
10
11
12
// 1.string -> java.util.Date
Date udHireDate = null;
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
try {
udHireDate = sdf.parse(strHireDate);
} catch (ParseException e) {
e.printStackTrace();
}

// 2. java.util.Date -> java.sql.Date
long time = udHireDate.getTime(); // 获取自1970年到现在的毫秒数
java.sql.Date sdHireDate = new java.sql.Date(time);

JDBC批处理

1
2
3
4
5
6
7
8
9
10
11
String sql = "insert into employee (eno, ename, salary, dname) values (?, ?, ?, ?);";
statement = conn.prepareStatement(sql);

for (int i = 200000; i < 300000; i++) {
statement.setInt(1, i);
statement.setString(2, "员工" + i);
statement.setFloat(3, 4000);
statement.setString(4, "市场部");
statement.addBatch(); // add params to batch job, not execute yet
}
statement.executeBatch(); // execute batch job

连接池与JDBC进阶

  • 建立数据库链接比较费时间
  • 当我们知道应用的大概人数后,可以在应用启动时,提前创建好大概的连接并存放到连接池中
  • 当有用户需要连接数据库时,直接从连接池中获取一个可用的连接,用完之后再放入到连接池中以供其他用户使用
  • 管理,分配,连接
  • 程序只负责取用和归还

Druid连接池

  • Druid是阿里巴巴开源连接池组件

  • 连接池配置文件
1
2
3
4
5
6
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/imooc?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Australia/Sydney
username=yuan
password=1111
initialSize=10 // 初始化时创建新链接的数量
maxActive=20 // 连接池中最多的连接数量
  • 连接的步骤
  1. 加载属性文件
1
2
3
4
5
6
7
8
Properties properties = new Properties();
String propertyFilePath = DruidSample.class.getResource("/druid-config.properties").getPath();
// URL Space will be represented as %20
// so we need to decode it
propertyFilePath = new URLDecoder().decode(propertyFilePath, "UTF-8");

// load propertyFile to properties object
properties.load(new FileInputStream(propertyFilePath));
  1. 获取dataSource数据源对象,指代数据库
1
2
dataSource = DruidDataSourceFactory.createDataSource(properties);
Connection conn = dataSource.getConnection();
  • 不使用连接池: conn.close()的作用是关闭连接
  • 使用连接池: conn.close()的作用是将连接回收到连接池
  • 当用户连接数量超过连接池设置的最大数量时,新的用户将会等待连接被其他用户关闭
  • Tips:可以将初始连接数量和最大连接数保持一致

扩展知识:C3P0连接池

  • C3P0连接池
  • C3P0会自动找到名为c3p0-config.xml的配置文件
  • DataSource dataSource = new ComboPooledDataSource();

Apache Commons DBUtils

  • Apache提供的JDBC工具类库
  • 它是对JDBC的简单封装,学习成本低
  • 简化JDBC编码工作量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Properties properties = new Properties();
String propertyFilePath = DBUtilsSample.class.getResource("/druid-config.properties").getPath();

propertyFilePath = new URLDecoder().decode(propertyFilePath, "UTF-8");
properties.load(new FileInputStream(propertyFilePath));
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

// Commons DBUtils会自动帮助我们创建并关闭连接
QueryRunner qr = new QueryRunner(dataSource);
// convert ResultSet into list of employees
List<Employee> list = qr.query("select * from employee limit ?, 10;",
new BeanListHandler<>(Employee.class),
new Object[]{10} // pass in the params in the SQL query
);

for (Employee emp : list) {
System.out.println(emp.getEname());
}