Mybatis常用工具类-SQL类
文章目录
- 前言
- 一、JDBC
-
- 1.Connection
- 2.Statement类
- 3.ResultSet类
- 4.DatabaseMetaData类
- 5.JDBC事务
- 二、使用SQL类生成语句
-
- 1.无工具类写SQL语句
- 2.使用工具类
- 3.SQL工具类的方法及作用
- 4.源码分析
前言
从本章开始将会从Mybatis源码分析,一步一步分析Mybatis,坐到深入浅出。
注:本系列文章主要参考书籍《Mybatis3源码深度解析》
源码地址:https://github.com/jiangrongbo/mybatis-book
一、JDBC
MyBatis框架对JDBC做了轻量级的封装,作为Java开发人员,我们对JDBC肯定不会陌生,但是要看懂MyBatis的源码,还需要熟练掌握JDBC API的使用。
使用JDBC操作数据源大致需要以下几个步骤:
(1)与数据源建立连接。
(2)执行SQL语句。
(3)检索SQL执行结果。
(4)关闭连接。
1.Connection
一个Connection对象表示通过JDBC驱动与数据源建立的连接,这里的数据源可以是关系型数据库管理系统(DBMS)、文件系统或者其他通过JDBC驱动访问的数据。使用JDBC API的应用程序可能需要维护多个Connection对象,一个Connection对象可能访问多个数据源,也可能访问单个数据源。
接下来看看两个例子:
例子一是通过DriverManager对象获取连接
public class Example01 {@Testpublic 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");// 遍历ResultSetResultSetMetaData 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 {@Testpublic 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");// 遍历ResultSetResultSetMetaData 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对象是从配置文件中读取数据库信息,这样若是数据库的信息发生变化,就无需改动代码。
2.Statement类
Statement接口中定义了执行SQL语句的方法,这些方法不支持参数输入,PreparedStatement接口中增加了设置SQL参数的方法,CallableStatement接口继承自PreparedStatement,在此基础上增加了调用存储过程以及检索存储过程调用结果的方法。
Statement的主要作用是与数据库进行交互,该接口中定义了一些数据库操作以及检索SQL执行结果相关的方法,具体如下:
例子:
public class Example06 {@Beforepublic 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("----------------------");}@Testpublic 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();}}
}
3.ResultSet类
ResultSet接口是JDBC API中另一个比较重要的组件,提供了检索和操作SQL执行结果相关的方法。
ResultSet对象的类型主要体现在两个方面:
(1)游标可操作的方式。
(2)ResultSet对象的修改对数据库的影响。
ResultSet有3种不同的类型,分别说明如下:
- TYPE_FORWARD_ONLY:这种类型的ResultSet不可滚动,游标只能向前移动,从第一行到最后一行,不允许向后移动,即只能使用ResultSet接口的next()方法,而不能使用previous()方法,否则会产生错误。
- TYPE_SCROLL_INSENSITIVE:这种类型的ResultSet是可滚动的,它的游标可以相对于当前位置向前或向后移动,也可以移动到绝对位置。当ResultSet没有关闭时,ResultSet的修改对数据库不敏感,也就是说对ResultSet对象的修改不会影响对应的数据库中的记录。
- TYPE_SCROLL_SENSITIVE:这种类型的ResultSet是可滚动的,它的游标可以相对于当前位置向前或向后移动,也可以移动到绝对位置。当ResultSet没有关闭时,对ResultSet对象的修改会直接影响数据库中的记录。
4.DatabaseMetaData类
DatabaseMetaData接口是由JDBC驱动程序实现的,用于提供底层数据源相关的信息。该接口主要用于为应用程序或工具确定如何与底层数据源交互。应用程序也可以使用DatabaseMetaData接口提供的方法获取数据源信息。
DatabaseMetaData接口中包含超过150个方法,根据这些方法的类型可以分为以下几类:
(1)获取数据源信息。
(2)确定数据源是否支持某一特性或功能。
(3)获取数据源的限制。
(4)确定数据源包含哪些SQL对象以及这些对象的属性。
(5)获取数据源对事务的支持。
例子:
public class Example08 {@Testpublic 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();}}
}
5.JDBC事务
学这个之前可以先看一下我的这个文章:Mysql事务
Connection对象的autoCommit属性决定什么时候结束一个事务。启用自动提交后,会在每个SQL语句执行完毕后自动提交事务。当Connection对象创建时,默认情况下,事务自动提交是开启的。Connection接口中提供了一个setAutoCommit()方法,可以禁用事务自动提交。此时,需要显式地调用Connection接口提供commit()方法提交事务,或者调用rollback()方法回滚事务。禁用事务自动提交适用于需要将多个SQL语句作为一个事务提交或者事务由应用服务器管理。
例子:
public class Example09 {@Beforepublic 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("------------------------");}@Testpublic 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();}}
}
二、使用SQL类生成语句
MyBatis中提供了一个SQL工具类。使用这个工具类,我们可以很方便地在Java代码中动态构建SQL语句。
1.无工具类写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";
2.使用工具类
public class SQLExample {@Testpublic 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);}@Testpublic 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();}@Testpublic void testInsertSql() {String insertSql = new SQL().INSERT_INTO("PERSON").VALUES("ID, FIRST_NAME", "#{id}, #{firstName}").VALUES("LAST_NAME", "#{lastName}").toString();System.out.println(insertSql);}@Testpublic void testDeleteSql() {String deleteSql = new SQL() {{DELETE_FROM("PERSON");WHERE("ID = #{id}");}}.toString();System.out.println(deleteSql);}@Testpublic void testUpdateSql() {String updateSql = new SQL() {{UPDATE("PERSON");SET("FIRST_NAME = #{firstName}");WHERE("ID = #{id}");}}.toString();System.out.println(updateSql);}}
3.SQL工具类的方法及作用
4.源码分析
public class SQL extends AbstractSQL<SQL> {@Overridepublic SQL getSelf() {return this;}}
分析:
- SQL继承至AbstractSQL类,只重写了该类的getSelf()方法
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();}.......}
分析:
- 所有的功能由AbstractSQL类完成,AbstractSQL类中维护了一个SQLStatement内部类的实例和一系列前面提到过的构造SQL语句的方法,例如SELECT()、UPDATE()等方法。
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}.....}
分析:
- 当执行SELECT().WHERE()这些方法时,就是SQL关键字加入对应数组,重点我们关注toString方法。
@Overridepublic 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