跳到主要内容
  1. 所有文章/
  2. 《Mybatis3源码深度解析》笔记/

Mybatis源码-环境准备和JDBC

·📄 4797 字·🍵 10 分钟

环境准备 #

直接在Github上拉取江荣波老师的工程即可:

https://github.com/jiangrongbo/mybatis-book

mybatis-book就是书中介绍的案例代码。mybatis-3就是主要分析的源码。直接导入IDEA即可。

JDBC介绍 #

JDBC(JavaDatabaseConnectivity)是Java语言中提供的访问关系型数据的接口。在Java编写的应用中,使用JDBC API可以执行SQL语句、检索SQL执行结果以及将数据更改写回到底层数据源。JDBC API也可以用于分布式、异构的环境中与多个数据源交互。

为什么先学习JDBC? #

MyBatis框架对JDBC做了轻量级的封装,作为Java开发人员,我们对JDBC肯定不会陌生,但是要看懂MyBatis的源码,还需要熟练掌握JDBCAPI的使用。“磨刀不误砍柴工”,在开始学习MyBatis源码之前,我们有必要全面地了解JDBC规范的所有内容。

Mybatis3实际上就是对JDBC API的封装。

JDBC官方规范文档 #

JDBC4.2规范文档:https://download.oracle.com/otndocs/jcp/jdbc-4_2-mrel2-spec/index.html.

基本使用 #

Class.forName()的作用,为了执行驱动的静态代码:class装载成功就表示执行了你的静态代码了,而且以后不会再执行这段静态代码了 Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。

SPI也能实现同样的效果,因为查到他的实现类。

public class Example01 {
    @Test
    public void testJdbc() {
        // 初始化数据
        DbUtils.initData();
        try {
            // 加载驱动
            Class.forName("org.hsqldb.jdbcDriver");
            // 获取Connection对象
            Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
                    "sa", "");
            Statement statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery("select * from user");
            // 遍历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("--------------------------------------");
            }
            // 关闭连接
            IOUtils.closeQuietly(statement);
            IOUtils.closeQuietly(connection);
            // 可以不用在继续关闭 resultSet,因为statement已经关闭。
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

JDBC一些关键结构图 #

Connection对象 #

一个Connection对象表示通过JDBC驱动与数据源建立的连接,这里的数据源可以是关系型数据库管理系统(DBMS)、文件系统或者其他通过JDBC驱动访问的数据。使用JDBCAPI的应用程序可能需要维护多个Connection对象,一个Connection对象可能访问多个数据源,也可能访问单个数据源。

两种方式生成Connection:

  1. 通过JDBC API中提供的DriverManager类获取。

  2. 通过DataSource接口的实现类获取。(比较推荐且常用)

    在所有的JavaEE项目中都是使用DataSource的具体实现来维护应用程序和数据库连接的。目前使用比较广泛的数据库连接池C3PO、DBCP、Druid等都是javax.sql.DataSource接口的具体实现。

java.sql.Driver驱动加载过程 #

所有的JDBC驱动都必须实现Driver接口,而且实现类必须包含一个静态初始化代码块。我们知道,类的静态初始化代码块会在类初始化时调用,驱动实现类需要在静态初始化代码块中向DriverManager注册自己的一个实例,例如:

当我们加载驱动实现类时,上面的静态初始化代码块就会被调用,向DriverManager中注册个驱动类的实例。这就是为什么我们使用JDBC操作数据库时一般会先加载驱动,例如:

Class.forName("org.hsqldb.jdbcDriver");

JDBC4.0以上的版本对DriverManager类的getConnection方法做了增强,可以通过Java的SPI机制加载驱动。符合JDBC4.O以上版本的驱动程序的JAR包中必须存在一个META-INF/services/java.sql.Driver文件,在java.sql.Driver文件中必须指定Driver接口的实现类。

Statement对象 #

Statement接口及它的子接口PreparedStatement和CallableStatement。

  • Statement接口中定义了执行SQL语句的方法,这些方法不支持参数输入(connection.createStatement()创建)。
  • PreparedStatement接口中增加了设置SQL参数的方法。
    Connection conn = ds.getConnection(user, passwd);
    PreparedStatement ps = conn.prepareStatement("INSERT INTO BOOKLIST""(AUTHOR,TITLE,ISBN) VALUES(?,?,?)");
    ps.setString(l,"Zamiatin,Evgenii");
    ps.setString(2,"we");
    ps.setLong3,140185852L);
    // 我们在使用setXXXO方法为参数占位符设置值时存在一个数据转换过程。
    // setXXXO方法的参数为Java数据类型,需要转换为JDBC类型(java.sql.Types中定义的SQL类型),
    // 这一过程由JDBC驱动来完成。
    
    PreparedStatement接口中提供了一个getParameterMetaData()方法,用于获取ParameterMetaData实例。
    ParameterMetaData pmd = stmt.getParameterMetaData();
    for(int i = 1; i <= pmd.getParameterCount(); i++) {
        String typeName = pmd.getParameterTypeName(i);
        String className = pmd.getParameterClassName(i);
        System.out.println("第" + i + "个参数," + "typeName:" + typeName + ", className:" + className);
    }
    //第1个参数,typeName:VARCHAR, className:java.lang.String
    //第2个参数,typeName:VARCHAR, className:java.lang.String
    //第3个参数,typeName:VARCHAR, className:java.lang.String
    //第4个参数,typeName:VARCHAR, className:java.lang.String
    //第5个参数,typeName:VARCHAR, className:java.lang.String
    
  • CallableStatement接口继承自PreparedStatement,在此基础上增加了调用存储过程以及检索存储过程调用结果的方法(connection.prepareCall()创建)。

