一JDBC 规范详解
Posted archerLuo罗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一JDBC 规范详解相关的知识,希望对你有一定的参考价值。
一、JDBC API简介
1、定义
- Java数据库连接(Java Database Connectivity,简称JDBC)是 Java 语言中提供访问关系型数据库(大多数情况下是关系型数据库)的接口
- 源码地址:https://github.com/RononoaZoro/mybatis-book/tree/master 的 mybatis-book ( mybatis-chapter02 )
- 文章内容出自《Mybatis 3 源码深度解析》第二章
2、建立数据库连接
-
1)、JDBC API 中定义了 Connection 接口,用来表示与底层数据源的链接,JDBC 应用有两种方式获取 Connection 对象
-
1、DriverManager
-
// 获取Connection对象 Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis","sa", "");
-
2、DataSource:
-
// 创建DataSource实例 DataSource dataSource = new UnpooledDataSource("org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:mybatis", "sa", ""); // 获取Connection对象 Connection connection = dataSource.getConnection();
-
-
2)、JDBC API 中定义了两个 DataSource 比较重要的扩展,用于支撑企业级应用
- 1、ConnectionPoolDataSource :支持缓存和复用 Connection 对象,这样可以很大程度提升应用性能和伸缩性
- 2、XADataSource :该实例返回的 Connection 对象能够支持分布式事务
3、执行 SQL 语句
-
1)、通过 Statement 执行 Sql 语句
-
Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("select * from user");
4、处理 SQL 执行结果
-
1)、通过 ResultSet 处理查询结果集
-
// 遍历ResultSet ResultSetMetaData metaData = resultSet.getMetaData(); int columCount = metaData.getColumnCount(); while (resultSet.next()) { for (int i = 1; i <= columCount; i++) { String columName = metaData.getColumnName(i); String columVal = resultSet.getString(columName); System.out.println(columName + ":" + columVal); } System.out.println("---------------------------------------"); }
5、使用 JDBC 操作数据库
-
1)、代码示例
-
package com.blog4java.jdbc; import com.blog4java.common.DbUtils; import com.blog4java.common.IOUtils; import org.apache.ibatis.datasource.unpooled.UnpooledDataSource; import org.junit.Test; import javax.sql.DataSource; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.Statement; public class Example02 { @Test public void testJdbc() { // 初始化数据 DbUtils.initData(); try { // 1、创建DataSource实例 DataSource dataSource = new UnpooledDataSource("org.hsqldb.jdbcDriver", "jdbc:hsqldb:mem:mybatis", "sa", ""); ///2、获取Connection对象 Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery("select * from user"); ///3、遍历ResultSet ResultSetMetaData metaData = resultSet.getMetaData(); int columCount = metaData.getColumnCount(); while (resultSet.next()) { for (int i = 1; i <= columCount; i++) { String columName = metaData.getColumnName(i); String columVal = resultSet.getString(columName); System.out.println(columName + ":" + columVal); } System.out.println("---------------------------------------"); } ///4、关闭连接 IOUtils.closeQuietly(statement); IOUtils.closeQuietly(connection); } catch (Exception e) { e.printStackTrace(); } } }
二、JDBC API中的类与接口
1、JDBC API 核心类之间的关系
三、Connection 详解
1、JDBC 驱动类型
- 1、JDBC-ODBC Bridge Driver (JDBC-ODBC 桥接驱动)
- 2、Native API Driver (Native API 类型驱动)
- 3、JDBC-Net Driver (JDBC-Net 驱动类型)
- 4、Native Protocol Driver (Native Protocol 驱动类型) 最常用的
2、java.sql.Driver 接口
- 1)、自定义驱动
public class JDBCDriver implements Driver {
public static final JDBCDriver driverInstance = new JDBCDriver();
static {
try {
DriverManager.registerDriver(driverInstance);
} catch (Exception var1) {
}
}
}
- 2)、指定驱动
java -Djdbc.drivers=org.hsqldb.jdbc.JDBCDriver
3、Java SPI 机制简介
- 1)、定义:SPI (Service Provider Interface) 是JDK 内置的一种提供发现机制,SPI 是一种动态替换发现机制,可以在程序运行时动态添加实现
- 2)、DriverManager 类中定义了静态初始化代码块,例子 参考代码 SPI
public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
}
4、java.sql.DriverAction 接口
- 1)、DriverAction 用于监听驱动类被解除注册事件
5、java.sql.DriverManager
- 1)、registerDriver() :注册驱动
- 2)、getConnection() :获取连接
6、java.sql.DataSource 接口
- 1)、可以自行实现
- 2)、mybatis 实现
- 1、ConnectionPoolDataSource :支持缓存和复用 Connection 对象,这样可以很大程度提升应用性能和伸缩性
- 2、XADataSource :该实例返回的 Connection 对象能够支持分布式事务
7、使用 JNDI API 增强应用可移植性
- 1)、定义:JNDI (Java Naming and Directory Interface , Java 命名和目录接口)为应用程序提供了通过网络访问远程服务的方式
- 2)、例子 参考代码 SPI
package com.blog4java.jndi;
import org.apache.ibatis.datasource.DataSourceFactory;
import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
public class Example04 {
@Before
public void before() throws IOException {
DataSourceFactory dsf = new UnpooledDataSourceFactory();
Properties properties = new Properties();
InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
properties.load(configStream);
dsf.setProperties(properties);
DataSource dataSource = dsf.getDataSource();
try {
Properties jndiProps = new Properties();
jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
jndiProps.put(Context.URL_PKG_PREFIXES, "org.apache.naming");
Context ctx = new InitialContext(jndiProps);
ctx.bind("java:TestDC", dataSource);
} catch (NamingException e) {
e.printStackTrace();
}
}
@Test
public void testJndi() {
try {
Properties jndiProps = new Properties();
jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
jndiProps.put(Context.URL_PKG_PREFIXES, "org.apache.naming");
Context ctx = new InitialContext(jndiProps);
DataSource dataSource = (DataSource) ctx.lookup("java:TestDC");
Connection conn = dataSource.getConnection();
Assert.assertNotNull(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
}
8、关闭 Connection 对象
- java.sql.Connection#close()
- java.sql.Connection#isClose()
四、Statement 详解
1、java.sql.Statement 接口
- 1)、Statement 是JDBC API 操作数据库的核心接口,具体实现由 JDBC 驱动来完成
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis","sa", "");
Statement stmt = conn.createStatement();
- 2)、注意点
- 1、若数据库支持返回的更新数量大于 Integer.MAX_VALUE , 则需要调用 executeLargeUpdate() 方法
- 2、当数据库支持返回的更新数量大于 Integer.MAX_VALUE , 需要使用 getLargeUpdateCount() 方法
- 3、Statement 具体方法和属性,请参考源代码
2、java.sql.PreparedStatement 接口
- 1)、PreparedStatement 接口继承自 Statement 接口,增加了参数占位符功能
// 获取Connection对象
Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement("insert into " +
"user(create_time, name, password, phone, nick_name) " +
"values(?,?,?,?,?);");
stmt.setString(1,"2010-10-24 10:20:30");
stmt.setString(2,"User1");
stmt.setString(3,"test");
stmt.setString(4,"18700001111");
stmt.setString(5,"User1");
3、java.sql.CallableStatement 接口
- 1)、CallableStatement 接口继承自 PreparedStatement 接口,增加了调用存储过程并检索结果的功能
Connection conn = DBUtil.getConnection();
CallableStatement cs = conn.prepareCall("call p1()");//call p1 -- 不带括号也行
cs.execute();
conn.close();
4、获取自增长的键值
// 获取Connection对象
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
Statement stmt = conn.createStatement();
String sql = "insert into user(create_time, name, password, phone, nick_name) " +
"values('2010-10-24 10:20:30','User1','test','18700001111','User1');";
stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
ResultSet genKeys = stmt.getGeneratedKeys();
if(genKeys.next()) {
System.out.println("自增长主键:" + genKeys.getInt(1));
}
五、ResultSet 详解
1、ResultSet 类型
- 1)、游标可操作的方式和 ResultSet 对象的修改对数据库的影响
- 1、ResultSet.TYPE_FORWARD_ONLY :结果集的游标只能向下滚动。一般是默认值
- 2、ResultSet.TYPE_SCROLL_INSENSITIVE :结果集的游标可以上下移动,当数据库变化时,当前结果集不变。
- 3、ResultSet.TYPE_SCROLL_SENSITIVE :返回可滚动的结果集,当数据库变化时,当前结果集同步改变。
2、ResultSet 并行性
- 1)、ResultSet 对象的并行性决定了它支持更新的级别,目前 JDBC 中支持两个级别
- 1、CONCUR_READ_ONLY : 只能从结果中读取数据,不可修改
- 2、CONCUR_UPDATABLE : ResultSet 对象可以执行数据库的新增、修改、和移除。
3、ResultSet 可保持性
- 1)、HOLD_CURSORS_OVER_COMMIT :调用 commit() 方法时,不关闭当前事务创建的 ResultSet 对象
- 2)、CLOSE_CURSORS_AT_COMMIT :调用 commit() 方法时,关闭当前事务创建的 ResultSet 对象,某些场景可以提升性能
- 3)、默认配置取决于具体的驱动实现,可通过 DatabaseMetaData 接口的 getResultSetHoldability() 获取JDBC 的默认可保持性
4、ResultSet 属性配置
// 获取Connection对象
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis","sa", "");
Statement stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY,
ResultSet.HOLD_CURSORS_OVER_COMMIT);
5、ResultSet 游标移动与修改 ResultSet 对象
- 1)、对不同的配置有不同的操作方式
- 2)、游标移动操作方法:next(), last(), relative() 等
- 3)、配置CONCUR_UPDATABLE,可通过修改 ResultSet 对象,同步修改数据库
Statement st = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
ResultSet rs = st.executeQuery("SELECT * FROM user where name = '" + name + "'");
rs.updateString("password",password);
rs.updateRow();
6、关闭 ResultSet 对象
- 1)、ResultSet 的 close()
- 2)、相关联的 Statement 对象重复执行
- 3)、创建 ResultSet 对象的 Statement 或者 Connection 对象显式关闭
- 4)、CLOSE_CURSORS_AT_COMMIT 提交事务后
六、DataBaseMetaData 详解
DataBaseMetaData 接口的方法可分为以下几类
- 1、获取数据源信息
- 2、确定数据源是否支持某一特性
- 3、获取数据源的限制
- 4、确定数据源包含哪些 SQL 对象以及这些对象的属性
- 5、获取数据源对事务的支持
1、创建 DataBaseMetaData 对象 以及获取数据源基本信息
package com.blog4java.jdbc;
import com.blog4java.common.IOUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
/**
* 获取数据库基本信息
*/
public class Example08 {
@Test
public void testDbMetaData() {
try {
Class.forName("org.hsqldb.jdbcDriver");
// 获取Connection对象
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
DatabaseMetaData dmd = conn.getMetaData();
System.out.println("数据库URL:" + dmd.getURL());
System.out.println("数据库用户名:" + dmd.getUserName());
System.out.println("数据库产品名:" + dmd.getDatabaseProductName());
System.out.println("数据库产品版本:" + dmd.getDatabaseProductVersion());
System.out.println("驱动主版本:" + dmd.getDriverMajorVersion());
System.out.println("驱动副版本:" + dmd.getDriverMinorVersion());
System.out.println("数据库供应商用于schema的首选术语:" + dmd.getSchemaTerm());
System.out.println("数据库供应商用于catalog的首选术语:" + dmd.getCatalogTerm());
System.out.println("数据库供应商用于procedure的首选术语:" + dmd.getProcedureTerm());
System.out.println("null值是否高排序:" + dmd.nullsAreSortedHigh());
System.out.println("null值是否低排序:" + dmd.nullsAreSortedLow());
System.out.println("数据库是否将表存储在本地文件中:" + dmd.usesLocalFiles());
System.out.println("数据库是否为每个表使用一个文件:" + dmd.usesLocalFilePerTable());
System.out.println("数据库SQL关键字:" + dmd.getSQLKeywords());
IOUtils.closeQuietly(conn);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、其他
- 获取数据源(支持特性、限制、SQL 对象及属性、事务支持),请参考官方文档或代码注释
七、JDBC 事务
1、并发下事务产生的问题
- 1)、脏读
- 就是指事务A读到了事务B还没有提交的数据
- 2)、不可重复读
- 是指在一个事务内,多次读同一数据,读出来的数据不一致
- 3)、幻读
- 一个事务在前后两次查询同一个范围的时候、后一次查询看到了前一次查询未看到的行
2、事务隔离级别
-
1)、DEFAULT
- 默认隔离级别,每种数据库支持的事务隔离级别不一样,mysql,可以使用"select @@tx_isolation"来查看默认的事务隔离级别
-
2)、READ_UNCOMMITTED
- 可读取到没有被提交的数据,无法解决脏读、不可重复读、幻读中的任何一种
-
3)、READ_COMMITED
- 可读取那些已经提交的数据,能够防止脏读,但是无法限制不可重复读和幻读
-
4)、REPEATABLE_READ
- 读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,可解决脏读、不可重复读,无法解决幻读
-
5)、SERLALIZABLE
- 串行化,最高的事务隔离级别,可脏读、不可重复读和幻读,单有性能问题
3、保存点
@Test
public void testSavePoint() {
try {
Class.forName("org.hsqldb.jdbcDriver");
// 获取Connection对象
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
String sql1 = "insert into user(create_time, name, password, phone, nick_name) " +
"values('2010-10-24 10:20:30','User1','test','18700001111','User1')";
String sql2 = "insert into user(create_time, name, password, phone, nick_name) " +
"values('2010-10-24 10:20:30','User2','test','18700001111','User2')";
conn.setAutoCommit(false);
Statement stmt = conn.createStatement();
stmt.executeUpdate(sql1);
// 创建保存点
Savepoint savepoint = conn.setSavepoint("SP1");
stmt.executeUpdate(sql2);
// 回滚到保存点
conn.rollback(savepoint);
conn.commit();
ResultSet rs = conn.createStatement().executeQuery("select * from user ");
DbUtils.dumpRS(rs);
IOUtils.closeQuietly(stmt);
IOUtils.closeQuietly(conn);
} catch (Exception e) {
e.printStackTrace(以上是关于一JDBC 规范详解的主要内容,如果未能解决你的问题,请参考以下文章