一、JDBC 规范详解

  • Post author:
  • Post category:其他




一、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
  • 3)、JNDI 详解见

    jndi的作用,为什么要有jndi
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();
        }
    }



版权声明:本文为lxz352907839原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。