Mybatis源码-环境准备和JDBC
目录
环境准备 #
直接在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:
通过JDBC API中提供的DriverManager类获取。
通过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参数的方法。PreparedStatement接口中提供了一个getParameterMetaData()方法,用于获取ParameterMetaData实例。
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.setLong(3,140185852L); // 我们在使用setXXXO方法为参数占位符设置值时存在一个数据转换过程。 // setXXXO方法的参数为Java数据类型,需要转换为JDBC类型(java.sql.Types中定义的SQL类型), // 这一过程由JDBC驱动来完成。
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)
#执行更新语句,包括UPDATE、DELETE、INSERT
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类型 #
- ResultSet.TYPE_FORWARD_ONLY:这种类型的ResultSet不可滚动,游标只能向前移动,从第一行到最后一行,不允许向后移动,即只能使用ResultSet接口的nextO方法,而不能使用previousO方法,否则会产生错误。
- ResultSet.TYPE_SCROLL_INSENSITIVE:这种类型的ResultSet是可滚动的,它的游标可以相对于当前位置向前或向后移动,也可以移动到绝对位置。当ResultSet没有关闭时,ResultSet的修改对数据库不敏感,也就是说对ResultSet对象的修改不会影响对应的数据库中的记录。
- 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个方法,根据这些方法的类型可以分为以下几类:
- 获取数据源信息。
- 确定数据源是否支持某一特性或功能。
- 获取数据源的限制。
- 确定数据源包含哪些SQL对象以及这些对象的属性。
- 获取数据源对事务的支持。
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();
}
}
}