MyBatis3源码深度解析(九)MyBatis常用工具类(二)ScriptRunner&SqlRunner

这篇具有很好参考价值的文章主要介绍了MyBatis3源码深度解析(九)MyBatis常用工具类(二)ScriptRunner&SqlRunner。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

3.2 使用ScriptRunner执行脚本

3.2.1 ScriptRunner工具类简介

ScriptRunner是MyBatis提供的读取脚本文件中的SQL语句并执行的工具类。

它提供了一些属性,用于控制执行SQL脚本的一些行为:

源码1:org/apache/ibatis/jdbc/ScriptRunner.java

public class ScriptRunner {
    // 执行脚本遇到异常时是否中断执行
    private boolean stopOnError;
    // 是否抛出SQLWarning警告
    private boolean throwWarning;
    // 是否自动提交
    private boolean autoCommit;
    // 属性为true时,批量执行文件中的SQL语句
    // 属性为false时,逐条执行文件中的SQL语句
    private boolean sendFullScript;
    // 是否取出windows系统换行符中的 \r
    private boolean removeCRs;
    // 设置Statement属性是否支持转义处理
    private boolean escapeProcessing = true;
    // 日志输出位置,默认是标准输入输出,即控制台
    private PrintWriter logWriter = new PrintWriter(System.out);
    // 错误日志输出位置,默认是标准输入输出,即控制台
    private PrintWriter errorLogWriter = new PrintWriter(System.err);
    // 脚本文件中SQL语句的分隔符,默认是分号
    private String delimiter = DEFAULT_DELIMITER;
    // 是否支持SQL语句分割符,单独占一行
    private boolean fullLineDelimiter;
    // ......
}

3.2.2 ScriptRunner工具类示例

  1. 编写脚本文件 insert-data.sql ,并放在resources目录下:
insert into user (name, age, phone, birthday) values('U1', 15, '18705464523', '2024-03-09');
insert into user (name, age, phone, birthday) values('U2', 16, '18705464523', '2024-03-10');
insert into user (name, age, phone, birthday) values('U3', 17, '18705464523', '2024-03-11');
insert into user (name, age, phone, birthday) values('U4', 18, '18705464523', '2024-03-12');
insert into user (name, age, phone, birthday) values('U5', 19, '18705464523', '2024-03-13');
  1. 编写测试代码:
