• 持久层框架之Mybatis


    概述

    • MyBatis是apache的一个开源项目iBatis,2010年改名为MyBatis,2013年11月迁移到Github
    • MyBatis是一款优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注SQL本身而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码
    • Mybatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回
    • MyBatis支持自定义SQL、存储过程以及高级映射

    执行流程原理

    • 读取MyBatis的配置文件: mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息
    • 加载映射文件: 映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,需要在MyBatis配置文件mybatis-config.xml中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表
    • 构造会话工厂: 通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory
    • 创建会话对象: 由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法
    • Executor执行器: MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护
    • MappedStatement对象: 在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息
    • 输入参数映射: 输入参数类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输入参数映射过程类似于JDBC对preparedStatement对象设置参数的过程
    • 输出结果映射: 输出结果类型可以是Map、List等集合类型,也可以是基本数据类型和POJO类型。输出结果映射过程类似于JDBC对结果集的解析过程

    SQL映射文件(开发中关注的核心)

    可参考官网:mybatis – MyBatis 3 | XML 映射器

    MyBatis分页插件PageHelper

    导入Maven依赖包

    1. <dependency>
    2. <groupId>com.github.pagehelpergroupId>
    3. <artifactId>pagehelperartifactId>
    4. <version>${pagehelper.version}version>
    5. dependency>
    6. <dependency>
    7. <groupId>com.github.pagehelpergroupId>
    8. <artifactId>pagehelper-spring-boot-autoconfigureartifactId>
    9. <version>${pagehelper-spring-boot-autoconfigure.version}version>
    10. dependency>
    11. <dependency>
    12. <groupId>com.github.pagehelpergroupId>
    13. <artifactId>pagehelper-spring-boot-starterartifactId>
    14. <version>${pagehelper-spring-boot-starter.version}version>
    15. dependency>

    启用分页(PageHelper会自动拼接sql查询数据)

    核心类就是PageHelper和PageInfo

    1. PageHelper.startPage(pageNum,pageSize);//设置分页属性pageNum(第几页)和pageSize(每页显示的条数)
    2. List list = userMapper.getUserList();//查询总数
    3. PageInfo pageInfo = new PageInfo<>(list);//把查询到的结果封装到PageInfo类中

    SpringBoot整合MyBatis

    可参考该博文Spring Boot 整合 Mybatis 实践教程(干货) - 知乎 (zhihu.com)[目前公司微服务应用整合Mybatis过程和该博文介绍基本一致]

    其他

    JDBC VS MyBatis

    传统方式JDBC访问数据库特点

    • 使用JDBC访问数据库有大量重复代码(如注册驱动、获取连接、获取传输器、释放资源等)
    • JDBC自身没有连接池,会频繁的创建连接和关闭连接,效率低
    • SQL是写死在程序中,一旦修改SQL,需要对类重新编译
    • 对查询SQL执行后返回的ResultSet对象,需要手动处,有时会特别麻烦

    mybatis框架访问数据库的特点

    • Mybatis对JDBC对了封装,可以简化JDBC代码
    • Mybatis自身支持连接池(也可以配置其他的连接池),因此可以提高程序的效率
    • Mybatis是将SQL配置在mapper文件中,修改SQL只是修改配置文件,类不需要重新编译
    • 对查询SQL执行后返回的ResultSet对象,Mybatis会帮我们处理,转换成Java对象
    • JDBC中所有的问题(代码繁琐、有太多重复代码、需要操作太多对象、释放资源、对结果的处理太麻烦等)在Mybatis框架中几乎都得到了解决

    Hibernate VS MyBatis 

    • ORM指的是对象关系映射,是一种持久化技术,将面向对象程序中的对象持久化到数据库中的技术; Hibernate和Mybatis属于ORM框架
    • Hibernate比较复杂、庞大,学习周期较长; Mybatis主要依赖于sql的书写,让开发者感觉更熟悉
    • Hibernate与数据库具体的关联都在XML中,适用于不同的数据库; Mybatis依赖数据库编写SQL,所以扩展性、迁移性比较差
    • 如果直接操作数据库表,没有过多的定制,建议使用Hibernate方式; 如果要灵活使用SQL语句,建议采用MyBatis方式

    业务批量操作最佳实践

    批量新增

    1. <insert id="batchSave" parameterType="java.util.List">
    2. insert into User(title,content) values
    3. <foreach collection="list" item="item" index="index" separator=",">
    4. (#{item.title},#{item.content})
    5. </foreach>
    6. </insert>
    7. <!-- 参数类型是List,实体集合 -->
    • MyBatis利用For循环批量插入: 一万条数据总耗时:26348ms
    • MyBatis以集合方式(xml中采用)批量新增(推荐): 一万条数据总耗时:521ms
    • MyBatis-Plus提供的SaveBatch方法: 一万条数据总耗时:24674ms,该问题解决方案可以在数据库配置的uri后面加上该属性后启用批量更新语句的优化rewriteBatchedStatements=true,可以将耗时降低为500ms

    批量更新

    1. <update id="batchUpdate" parameterType="java.util.List">
    2. <foreach collection="list" item="item" index="index" open="" close="" separator=";">
    3. update User
    4. <set>
    5. title = #{item.title}, content = #{item.content}
    6. </set>
    7. where id = #{item.id}
    8. </foreach>
    9. </update>
    10. <!-- 参数类型是List,实体集合 -->

    批量删除

    1. <delete id="batchDel" parameterType="java.util.List">
    2. delete from User where id in
    3. <foreach collection="list" index="index" item="item" open="(" close=")" separator=",">
    4. #{item}
    5. </foreach>
    6. </delete>
    7. <!-- 参数类型是List,id集合 -->

    常见问题 

    Mybatis中#{}和${}的区别

    • #{}是预编译处理,可以有效的防止SQL注入(发生在编译的过程中),提高系统安全性,${}是单纯的字符串替换
    • mybatis在处理#{}时,会将sql中的#{}替换为?号,调用PreparedStatement的set方法来赋,mybatis在处理${}时,就是把${}替换成变量的值
    • 既然${}会引起sql注入,为什么有了#{}还需要有${}呢?那其存在的意义呢
      • #{}主要用于预编译,而预编译的场景其实非常受限,而${}用于替换,很多场景会出现替换,例如SQL片段,抽取可重用的SQL语句

    Mybatis在高并发环境下经常出现数据插入重复的现象(数据库表未设置唯一字段),该如何解决

    • 使用Redis锁,即redis setnx key
      • 判断数据库是否有该记录数据,有的话则终止,没有数据的话,则设置redis锁
      • 用redis锁,因为Redis是单线程模型,同一时刻只能有一个线程会执行成功,假设线程a会成功,其他并发的线程b和c则会失败
      • set key成功的线程a,开始执行插入数据操作,无论最终是否插入数据成功,都在最后del key
    1. @RequestMapping("add")
    2. public void add(HttpServletRequest request) throws Exception {
    3. List cList = testService.queryByName("key");//查询
    4. System.out.println(Thread.currentThread().getName() + "clist.size:" + cList.size());
    5. if (cList.size() == 0) {
    6. if (redisUtil.setnx("name_" + "key", "key") == 1) {//如果数据存在则返回0,不存在返回1
    7. Course course = new Course();
    8. course.setCname("key");
    9. try {
    10. testService.add(course);//插入
    11. } catch (Exception e) {
    12. redisUtil.del("name_" + "key");//插入出异常则删除
    13. throw e;
    14. }
    15. System.out.println(Thread.currentThread().getName() + "success");
    16. redisUtil.expire("name_" + "key", 3);//防止业务处理过程出现异常无法释放锁所以设置锁失效时间3秒
    17. } else {
    18. System.out.println(Thread.currentThread().getName() + "exists");
    19. }
    20. } else {
    21. System.out.println(Thread.currentThread().getName() + "false");
    22. }
    23. }

    Mybatis中的Dao接口和XML文件里的SQL是如何建立关系?

  • 相关阅读:
    SpringSecurity - 认证与授权、自定义失败处理、跨域问题、认证成功/失败处理器
    C#源码 LIS实验室(检验科)信息系统源码 SaaS模式的Client/Server架构
    Junit单元测试异常处理方法
    Linux系统管理指南:用户权限、进程管理和网络配置精解
    04-创建型---原型模式-----掌握深复制
    XCZU19EG板卡设计资料:610-基于6U VPX 的FPGA XCZU19EG存储阵列
    什么是单域名SSL证书?单域名证书如何申请?
    程序设计:C++ 一个用目录结构构建索引的类
    UnityShader_基础理论
    Python - PEP572: 海象运算符
  • 原文地址:https://blog.csdn.net/qq_34020761/article/details/132715739