持久层框架之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依赖包

<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>${pagehelper.version}</version>
</dependency>

<!--pageHelper使用了springboot的自动装配功能,
springboot启动时自动装配pageHelper相关的bean,所以在开发时无需手动添加任何注解,
spring.factories文件配置了自动配置类,Springboot启动时自动加载该对象到容器中 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-autoconfigure</artifactId>
    <version>${pagehelper-spring-boot-autoconfigure.version}</version>
</dependency>

<!--pagehelper-spring-boot-starter启动包,
该包通过spring.provides配置文件把需要依赖的相关Jar包导入的工程中。
因此在springboot工程中只需要把pagehelper-spring-boot-starter引入即可,相关Jar包会自动导入
provides内容: pagehelper-spring-boot-autoconfigure,pagehelper,mybatis-spring-boot-autoconfigure,mybatis,mybatis-spring;
实际上这个启动包非必要的,可要直接引入相关的jar即可
 -->
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper-spring-boot-starter</artifactId>
	<version>${pagehelper-spring-boot-starter.version}</version>
</dependency>

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

核心类就是PageHelper和PageInfo

PageHelper.startPage(pageNum,pageSize);//设置分页属性pageNum(第几页)和pageSize(每页显示的条数)
List<Users> list = userMapper.getUserList();//查询总数
PageInfo<Users> 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方式

业务批量操作最佳实践

批量新增

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

批量更新

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

批量删除

<delete id="batchDel" parameterType="java.util.List">
delete from User where id in
<foreach collection="list" index="index" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</delete>
<!-- 参数类型是List<String>,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
@RequestMapping("add")
public void add(HttpServletRequest request) throws Exception {
	List<Course> cList = testService.queryByName("key");//查询
	System.out.println(Thread.currentThread().getName() + "clist.size:" + cList.size());
	if (cList.size() == 0) {
		if (redisUtil.setnx("name_" + "key", "key") == 1) {//如果数据存在则返回0,不存在返回1
			Course course = new Course();
			course.setCname("key");
			try {
				testService.add(course);//插入
			} catch (Exception e) {
				redisUtil.del("name_" + "key");//插入出异常则删除
				throw e;
			}
			System.out.println(Thread.currentThread().getName() + "success");
			redisUtil.expire("name_" + "key", 3);//防止业务处理过程出现异常无法释放锁所以设置锁失效时间3秒
		} else {
			System.out.println(Thread.currentThread().getName() + "exists");
		}
	} else {
		System.out.println(Thread.currentThread().getName() + "false");
	}
}

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

猜你喜欢

转载自blog.csdn.net/qq_34020761/article/details/132715739