简介
jdbc就是java标准库定义的一组接口,而不同数据库的jdbc驱动包就是接口的实现。
- Java程序编译期仅依赖java.sql包,不依赖具体数据库的jar包;(因此jdbc驱动包是运行时需要)
- 改为空间为编译时虽然不报错,但是会多出来一大堆类似com.mysql.jdbc.Connection这样的类,非常容易与Java标准库的JDBC接口混淆
第一个JDBC
依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
<scope>runtime</scope>
</dependency>
获取连接
// JDBC连接的URL, 不同数据库有不同的格式。URL是由数据库厂商指定的格式 String JDBC_URL = "jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=utf8"; String JDBC_USER = "root"; String JDBC_PASSWORD = "password"; //DriverManager会自动扫描classpath,找到所有的JDBC驱动,然后根据我们传入的URL自动挑选一个合适的驱动。 Connection conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD);
获取Statement执行sql并获取结果
- 改为空间为编译时虽然不报错,但是会多出来一大堆类似com.mysql.jdbc.Connection这样的类,非常容易与Java标准库的JDBC接口混淆
- Java程序编译期仅依赖java.sql包,不依赖具体数据库的jar包;(因此jdbc驱动包是运行时需要)
Statement
对象,该对象用于执行sql相关操作。PreparedStatement
可以防止sql注入,它执行一个不包含参数的sql,对sql进行预编译,再把参数传到编译好的sql中。被预编译的sql不会再变化- 注意不要把拼接参数得到的sql传给
PreparedStatement
,这样不能起到防止sql注入 - 我们应当尽可能使用
**PreparedStatement**
而不是**Statement**
- 注意不要把拼接参数得到的sql传给
对于查询操作,调用
**executeQuery**
执行sql,返回的是类似**List<List<Object>>**
的结果集**ResultSet**
。对于增删改,调用**executeUpdate**
,返回的是**int**
try (Statement stmt = conn.createStatement()) { try (ResultSet rs = stmt.executeQuery("SELECT id, grade, name, gender FROM students WHERE gender=1")) { //rs.next()用于判断是否有下一行记录,一开始获得ResultSet时当前行不是第一行 //循环行记录 while (rs.next()) { //循环列记录,注意输出的字段顺序跟获取的类型要对应。另外列索引从1开始 long id = rs.getLong(1); long grade = rs.getLong(2); String name = rs.getString(3); int gender = rs.getInt(4); } } } }
PreparedStatement ps = conn.prepareStatement("SELECT id, grade, name, gender FROM students WHERE gender=? AND grade=?") ps.setObject(1, "M"); // 注意:索引从1开始 ps.setObject(2, 3); ResultSet rs = ps.executeQuery()
关闭资源
Connection``Statment``ResultSet
都是需要关闭的资源,可以使用try(resource)
自动关闭JDBC事务
我们首先需要关闭自动提交,执行多条命令后手动提交即是jdbc事务 ```java Connection conn =…; //该方法可以知道当前是否开启了自动提交 boolean isAutoCommit = conn.getAutoCommit(); //可以设置事务隔离级别,不设置则采用数据库默认的事务级别 conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); conn.setAutoCommit(false); // 关闭自动提交事务 PreparedStatement ps =…; int n=ps.executeUpdate(); if(n>1){ conn.rollback(); //回滚 } else { conn.commit(); //提交 } conn.setAutoCommit(true); //恢复自动提交
<a name="OoS8o"></a>
## JDBC Batch
- 批量操作的本质在于:**SQL数据库对SQL语句相同,但只有参数不同的若干语句可以作为batch执行,即批量执行,这种操作有特别优化,速度远远快于循环执行每个SQL。**
- **即Batch本质基于是数据库提供的优化**
```java
try (PreparedStatement ps = conn.prepareStatement("INSERT INTO students (name, gender, grade, score) VALUES (?, ?, ?, ?)")) {
// 对同一个PreparedStatement反复设置参数并调用addBatch():
for (Student s : students) {
ps.setString(1, s.name);
ps.setBoolean(2, s.gender);
ps.setInt(3, s.grade);
ps.setInt(4, s.score);
ps.addBatch(); // 添加到batch
}
// 执行batch:
int[] ns = ps.executeBatch();
for (int n : ns) {
System.out.println(n + " inserted."); // batch中每个SQL执行的结果数量
}
}
连接池
创建线程是一个昂贵的操作,如果有大量的小任务需要执行,并且频繁地创建和销毁线程,实际上会消耗大量的系统资源,往往创建和消耗线程所耗费的时间比执行任务的时间还长,所以,为了提高效率,可以用线程池
类似的,在执行JDBC的增删改查的操作时,如果每一次操作都来一次打开连接,操作,关闭连接,那么创建和销毁JDBC连接的开销就太大了。为了避免频繁地创建和销毁JDBC连接,我们可以通过连接池(Connection Pool)复用已经创建好的连接。
- JDBC连接池有一个标准的接口javax.sql.DataSource,注意这个类位于Java标准库中,但仅仅是接口。要使用JDBC连接池,我们必须选择一个JDBC连接池的实现。
datasource
可以看成是数据源,它封装了数据连接的各种信息和连接流。
- 数据库连接池必须实现
**getConnection**
方法
常用的JDBC连接池有:
- HikariCP
- C3P0
- BoneCP
-
HikariCP
-
导入依赖
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.19</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>2.7.1</version> </dependency>
创建连接池并获取Connection
- 创建HikariConfig对象并设置数据库连接池配置信息
- 通过HikariConfig对象创建
DataSource
对象。该对象即为数据库连接池 Connection conn = createds().getConnection()
以获取Connection
实例- 一开始,连接池内部并没有连接,所以,第一次调用ds.getConnection(),会迫使连接池内部先创建一个Connection,再返回给客户端使用。当我们调用conn.close()方法时(在try(resource){…}结束处),不是真正“关闭”连接,而是释放到连接池中,以便下次获取连接时能直接返回。
- 因此,连接池内部维护了若干个Connection实例,如果调用ds.getConnection(),就选择一个空闲连接,并标记它为“正在使用”然后返回,如果对Connection调用close(),那么就把连接再次标记为“空闲”从而等待下次调用。这样一来,我们就通过连接池维护了少量连接,但可以频繁地执行大量的SQL语句。 ```java import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import javax.sql.DataSource; import java.sql.*;
class BookMapper{ public static DataSource createds() throws SQLException { HikariConfig config = new HikariConfig(); config.setJdbcUrl(“jdbc:mysql://localhost:3306/bookManagement”); config.setUsername(“root”); config.setPassword(“root”); config.addDataSourceProperty(“connectionTimeout”, “1000”); // 连接超时:1秒 config.addDataSourceProperty(“idleTimeout”, “60000”); // 空闲超时:60秒 config.addDataSourceProperty(“maximumPoolSize”, “10”); // 最大连接数:10 DataSource ds = new HikariDataSource(config); return ds; }
public static void main(String[] args) {
try (Connection conn = createds().getConnection()) { // 在此获取连接
Statement stmt=conn.createStatement();
}
catch (SQLException throwables) {
throwables.printStackTrace();
}
}
} ```