文章目录
JBDC
单元测试
在运行方法时,如果没有出现异常,那么测试通过,否则测试不通过
对单元测试方法的限制:
- 返回值类型必须是void
- 方法的参数为空
- 方法必须是public
1.概述
- JDBC (Java Data Base Connectivity) 是一种用于执行 SQL 语句的 Java API。JDBC 是 Java 访问数据库的标准规范,可以为不同的关系型数据库提供统一访问。它由一组用 Java 语言编写的接口和类组成,位于 java.sql 和 javax.sql 包下面。
- JDBC 需要连接驱动。客户端要和 DBMS 进行通信,需要满足一定的通信数据格式 (协议),协议是由数据库厂商制定的,不同的DBMS有不同的协议。因此数据库厂商必须为客户端提供驱动软件,这样客户端才能连接到 DBMS。就像硬件设备厂家为了自家的设备能跑在操作系统上,必须提供驱动程序一样。
- Sun 公司指定了 Java 访问数据库的规范,这些规范称为 JDBC。而各个数据库厂商提供了各自的实现,这些实现我们称为驱动 。
JAVA程序操作数据库
- 注册驱动
- 建立连接
- 获取 SQL 语句执行平台
- 执行 SQL 语句
- 处理结果
- 退出 (关闭连接,释放资源)
// 注册驱动
Driver driver = new com.mysql.jdbc.Driver();
// 连接数据库
String url = "jdbc:mysql://localhost:3306/index_db";
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "r00tme");
Connection conn = driver.connect(url, info);
// 获取执行SQL语句的平台
Statement stmt = conn.createStatement();
// 执行SQL
String sql = "select * from t_citizen where id = 1";
ResultSet rs = stmt.executeQuery(sql);
// 处理结果
while (rs.next()) {
int id = rs.getInt(1);
String idCard = rs.getString(2);
String name = rs.getString(3);
int age = rs.getInt(4);
String gender = rs.getString(5);
System.out.println(id);
System.out.println(idCard);
System.out.println(name);
System.out.println(age);
System.out.println(gender);
}
// 关闭连接,释放资源
conn.close();
2.API详解
2.1Driver&DriverManager
Driver
boolean acceptsURL(String url)
查询驱动程序是否认为它可以打开到给定 URL 的连接。
Connection connect(String url, Properties info)
试图创建一个到给定 URL 的数据库连接。
int getMajorVersion()
获取此驱动程序的主版本号。
int getMinorVersion()
获得此驱动程序的次版本号。
DriverPropertyInfo[] getPropertyInfo(String url, Properties info)
获得此驱动程序的可能属性信息。
boolean jdbcCompliant()
报告此驱动程序是否是一个真正的 JDBC CompliantTM 驱动程序
DriverManager:管理一组 JDBC 驱动程序的基本服务(工具类)
- 一个DriverManager可以管理多个Driver
static void deregisterDriver(Driver driver)
从 DriverManager 的列表中删除一个驱动程序。
static Connection getConnection(String url)
试图建立到给定数据库 URL 的连接。
static Connection getConnection(String url, Properties info)
试图建立到给定数据库 URL 的连接。
static Connection getConnection(String url, String user, String password)
试图建立到给定数据库 URL 的连接。
static Driver getDriver(String url)
试图查找能理解给定 URL 的驱动程序。
static Enumeration<Driver> getDrivers()
获取带有当前调用者可以访问的所有当前已加载 JDBC 驱动程序的 Enumeration。
static int getLoginTimeout()
获取驱动程序试图登录到某一数据库时可以等待的最长时间,以秒为单位。
static PrintStream getLogStream()
已过时。
static PrintWriter getLogWriter()
获取日志 writer。
static void println(String message)
将一条消息打印到当前 JDBC 日志流中。
static void registerDriver(Driver driver)
向 DriverManager 注册给定驱动程序。
static void setLoginTimeout(int seconds)
设置驱动程序试图连接到某一数据库时将等待的最长时间,以秒为单位。
static void setLogStream(PrintStream out)
已过时。
static void setLogWriter(PrintWriter out)
设置由 DriverManager 和所有驱动程序使用的日志/追踪 PrintWriter 对象。
连接数据库的不同方式
版本一:用Driver直接连接
@Test
public void testGetConnection1() throws Exception{
Driver driver = new com.mysql.jdbc.Driver();
String url = "jdbc:mysql://localhost:3306/index_db";
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "r00tme");
Connection conn = driver.connect(url, info);
conn.close();
Assert.assertNotNull(conn);
}
版本二:用DriverManager
@Test
public void testGetConnection2() throws Exception{
Driver driver = new com.mysql.jdbc.Driver();
DriverManager.registerDriver(driver);
String url = "jdbc:mysql://localhost:3306/index_db";
String user = "root";
String password = "r00tme";
Connection conn = DriverManager.getConnection(url, user, password);
conn.close();
Assert.assertNotNull(conn);
}
版本三:反射
@Test
public void testGetConnection3() throws Exception{
Properties info = new Properties();
Reader reader = new FileReader("db.properties");
info.load(reader);
String driverClass = info.getProperty("driverClass");
String url = info.getProperty("url");
String user = info.getProperty("user");
String password = info.getProperty("password");
Class<Driver> cl = (Class<Driver>) Class.forName(driverClass);
Driver driver = cl.newInstance();
DriverManager.registerDriver(driver);
Connection conn = DriverManager.getConnection(url, user, password);
Assert.assertNotNull(conn);
}
版本四:反射简化
@Test
public void testGetConnection4() throws Exception{
Properties info = new Properties();
Reader reader = new FileReader("db.properties");
info.load(reader);
String driverClass = info.getProperty("driverClass");
String url = info.getProperty("url");
String user = info.getProperty("user");
String password = info.getProperty("password");
Class.forName(driverClass);
Connection conn = DriverManager.getConnection(url, user, password);
Assert.assertNotNull(conn);
}
版本五:JDBC4版本
@Test
public void testGetConnection5() throws Exception{
Properties info = new Properties();
Reader reader = new FileReader("db.properties");
info.load(reader);
String url = info.getProperty("url");
String user = info.getProperty("user");
String password = info.getProperty("password");
Connection conn = DriverManager.getConnection(url, user, password);
Assert.assertNotNull(conn);
}
2.2 Statement
- 我们可以通过 Statement 执行各种 SQL 语句,包括 DDL、DCL、DML、DQL等。
- 通过 Statement 创建数据库、创建表、插入数据、更新数据、查找数据、删除表、删除数据库
- 在默认情况下,同一时间每个 Statement 对象在只能打开一个 ResultSet 对象。因此,如果读取一个 ResultSet 对象与读取另一个交叉,则这两个对象必须是由不同的 Statement 对象生成的。如果存在某个语句的打开的当前 ResultSet 对象,则 Statement 接口中的所有执行方法都会隐式关闭它
void addBatch(String sql)
将给定的 SQL 命令添加到此 Statement 对象的当前命令列表中。
void cancel()
如果 DBMS 和驱动程序都支持中止 SQL 语句,则取消此 Statement 对象。
void clearBatch()
清空此 Statement 对象的当前 SQL 命令列表。
void clearWarnings()
清除在此 Statement 对象上报告的所有警告。
void close()
立即释放此 Statement 对象的数据库和 JDBC 资源,而不是等待该对象自动关闭时发生此操作。
boolean execute(String sql)
执行给定的 SQL 语句,该语句可能返回多个结果。
boolean execute(String sql, int autoGeneratedKeys)
执行给定的 SQL 语句(该语句可能返回多个结果),并通知驱动程序所有自动生成的键都应该可用于获取。
boolean execute(String sql, int[] columnIndexes)
执行给定的 SQL 语句(该语句可能返回多个结果),并通知驱动程序在给定数组中指示的自动生成的键应该可用于获取。
boolean execute(String sql, String[] columnNames)
执行给定的 SQL 语句(该语句可能返回多个结果),并通知驱动程序在给定数组中指示的自动生成的键应该可用于获取。
int[] executeBatch()
将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。
ResultSet executeQuery(String sql)
执行给定的 SQL 语句,该语句返回单个 ResultSet 对象。
int executeUpdate(String sql)
执行给定 SQL 语句,该语句可能为 INSERT、UPDATE 或 DELETE 语句,或者不返回任何内容的 SQL 语句(如 SQL DDL 语句)。
int executeUpdate(String sql, int autoGeneratedKeys)
执行给定的 SQL 语句,并用给定标志通知驱动程序由此 Statement 生成的自动生成键是否可用于获取。
int executeUpdate(String sql, int[] columnIndexes)
执行给定的 SQL 语句,并通知驱动程序在给定数组中指示的自动生成的键应该可用于获取。
int executeUpdate(String sql, String[] columnNames)
执行给定的 SQL 语句,并通知驱动程序在给定数组中指示的自动生成的键应该可用于获取。
Connection getConnection()
获取生成此 Statement 对象的 Connection 对象。
int getFetchDirection()
获取从数据库表获取行的方向,该方向是根据此 Statement 对象生成的结果集合的默认值。
int getFetchSize()
获取结果集合的行数,该数是根据此 Statement 对象生成的 ResultSet 对象的默认获取大小。
ResultSet getGeneratedKeys()
获取由于执行此 Statement 对象而创建的所有自动生成的键。
int getMaxFieldSize()
获取可以为此 Statement 对象所生成 ResultSet 对象中的字符和二进制列值返回的最大字节数。
int getMaxRows()
获取由此 Statement 对象生成的 ResultSet 对象可以包含的最大行数。
boolean getMoreResults()
移动到此 Statement 对象的下一个结果,如果其为 ResultSet 对象,则返回 true,并隐式关闭利用方法 getResultSet 获取的所有当前 ResultSet 对象。
boolean getMoreResults(int current)
将此 Statement 对象移动到下一个结果,根据给定标志指定的指令处理所有当前 ResultSet 对象;如果下一个结果为 ResultSet 对象,则返回 true。
int getQueryTimeout()
获取驱动程序等待 Statement 对象执行的秒数。
ResultSet getResultSet()
以 ResultSet 对象的形式获取当前结果。
int getResultSetConcurrency()
获取此 Statement 对象生成的 ResultSet 对象的结果集合并发性。
int getResultSetHoldability()
获取此 Statement 对象生成的 ResultSet 对象的结果集合可保存性。
int getResultSetType()
获取此 Statement 对象生成的 ResultSet 对象的结果集合类型。
int getUpdateCount()
以更新计数的形式获取当前结果;如果结果为 ResultSet 对象或没有更多结果,则返回 -1。
SQLWarning getWarnings()
获取此 Statement 对象上的调用报告的第一个警告。
boolean isClosed()
获取是否已关闭了此 Statement 对象。
boolean isPoolable()
返回指示 Statement 是否是可池化的值。
void setCursorName(String name)
将 SQL 指针名称设置为给定的 String,后续 Statement 对象的 execute 方法将使用此字符串。
void setEscapeProcessing(boolean enable)
将转义处理设置为开或关。
void setFetchDirection(int direction)
向驱动程序提供关于方向的提示,在使用此 Statement 对象创建的 ResultSet 对象中将按该方向处理行。
void setFetchSize(int rows)
为 JDBC 驱动程序提供一个提示,它提示此 Statement 生成的 ResultSet 对象需要更多行时应该从数据库获取的行数。
void setMaxFieldSize(int max)
设置此 Statement 对象生成的 ResultSet 对象中字符和二进制列值可以返回的最大字节数限制。
void setMaxRows(int max)
将此 Statement 对象生成的所有 ResultSet 对象可以包含的最大行数限制设置为给定数。
void setPoolable(boolean poolable)
请求将 Statement 池化或非池化。
void setQueryTimeout(int seconds)
将驱动程序等待 Statement 对象执行的秒数设置为给定秒数
Statement 会引入 SQL 注入问题
SQL注入(SQLi)是一种注入攻击,,可以执行恶意SQL语句。它通过将任意SQL代码插入数据库查询,使攻击者能够完全控制Web应用程序后面的数据库服务器。
2.3 PerparedStatement
通常我们执行一条 SQL 语句,得经过下面三个过程。
- 词法和语义解析
- 优化 SQL 语句,制定执行计划
- 执行并返回结果
我们把这种普通 SQL 语句称作 Immediate Statements。
但是很多情况下,一条 SQL 语句可能会被反复执行。或者是反复执行的 SQL 语句,它们结构相似,只是参数不同而已。 如果每次执行的时候都要经过上面的词法语义解析、优化 SQL、制定执行计划等,那效率就明显会降低。
MySQL 提供了预编译语句,可以帮我们解决这个问题。在预编译语句中,参数我们用占位符 ? 替代,这样我们就可以为多条不同的 SQL 语句,提供统一的模板。我们把这种 SQL 语句称作 PreparedStatements 或者Parameterized Statements。
预编译语句的优势有以下两点:
- 一次解析优化,多次执行。
- 可以防止 SQL 注入问题。
SQL中
#编译
prepare ps from 'select * from user where name=? and password=?';
set @name='Thomas_he';
set @password='abc';
#执行
execute ps using @name, @password;
#删除
deallocate prepare ps;
drop prepare ps;
JAVA中
public void testQuery() throws SQLException {
Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement("select * from user where name=? and password=?");
pstmt.setString(1, "Thomas_He");
pstmt.setString(2, "abc");
ResultSet rs = pstmt.executeQuery();
Assert.assertTrue(rs.next());
}
虽然这样我们就能避免 SQL 注入攻击了,但是测试性能时,我们会发现并没有什么提高。原因在于,这样我们只是开启了客户端预编译,实际还是用 Query 执行的,而不是 Execute 。
为了提高性能,我们需要开启服务端预编译,在 url 后面添加一些参数
- url=jdbc:mysql://localhost:3306/jdbc_db?useServerPrepStmts=true&cachePrepStmts=true
- useServerPrepStmts: 是否启用服务端预编译, true表示启用。
- cachePrepStmts: 是否缓存预编译语句, true表示缓存
2.4 Batch
不管是 Statement 还是 PreparedStatement,执行一条 SQL 语句,客户端都要和服务器进行一次通信,这样效率就比较低下。因此,MySQL 提供了批处理功能。
默认情况下,MySQL 没有开启批处理模式。需要在 url 后面添加 rewriteBatchedStatements=true 参数
void clearBatch()
清空此 Statement 对象的当前 SQL 命令列表
int[] executeBatch()
将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。
void addBatch(String sql)
将给定的 SQL 命令添加到此 Statement 对象的当前命令列表中
public void testBatch() throws SQLException { //153ms
Connection conn = getConnection();
String sql = "insert into user(name,password) values(?,?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 0; i < 10000; i++) {
if (i % 1000 == 0) {
pstmt.executeBatch();
pstmt.clearBatch();
}
pstmt.setString(1, "user" + i);
pstmt.setString(2, "pwd" + i);
pstmt.addBatch();
}
pstmt.executeBatch();
long end = System.currentTimeMillis();
System.out.println(end - start + "ms");
conn.close();
}
2.5 ResuleSet
- Java 中用 ResultSet 接口代表数据库返回的结果集
- ResultSet 封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前。ResultSet 的 next() 方法,可以判断是否有还有下一行数据。如果有,返回 true, 将游标指向该行,我们可以通过 ResultSet 对象获取该行的数据。如果没有,返回false
API
getXXX(int index) 获得第index列信息
getXXX(String columnName) 通过列名获取指定列信息
next() 移动光标
previous() 移动光标
absolute(int row) 将光标移动到此 ResultSet 对象的给定行编号
beforeFirst() 将光标置于第一行前面
afterLast() 光标置于最后一行后面
2.6 管理连接、语句和结果集
- 每个 Connection 对象都可以创建一个或多个 Statement 对象。同一个 Statement 对象可以用于多个不相关的命令和查询。但是,一个 Statement 对象最多只能有一个打开的结果集。如果需要执行多个查询操作,且需要同时分析查询结果,那么必须创建多个 Statement 对象。
- 使用完 ResultSet、Statement 或 Connection 对象后,应立即调用 close() 方法。这些对象都使用了规模较大的数据结构,它们会占用数据库服务器有限的资源
- 如果 Statement 对象上有一个打开的结果集,那么调用 close() 方法将自动关闭该结果集。同样地,调用 Connection 类的 close() 方法将关闭该连接上所有的 Statement
2.7 JDBC 事务相关 API
void close()
立即释放此 Connection 对象的数据库和 JDBC 资源,而不是等待它们被自动释放
Statement createStatement()
创建一个 Statement 对象来将 SQL 语句发送到数据库
PreparedStatement prepareStatement(String sql)
创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库
void setAutoCommit(boolean autoCommit)
(开启事务)将此连接的自动提交模式设置为给定状态
void commit()
(提交事务)使所有上一次提交/回滚后进行的更改成为持久更改,并释放此 Connection 对象当前持有的所有数据库锁
Savepoint setSavepoint()
(设置回滚点)在当前事务中创建一个未命名的保存点 (savepoint),并返回表示它的新 Savepoint 对象
void rollback()
(回滚到事务开始前状态)取消在当前事务中进行的所有更改,并释放此 Connection 对象当前持有的所有数据库锁
void rollback(Savepoint savepoint)
(回滚到回滚点)取消所有设置给定 Savepoint 对象之后进行的更改
void releaseSavepoint(Savepoint savepoint)
(删除回滚点)从当前事务中移除指定的 Savepoint 和后续 Savepoint 对象
void setTransactionIsolation(int level)
试图将此 Connection 对象的事务隔离级别更改为给定的级别
字段摘要
static int TRANSACTION_NONE
指示事务不受支持的常量。
static int TRANSACTION_READ_COMMITTED
指示不可以发生脏读的常量;不可重复读和虚读可以发生。
static int TRANSACTION_READ_UNCOMMITTED
指示可以发生脏读 (dirty read)、不可重复读和虚读 (phantom read) 的常量。
static int TRANSACTION_REPEATABLE_READ
指示不可以发生脏读和不可重复读的常量;虚读可以发生。
static int TRANSACTION_SERIALIZABLE
指示不可以发生脏读、不可重复读和虚读的常量