一些相关方法:

#批量执行SQL
void addBatch(String sql)
void clearBatch()
int[]executeBatch()
#执行未知SQL语句
boolean execute(String sql)
boolean execute(String sql,int autoGeneratedKeys)
boolean execute(String sql,,int[] columnIndexes)
boolean execute(String sql, String[] columnNames)
#执行查询语句
ResultSet executeQuery(String sql)
#执行更新语句包括UPDATEDELETEINSERT
int executeUpdate(String sql)
int executeUpdate(String sql, int autoGeneratedKeys)
int executeUpdate(String sql,int[] columnIndexes)
int executeUpdate(String sql, String[] columnNames)
#SQL执行结果处理
long getLargeUpdateCount()ResultSet getResultSet()
int getUpdateCount()
boolean getMoreResults()
boolean getMoreResults(int current)
ResultSet getGeneratedKeys()
#JDBC 4.2新增数据量大于Integer.MAX VALUE时使用
long[] executeLargeBatch()
long executeLargeUpdate(String sql)
long executeLargeUpdate(String sql, int autoGeneratedKeys)
long executeLargeUpdate(String sql, int[] columnIndexes)
long executeLargeUpdate(String sql, String[] columnNames)
#取消SQL执行需要数据库和驱动支持
void cancel()
#关闭Statement对象
void close()
void closeOnCompletion()

ResultSet对象 #

ResultSet接口是JDBCAPI中另一个比较重要的组件,提供了检索和操作SQL执行结果相关的方法。

敏感性:ResultSet对象的修改对数据库的影响。

ResultSet类型 #

  1. ResultSet.TYPE_FORWARD_ONLY:这种类型的ResultSet不可滚动,游标只能向前移动,从第一行到最后一行,不允许向后移动,即只能使用ResultSet接口的nextO方法,而不能使用previousO方法,否则会产生错误。
  2. ResultSet.TYPE_SCROLL_INSENSITIVE:这种类型的ResultSet是可滚动的,它的游标可以相对于当前位置向前或向后移动,也可以移动到绝对位置。当ResultSet没有关闭时,ResultSet的修改对数据库不敏感,也就是说对ResultSet对象的修改不会影响对应的数据库中的记录。
  3. ResultSet.TYPE_SCROLL_SENSITIVE:这种类型的ResultSet是可滚动的,它的游标可以相对于当前位置向前或向后移动,也可以移动到绝对位置。当ResultSet没有关闭时,对ResultSet对象的修改会直接影响数据库中的记录。

ResultSet并行性 #

ResultSet对象的并行性决定了它支持更新的级别:

  • CONCUR_READ_ONLY:为 ResultSet对象设置这种属性后,只能从ResuISet对象中读取数据,但是不能更新ResultSet对象中的数据。
  • CONCUR_UPDATABLE:该属性表明,既可以从 ResuISet对象中读取数据,又能更新ResultSet中的数据。

ResultSet可保持性 #

调用Connection对象的commit()方法能够关闭当前事务中创建的ResultSet对象。然而,在某些情况下,这可能不是我们期望的行为。ResultSet对象的 holdability属性使得应用程序能够在Connection对象的commit()方法调用后控制ResultSet对象是否关闭。

  • HOLD_CURSORS_OVER_COMMIT:当调用Connection对象的commit()方法时,不关闭当前事务创建的ResultSet对象。
  • CLOSE_CURSORS_AT_COMMIT:当前事务创建的ResultSet对象在事务提交后会被关闭,对一些应用程序来说,这样能够提升系统性能。

ResultSet的类型、并行性和可保持性等属性可以在调用Connection对象的createStatementOprepareStatementO或prepareCallO方法创建Statement对象时设置:

常用方法 #

  • next():游标向前移动一行,如果游标定位到下一行,则返回true;如果游标位于最后一行之后,则返回false。
  • previous():游标向后移动一行,如果游标定位到上一行,则返回true;如果游标位于第一行之前,则返回false。
  • first():游标移动到第一行,如果游标定位到第一行,则返回true;如果ResultSet 对象中一行数据都没有,则返回false。
  • last():移动游标到最后一行,如果游标定位到最后一行,则返回true;如果ResultSet不包含任何数据行,则返回false。
  • beforeFirst():移动游标到ResultSet对象的第一行之前,如果ResultSet对象不包含任何数据行,则该方法不生效。
  • afterLast():游标位置移动到ResultSet对象最后一行之后,如果ResultSet对象中不包含任何行,则该方法不生效。

DatabaseMetaData对象 #

DatabaseMetaData接口中包含超过150个方法,根据这些方法的类型可以分为以下几类:

  1. 获取数据源信息。
  2. 确定数据源是否支持某一特性或功能。
  3. 获取数据源的限制。
  4. 确定数据源包含哪些SQL对象以及这些对象的属性。
  5. 获取数据源对事务的支持。
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();
        }
    }
}