从本章开始将会从Mybatis源码分析,一步一步分析Mybatis,坐到深入浅出。
注:本系列文章主要参考书籍《Mybatis3源码深度解析》
源码地址:https://github.com/jiangrongbo/mybatis-book
MyBatis框架对JDBC做了轻量级的封装,作为Java开发人员,我们对JDBC肯定不会陌生,但是要看懂MyBatis的源码,还需要熟练掌握JDBC API的使用。
使用JDBC操作数据源大致需要以下几个步骤:
(1)与数据源建立连接。
(2)执行SQL语句。
(3)检索SQL执行结果。
(4)关闭连接。
一个Connection对象表示通过JDBC驱动与数据源建立的连接,这里的数据源可以是关系型数据库管理系统(DBMS)、文件系统或者其他通过JDBC驱动访问的数据。使用JDBC API的应用程序可能需要维护多个Connection对象,一个Connection对象可能访问多个数据源,也可能访问单个数据源。
接下来看看两个例子:
例子一是通过DriverManager对象获取连接
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);
} catch (Exception e) {
e.printStackTrace();
}
}
}
例子二通过DataSource对象获取连接
public class Example03 {
@Test
public void testJdbc() {
// 初始化数据
DbUtils.initData();
try {
// 创建DataSource实例
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();
// 获取Connection对象
Connection connection = dataSource.getConnection();
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);
} catch (Exception e) {
e.printStackTrace();
}
}
}
区别就是:DataSource对象是从配置文件中读取数据库信息,这样若是数据库的信息发生变化,就无需改动代码。
Statement接口中定义了执行SQL语句的方法,这些方法不支持参数输入,PreparedStatement接口中增加了设置SQL参数的方法,CallableStatement接口继承自PreparedStatement,在此基础上增加了调用存储过程以及检索存储过程调用结果的方法。
Statement的主要作用是与数据库进行交互,该接口中定义了一些数据库操作以及检索SQL执行结果相关的方法,具体如下:
public class Example06 {
@Before
public void initData() throws Exception {
// 初始化数据
Class.forName("org.hsqldb.jdbcDriver");
// 获取Connection对象
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
// 使用Mybatis的ScriptRunner工具类执行数据库脚本
ScriptRunner scriptRunner = new ScriptRunner(conn);
// 不输出sql日志
scriptRunner.setLogWriter(null);
scriptRunner.runScript(Resources.getResourceAsReader("create-table.sql"));
System.out.println("----------------------");
}
@Test
public void testJdbc() {
try {
// 创建DataSource实例
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();
// 获取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");
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);
}
stmt.execute();
// 关闭连接
IOUtils.closeQuietly(stmt);
IOUtils.closeQuietly(connection);
} catch (Exception e) {
e.printStackTrace();
}
}
}
ResultSet接口是JDBC API中另一个比较重要的组件,提供了检索和操作SQL执行结果相关的方法。
ResultSet对象的类型主要体现在两个方面:
(1)游标可操作的方式。
(2)ResultSet对象的修改对数据库的影响。
ResultSet有3种不同的类型,分别说明如下:
DatabaseMetaData接口是由JDBC驱动程序实现的,用于提供底层数据源相关的信息。该接口主要用于为应用程序或工具确定如何与底层数据源交互。应用程序也可以使用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();
}
}
}
学这个之前可以先看一下我的这个文章:Mysql事务
Connection对象的autoCommit属性决定什么时候结束一个事务。启用自动提交后,会在每个SQL语句执行完毕后自动提交事务。当Connection对象创建时,默认情况下,事务自动提交是开启的。Connection接口中提供了一个setAutoCommit()方法,可以禁用事务自动提交。此时,需要显式地调用Connection接口提供commit()方法提交事务,或者调用rollback()方法回滚事务。禁用事务自动提交适用于需要将多个SQL语句作为一个事务提交或者事务由应用服务器管理。
例子:
public class Example09 {
@Before
public void initData() throws Exception {
// 初始化数据
Class.forName("org.hsqldb.jdbcDriver");
// 获取Connection对象
Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
"sa", "");
// 使用Mybatis的ScriptRunner工具类执行数据库脚本
ScriptRunner scriptRunner = new ScriptRunner(conn);
// 不输出sql日志
scriptRunner.setLogWriter(null);
scriptRunner.runScript(Resources.getResourceAsReader("create-table.sql"));
System.out.println("------------------------");
}
@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();
}
}
}
MyBatis中提供了一个SQL工具类。使用这个工具类,我们可以很方便地在Java代码中动态构建SQL语句。
使用JDBC API开发过项目的读者应该知道,当我们需要使用Statement对象执行SQL时,SQL语句会嵌入Java代码中。SQL语句比较复杂时,我们可能会在代码中对SQL语句进行拼接,查询条件不固定时,还需要根据不同条件拼接不同的SQL语句,拼接语句时不要忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。这个过程对于开发人员来说简直就是一场噩梦,而且代码可维护性级低,例如:
String orgSql = "SELECT P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME, P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON\n" +
"FROM PERSON P, ACCOUNT A\n" +
"INNER JOIN DEPARTMENT D on D.ID = P.DEPARTMENT_ID\n" +
"INNER JOIN COMPANY C on D.COMPANY_ID = C.ID\n" +
"WHERE (P.ID = A.ID AND P.FIRST_NAME like ?) \n" +
"OR (P.LAST_NAME like ?)\n" +
"GROUP BY P.ID\n" +
"HAVING (P.LAST_NAME like ?) \n" +
"OR (P.FIRST_NAME like ?)\n" +
"ORDER BY P.ID, P.FULL_NAME";
public class SQLExample {
@Test
public void testSelectSQL() {
String orgSql = "SELECT P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME, P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON\n" +
"FROM PERSON P, ACCOUNT A\n" +
"INNER JOIN DEPARTMENT D on D.ID = P.DEPARTMENT_ID\n" +
"INNER JOIN COMPANY C on D.COMPANY_ID = C.ID\n" +
"WHERE (P.ID = A.ID AND P.FIRST_NAME like ?) \n" +
"OR (P.LAST_NAME like ?)\n" +
"GROUP BY P.ID\n" +
"HAVING (P.LAST_NAME like ?) \n" +
"OR (P.FIRST_NAME like ?)\n" +
"ORDER BY P.ID, P.FULL_NAME";
String newSql = new SQL().
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME").
SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON").
FROM("PERSON P").
FROM("ACCOUNT A").
INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID").
INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID").
WHERE("P.ID = A.ID").
WHERE("P.FIRST_NAME like ?").
OR().
WHERE("P.LAST_NAME like ?").
GROUP_BY("P.ID").
HAVING("P.LAST_NAME like ?").
OR().
HAVING("P.FIRST_NAME like ?").
ORDER_BY("P.ID").
ORDER_BY("P.FULL_NAME").toString();
assertEquals(orgSql, newSql);
}
@Test
public void testDynamicSQL() {
selectPerson(null,null,null);
}
public String selectPerson(final String id, final String firstName, final String lastName) {
return new SQL() {{
SELECT("P.ID, P.USERNAME, P.PASSWORD");
SELECT("P.FIRST_NAME, P.LAST_NAME");
FROM("PERSON P");
if (id != null) {
WHERE("P.ID = #{id}");
}
if (firstName != null) {
WHERE("P.FIRST_NAME = #{firstName}");
}
if (lastName != null) {
WHERE("P.LAST_NAME = #{lastName}");
}
ORDER_BY("P.LAST_NAME");
}}.toString();
}
@Test
public void testInsertSql() {
String insertSql = new SQL().
INSERT_INTO("PERSON").
VALUES("ID, FIRST_NAME", "#{id}, #{firstName}").
VALUES("LAST_NAME", "#{lastName}").toString();
System.out.println(insertSql);
}
@Test
public void testDeleteSql() {
String deleteSql = new SQL() {{
DELETE_FROM("PERSON");
WHERE("ID = #{id}");
}}.toString();
System.out.println(deleteSql);
}
@Test
public void testUpdateSql() {
String updateSql = new SQL() {{
UPDATE("PERSON");
SET("FIRST_NAME = #{firstName}");
WHERE("ID = #{id}");
}}.toString();
System.out.println(updateSql);
}
}
public class SQL extends AbstractSQL<SQL> {
@Override
public SQL getSelf() {
return this;
}
}
分析:
public abstract class AbstractSQL<T> {
private static final String AND = ") \nAND (";
private static final String OR = ") \nOR (";
private final SQLStatement sql = new SQLStatement();
public abstract T getSelf();
public T UPDATE(String table) {
sql().statementType = SQLStatement.StatementType.UPDATE;
sql().tables.add(table);
return getSelf();
}
public T SET(String sets) {
sql().sets.add(sets);
return getSelf();
}
/**
* @since 3.4.2
*/
public T SET(String... sets) {
sql().sets.addAll(Arrays.asList(sets));
return getSelf();
}
public T INSERT_INTO(String tableName) {
sql().statementType = SQLStatement.StatementType.INSERT;
sql().tables.add(tableName);
return getSelf();
}
.......
}
分析:
private static class SQLStatement {
// SQL语句的类型
public enum StatementType {
DELETE, INSERT, SELECT, UPDATE
}
StatementType statementType;
// 用于记录SQL实例SELECT()、UPDATE()等方法调用参数
List<String> sets = new ArrayList<String>();
List<String> select = new ArrayList<String>();
List<String> tables = new ArrayList<String>();
List<String> join = new ArrayList<String>();
List<String> innerJoin = new ArrayList<String>();
List<String> outerJoin = new ArrayList<String>();
List<String> leftOuterJoin = new ArrayList<String>();
List<String> rightOuterJoin = new ArrayList<String>();
List<String> where = new ArrayList<String>();
List<String> having = new ArrayList<String>();
List<String> groupBy = new ArrayList<String>();
List<String> orderBy = new ArrayList<String>();
List<String> lastList = new ArrayList<String>();
List<String> columns = new ArrayList<String>();
List<String> values = new ArrayList<String>();
// 是否包含distinct关键字
boolean distinct;
public SQLStatement() {
// Prevent Synthetic Access
}
.....
}
分析:
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
// 调用SQLStatement对象的sql()方法生成SQL语句
sql().sql(sb);
return sb.toString();
}
public String sql(Appendable a) {
SafeAppendable builder = new SafeAppendable(a);
if (statementType == null) {
return null;
}
String answer;
switch (statementType) {
case DELETE:
answer = deleteSQL(builder);
break;
case INSERT:
answer = insertSQL(builder);
break;
case SELECT:
answer = selectSQL(builder);
break;
case UPDATE:
answer = updateSQL(builder);
break;
default:
answer = null;
}
return answer;
}
private String selectSQL(SafeAppendable builder) {
if (distinct) {
sqlClause(builder, "SELECT DISTINCT", select, "", "", ", ");
} else {
sqlClause(builder, "SELECT", select, "", "", ", ");
}
sqlClause(builder, "FROM", tables, "", "", ", ");
joins(builder);
sqlClause(builder, "WHERE", where, "(", ")", " AND ");
sqlClause(builder, "GROUP BY", groupBy, "", "", ", ");
sqlClause(builder, "HAVING", having, "(", ")", " AND ");
sqlClause(builder, "ORDER BY", orderBy, "", "", ", ");
return builder.toString();
}
分析:
1.通过源码分析,根据SQL类型,将数组中的字符串取出来拼接,大家若感兴趣可以去看源码。
学习记录,不断沉淀,终究会成为一个优秀的程序员,加油!
您的点赞、关注与收藏是我分享博客的最大赞赏!
博主博客地址: https://blog.csdn.net/qq_45701514