@Test
public void testScriptRunner() {
    try {
        Connection connection = DbUtils.getConnection();
        ScriptRunner scriptRunner = new ScriptRunner(connection);
        scriptRunner.runScript(Resources.getResourceAsReader("insert-data.sql"));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
  1. 执行测试代码,控制台打印了5条SQL语句,数据库插入了这5条数据:

mybatis sqlrunner文档,MyBatis3源码深度解析,mybatis,后端,java,数据库
如示例代码所示,ScriptRunner工具类的构造方法需要需要一个Connection对象作为参数。创建ScriptRunner对象后,调用该对象的runScript()方法,传入一个SQL脚本文件的Reader对象,即可执行脚本文件中的SQL语句。

3.2.3 ScriptRunner工具类源码

源码2:org/apache/ibatis/jdbc/ScriptRunner.java

public void runScript(Reader reader) {
    // 设置事务是否自动提交
    setAutoCommit();
    try {
        if (sendFullScript) {
            // 批量执行文件中的SQL语句
            executeFullScript(reader);
        } else {
            // 逐条执行文件中的SQL语句
            executeLineByLine(reader);
        }
    } finally {
        rollbackConnection();
    }
}

由 源码2 可知,runScript()方法首先会调用setAutoCommit方法使事务的自动提交配置生效,实际上就是将autoCommit属性的值设置到Connection对象中。

该方法接下来根据sendFullScript属性的值,判断出是批量执行文件中的SQL语句,还是逐条执行文件中的SQL语句。

源码3:org/apache/ibatis/jdbc/ScriptRunner.java

private void executeFullScript(Reader reader) {
    StringBuilder script = new StringBuilder();
    try {
        // 逐行读取脚本文件的SQL语句,并以LINE_SEPARATOR拼接
        BufferedReader lineReader = new BufferedReader(reader);
        String line;
        while ((line = lineReader.readLine()) != null) {
            script.append(line);
            script.append(LINE_SEPARATOR);
        }
        String command = script.toString();
        println(command);
        // 执行拼接起来的SQL语句
        executeStatement(command);
        // 提交事务
        commitConnection();
    } // catch ......
}

private void executeStatement(String command) throws SQLException {
    try (Statement statement = connection.createStatement()) {
        statement.setEscapeProcessing(escapeProcessing);
        String sql = command;
        if (removeCRs) {
            // removeCRs属性是指是否取出windows系统换行符中的\r
            sql = sql.replace("\r\n", "\n");
        }
        try {
            // 执行SQL语句
            boolean hasResults = statement.execute(sql);
            while (!(!hasResults && statement.getUpdateCount() == -1)) {
                checkWarnings(statement);
                printResults(statement, hasResults);
                hasResults = statement.getMoreResults();
            }
        } // catch ...
    }
}

由 源码3 可知,批量执行SQL语句的方法是executeFullScript(),该方法会逐行读取脚本文件的SQL语句,并以LINE_SEPARATOR(如果是Linux系统,则是"\n";如果是Windows系统,则是"\r\n")拼接起来,然后直接执行这个拼接起来的SQL语句,并提交事务。

源码4:org/apache/ibatis/jdbc/ScriptRunner.java

private void executeLineByLine(Reader reader) {
    StringBuilder command = new StringBuilder();
    try {
        BufferedReader lineReader = new BufferedReader(reader);
        String line;
        while ((line = lineReader.readLine()) != null) {
            // 读一行,处理一行
            handleLine(command, line);
        }
        // 提交事务
        commitConnection();
        checkForMissingLineTerminator(command);
    } // catch ......
}

private void handleLine(StringBuilder command, String line) throws SQLException {
    String trimmedLine = line.trim();
    if (lineIsComment(trimmedLine)) {
        // 该行是注释:以"//"或"--"开头
        Matcher matcher = DELIMITER_PATTERN.matcher(trimmedLine);
        if (matcher.find()) {
            delimiter = matcher.group(5);
        }
        println(trimmedLine);
    } else if (commandReadyToExecute(trimmedLine)) {
        // 该行是待执行的命令
        // 获取该行分号之前的内容
        command.append(line, 0, line.lastIndexOf(delimiter));
        // 添加一个分号
        command.append(LINE_SEPARATOR);
        println(command);
        // 执行SQL语句
        executeStatement(command.toString());
        command.setLength(0);
    } else if (trimmedLine.length() > 0) {
        // 如果不是待执行的命令,则说明这条SQL还没结束
        // 则追加到上一行内容
        command.append(line);
        command.append(LINE_SEPARATOR);
    }
}

// 判断某行是否是注释:以"//"或"--"开头
private boolean lineIsComment(String trimmedLine) {
    return trimmedLine.startsWith("//") || trimmedLine.startsWith("--");
}

// 判断某行是否是待执行的命令
// 如果分割符没有占一行,则必须包括分号
// 如果分割符占一行,则只能是分号
private boolean commandReadyToExecute(String trimmedLine) {
    return !fullLineDelimiter && trimmedLine.contains(delimiter) || fullLineDelimiter && trimmedLine.equals(delimiter);
}

由 源码4 可知,逐行执行SQL语句的方法是executeLineByLine(),该方法会逐行读取脚本文件的SQL语句,然后直接调用handleLine()方法进行处理。

handleLine()方法中,首先会判断这一行内容是否是SQL注释(以"//“或”–"开头),如果是则打印注释内容;

其次判断这一行内容是否是待执行的命令(包含分号),如果是则立即调用Statement对象的execute()方法执行SQL语句;

如果既不是注释,又不是命令,则说明这条SQL还没结束,需要追加到上一行内容中。

3.3 使用SqlRunner操作数据库

3.3.1 SqlRunner工具类简介

SqlRunner是MyBatis提供的用于操作数据库的工具类,它对JDBC做了很好的封装,结合SQL工具类,可以方便地通过Java代码执行SQL语句并检索SQL执行结果。

SqlRunner工具类提供了几个操作数据库的方法:

源码5:org/apache/ibatis/jdbc/SqlRunner.java

// 关闭Connection对象
public void closeConnection() {...}

// 执行SELECT语句,只返回1条记录,如果查询结果行数不等于1,则抛出异常
// SQL语句中可以使用占位符,可变参数args为占位符赋值
public Map<String, Object> selectOne(String sql, Object... args) throws SQLException {...}

// 执行SELECT语句,但返回多条记录,返回值中每个Map对象就是一行记录
// SQL语句中可以使用占位符,可变参数args为占位符赋值
public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException {...}

// 分别执行INSERT、UPDATE、DELETE语句,插入、更新、删除一条数据
public int insert(String sql, Object... args) throws SQLException {...}
public int update(String sql, Object... args) throws SQLException {...}
public int delete(String sql, Object... args) throws SQLException {...}

// 执行任意一条SQL语句,最好为DDL语句
public void run(String sql) throws SQLException {...}

3.3.2 SqlRunner工具类示例

@Test
public void testSqlRunner() {
    try {
        Connection connection = DbUtils.getConnection();
        SqlRunner sqlRunner = new SqlRunner(connection);
        // 插入一条记录
        String insertSql = new SQL() {{
            INSERT_INTO("user");
            INTO_COLUMNS("name", "age", "phone", "birthday");
            INTO_VALUES("?,?,?,?");
        }}.toString();
        sqlRunner.insert(insertSql, "王母娘娘", 1000, "12530", "0000-05-21");
        System.out.println("执行INSERT语句成功");
        // 查询该条记录
        String selectSql = new SQL() {{
            SELECT("*");
            FROM("user");
            WHERE("name = ?");
        }}.toString();
        Map<String, Object> resultMap = sqlRunner.selectOne(selectSql, "王母娘娘");
        System.out.println(resultMap);
        // 修改该条记录
        String updateSql = new SQL() {{
            UPDATE("user");
            SET("phone = ?");
            WHERE("name = ?");
        }}.toString();
        sqlRunner.update(updateSql, "12345", "王母娘娘");
        System.out.println("执行UPDATE语句成功");
        // 再次查询该条记录
        resultMap = sqlRunner.selectOne(selectSql, "王母娘娘");
        System.out.println(resultMap);
        // 删除这条记录
        String deleteSql = new SQL() {{
            DELETE_FROM("user");
            WHERE("name = ?");
        }}.toString();
        sqlRunner.delete(deleteSql, "王母娘娘");
        System.out.println("执行DELETE语句成功");
        // 再次查询该条记录
        resultMap = sqlRunner.selectOne(selectSql, "王母娘娘");
        System.out.println(resultMap);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

控制台打印执行结果:

执行INSERT语句成功
{PHONE=12530, ID=20, BIRTHDAY=0001-05-21 00:00:00.0, NAME=王母娘娘, AGE=1000}
执行UPDATE语句成功
{PHONE=12345, ID=20, BIRTHDAY=0001-05-21 00:00:00.0, NAME=王母娘娘, AGE=1000}
执行DELETE语句成功

java.lang.RuntimeException: java.sql.SQLException: Statement returned 0 results where exactly one (1) was expected.

在以上案例中,先后执行SqlRunner工具类的insert()→selectOne()→update()→selectOne()→delete()→selectOne()方法,测试了SqlRunner工具类的增删改查的功能。

可以发现,最后一次执行selectOne()方法时抛出异常,提示返回0条记录但却使用了selectOne()方法,这说明delete()方法确实生效了。

3.3.3 SqlRunner工具类源码解读

selectAll()方法为例,研究SqlRunner工具类的具体实现。

源码6:org/apache/ibatis/jdbc/SqlRunner.java

public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException {
    try (PreparedStatement ps = connection.prepareStatement(sql)) {
        // 为参数占位符设置参数
        setParameters(ps, args);
        // 执行查询操作
        try (ResultSet rs = ps.executeQuery()) {
            // 处理结果
            return getResults(rs);
        }
    }
}

由 源码6 可知,selectAll()方法的逻辑有三步:

(1)调用Connection对象的prepareStatement()方法获取PreparedStatement对象,并调用setParameters()方法为SQL语句中的占位符设置参数;
(2)调用PreparedStatement的executeQuery()方法执行查询操作;
(3)调用getResults()方法将ResultSet对象转换为List集合,其中List集合中每一个Map对象对应数据库中的一条记录。

源码7:org/apache/ibatis/jdbc/SqlRunner.java

private void setParameters(PreparedStatement ps, Object... args) throws SQLException {
    // 遍历参数
    for (int i = 0, n = args.length; i < n; i++) {
        // 参数为空,直接抛出异常
        if (args[i] == null) {
            // throw ...
        }
        if (args[i] instanceof Null) {
            // 参数是Null类型的,则为占位符设置null
            ((Null) args[i]).getTypeHandler().setParameter(ps, i + 1, null, ((Null) args[i]).getJdbcType());
        } else {
            // 正常参数,根据参数类型获取对应的TypeHandler
            TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(args[i].getClass());
            if (typeHandler == null) {
                // TypeHandler对象没有获取到,抛出异常
                // throw ...
            } else {
                // 使用TypeHandler对象为占位符设置参数
                typeHandler.setParameter(ps, i + 1, args[i], null);
            }
        }
    }
}

由 源码7 可知,setParameters()方法会对参数进行遍历,逐个处理:

(1)如果参数为空,直接抛出异常;
(2)如果参数是Null类型的,则为占位符设置null值;
(3)如果是正常参数,则先根据参数类型获取对应的TypeHandler;TypeHandler对象没有获取到,则抛出异常;获取到了则调用TypeHandler对象的setParameter()方法为占位符设置参数。

源码:org/apache/ibatis/jdbc/SqlRunner.java

private List<Map<String, Object>> getResults(ResultSet rs) throws SQLException {
    List<Map<String, Object>> list = new ArrayList<>();
    List<String> columns = new ArrayList<>();
    List<TypeHandler<?>> typeHandlers = new ArrayList<>();
    // 1.获取ResultSetMetaData对象,通过该对象获取所有列名
    ResultSetMetaData rsmd = rs.getMetaData();
    for (int i = 0, n = rsmd.getColumnCount(); i < n; i++) {
        columns.add(rsmd.getColumnLabel(i + 1));
        try {
            // 2.获取列的JDBC类型
            Class<?> type = Resources.classForName(rsmd.getColumnClassName(i + 1));
            // 根据列的JDBC类型获取对应的TypeHandler对象
            TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(type);
            // 如果没有获取到则获取Object对象的TypeHandler对象
            if (typeHandler == null) {
                typeHandler = typeHandlerRegistry.getTypeHandler(Object.class);
            }
            typeHandlers.add(typeHandler);
        } catch (Exception e) {
            typeHandlers.add(typeHandlerRegistry.getTypeHandler(Object.class));
        }
    }
    // 遍历ResultSet对象,将ResultSet对象中的记录行转换为Map对象
    while (rs.next()) {
        Map<String, Object> row = new HashMap<>();
        for (int i = 0, n = columns.size(); i < n; i++) {
            String name = columns.get(i);
            TypeHandler<?> handler = typeHandlers.get(i);
            // 往Map对象中添加一行数据
            // key - 列名,转为大写
            // value - 通过TypeHandler对象的getResult方法将JDBC类型转换为JAVA类型数据
            row.put(name.toUpperCase(Locale.ENGLISH), handler.getResult(rs, name));
        }
        list.add(row);
    }
    return list;
}

由 源码 可知,getResults()方法的处理逻辑是:

(1)获取ResultSetMetaData对象,该对象封装了结果集的元数据信息,包括所有的字段名称及列的数量等信息;
(2)遍历所有列,获取每一列的JDBC类型,根据JDBC类型获取对应的TypeHandler对象,如果没有获取到则获取Object对象的TypeHandler对象,最后将TypeHandler对象注册到变量名为typeHandlers的List集合中。
(3)遍历ResultSet对象,将ResultSet对象中的记录行转换为Map对象。这个Map对象的key是列名(转为大写),value是通过TypeHandler对象的getResult()方法将JDBC类型数据转换为JAVA类型数据。

本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析文章来源地址https://www.toymoban.com/news/detail-840988.html

到了这里,关于MyBatis3源码深度解析(九)MyBatis常用工具类(二)ScriptRunner&SqlRunner的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【23JavaScript 正则表达式】深入解析JavaScript正则表达式:基础概念、常用方法和实例详解,轻松掌握强大的文本模式匹配工具

    正则表达式是一种强大的文本模式匹配工具,用于在字符串中搜索和操作特定的文本模式。在JavaScript中,正则表达式提供了一种灵活的方式来处理字符串操作。 在JavaScript中,可以通过使用字面量表示法或RegExp对象来创建正则表达式。 字面量表示法 RegExp对象 JavaScript中的正则

    2024年02月08日
    浏览(64)
  • 分享20+个在线工具网站,60+常用工具

    💂 个人网站:【工具大全】【游戏大全】【神级源码资源网】 🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】 💅 寻找学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 今天给大家分享20+在线工具网站和 60个常用在线工具+前端工具 欢迎大家访问:h

    2024年02月09日
    浏览(50)
  • IDEA常用工具&配置

    IDEA常用工具配置 如果发现插件市场用不了,可以设置Http Proxy,在该界面上点击”Check connection“并输入的地址:https://plugins.jetbrains.com/ 。 一、常用插件 1、MybatisX Mybaits Plus插件,支持java与xml互转 2、FindBugs-IDEA 检测代码中可能的bug及不规范的位置,写完代码后检测下 避免低

    2024年02月12日
    浏览(38)
  • 开发规范及常用工具

    entity : 是与数据库一一对应的字段 vo : 返回给前端的视图对象 dto : 前端传过来的参数封装成dto,用于返回给前端的对象,一般用于查询操作。 POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。 注意:数据库中拼接字段中间使用下划线(_)进行分割,而实体类中拼接字段采用的是驼峰映射

    2024年02月01日
    浏览(43)
  • 产品经理常用工具汇总

    英文名称 中文名称 描述 Axure 原型 原型图,流程图,框架图,原型图; Axhub 团队原型共享 Axure原型团队共享,链接转发; iconfont 阿里矢量图标 图标下载,协助原型和方案; visio 流程图  业务流程图,泳道图编写; OBS 录屏 录屏工具 EV录屏 EV录屏 录屏工具 腾讯文档 腾讯文

    2024年04月24日
    浏览(37)
  • 常用工具类

    鼠标放在方法上按 Alt + F7 :查找该方法被哪些类所调用 Alt + Insert :生成 get set 构造方法 等 Ctrl + E :查看最近浏览过的文件,方便切换 ObjectUtils.isNull() 与 ObjectUtils.isEmpty() 前者只在对象为null是返回true。 后者在对象为null和空(比如字符串为\\\"\\\",比如一个list、map等不为null,但

    2024年02月09日
    浏览(38)
  • MySQL中常用工具

    ♥️ 作者:小刘在C站 ♥️ 个人主页:  小刘主页  ♥️ 努力不一定有回报,但一定会有收获加油!一起努力,共赴美好人生! ♥️ 学习两年总结出的运维经验,以及思科模拟器全套网络实验教程。专栏: 云计算技术 ♥️小刘私信可以随便问,只要会绝不吝啬,感谢CSD

    2024年02月12日
    浏览(52)
  • 常用工具记录

    代码管理(SCM):GitHub、GitLab、BitBucket、SubVersion 构建工具:Ant、Gradle、maven 自动部署:Capistrano、CodeDeploy 持续集成(CI):Bamboo、Hudson、Jenkins 配置管理:Ansible、Chef、Puppet、SaltStack、ScriptRock GuardRail 容器:Docker、LXC、第三方厂商如AWS 编排:Kubernetes、Core、Apache Mesos、DC/OS 服

    2024年02月21日
    浏览(35)
  • java常用工具类

    Arrays:这是一个操作数组的工具类,提供了如排序、查找等功能。 Collections:这个类提供了大量的静态方法,用于操纵和处理集合类,如List、Set和Map。 Math:这个类包含用于执行基本数学运算的方法,如最初级的加、减、乘、除,还有取绝对值,平方根,取最大最小值等。

    2024年04月15日
    浏览(41)
  • 6 - 常用工具类

    目录 1. Scanner 扫描控制台输入 1.1 扫描控制台输入 1)nextLine 2)nextInt 3)其他方法 1.2 扫描文件 1.3 查找匹配项 2. Arrays 数组工具 2.1 创建数组 1)copyOf 2)copyOfRange 3)fill 2.2 比较数组 2.3 数组排序 2.4 数组检索 2.5 数组转流 2.6 打印数组 2.7 数组转 List 2.8 setAll 2.9 parallelPrefix 3. S

    2024年01月16日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包