当前位置: 首页 > news >正文

MyBatis知识点

目录

基础

说说什么是MyBatis

ORM是什么

为什么MyBatis是半自动ORM映射工具,它与全自动的区别在哪里?

Hibernate和MyBatis的区别?

MyBatis的使用过程,生命周期

 MyBatis的生命周期

 在Mapper中如何传递多个参数?

实体类属性名和表中字段名不一样?

#{}和${}的区别

模糊查询like怎么写?

MyBatis是否支持延迟加载

 MyBatis支持动态sql吗

 MyBatis如何执行批量操作

MyBatis的一级和二级缓存

MyBatis的工作原理

MyBatis的功能架构

​编辑 

Mapper接口没有实现类的原因

Executor执行器

​编辑 

 指定Executor

MyBatis的插件运行原理

分页的实现

JDBC的执行流程

Statement和PreparedStatement的区别

基础

说说什么是MyBatis

  • Mybatis 是一个半 ORM(对象关系映射)框架,它内部封装了 JDBC,开发时只需要关注 SQL 语句本身,不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。程序员直接编写原生态 sql,可以严格控制 sql 执行性能,灵活度高。
  • MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO 映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

缺点:

  • SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有一定要求
  • SQL 语句依赖于数据库,导致数据库移植性差,不能随意更换数据库

ORM是什么

ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单 Java 对象(POJO)的映射关系的技术。

为什么MyBatis是半自动ORM映射工具,它与全自动的区别在哪里?

Hibernate属于全自动ORM映射工具,使用Hibernate查询对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。

而MyBatis在查询关联对象或关联集合对象时,需要手动编写SQL来完成,所以,被称为半自动ORM映射工具。

JDBC编程有哪些不足之处,MyBatis是如何解决的?

1、数据连接创建,释放频繁造成资源浪费从而影响系统性能,在MyBatis-config.xml中配置数据连接池,使用连接池统一管理数据库连接。

2、sql语句写在代码中造成代码不易维护,将sql语句配置在XXXXmapper.xml文件中与Java代码分离。

3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。MyBatis自动将Java对象映射到sql语句。

4、对结果集解析麻烦,sql语句变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便,MyBatis自动将sql执行结果映射到Java对象。

Hibernate和MyBatis的区别?

相同点:

都是对JDBC的封装,都是应用于持久层的框架。

不同点:

映射关系:

MyBatis 是一个半自动映射的框架,配置 Java 对象与 sql 语句执行结果的对应关系,多表关联关系配置简单
Hibernate 是一个全表映射的框架,配置 Java 对象与数据库表的对应关系,多表关联关系配置复杂

SQL优化和移植性

  • Hibernate 对 SQL 语句封装,提供了日志、缓存、级联(级联比 MyBatis 强大)等特性,此外还提供 HQL(Hibernate Query Language)操作数据库,数据库无关性支持好,但会多消耗性能。如果项目需要支持多种数据库,代码开发量少,但 SQL 语句优化困难。
  • MyBatis 需要手动编写 SQL,支持动态 SQL、处理列表、动态生成表名、支持存储过程。开发工作量相对大些。直接使用 SQL 语句操作数据库,不支持数据库无关性,但 sql 语句优化容易。

MyBatis和Hibernate适用场景

  • Hibernate 是标准的 ORM 框架,SQL 编写量较少,但不够灵活,适合于需求相对稳定,中小型的软件项目,比如:办公自动化系统
  • MyBatis 是半 ORM 框架,需要编写较多 SQL,但是比较灵活,适合于需求变化频繁,快速迭代的项目,比如:电商网站

MyBatis的使用过程,生命周期

创建 SqlSessionFactory

 可以从配置或者直接编码来创建 SqlSessionFactory

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = newSqlSessionFactoryBuilder().build(inputStream);

 通过 SqlSessionFactory 创建 SqlSession

SqlSession(会话)可以理解为程序和数据库之间的桥梁

SqlSession session = sqlSessionFactory.openSession();

 通过sqlsession执行数据库操作

可以通过Sqlsession实例来直接执行已映射的sql语句

Blog blog = (Blog)session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);

 更常用的方式是先获取 Mapper(映射),然后再执行 SQL 语句:

BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);

调用session.commit提交事务

如果是更新,删除语句,我们还需要提交一下事务

调用session.close关闭会话

 MyBatis的生命周期

SqlSessionFactoryBuilder

一旦创建了SqlSessionFactoryBuilder,就不再需要它了。因此SqlSessionFactoryBuilder实例的生命周期只存在方法的内部。

SqlSessionFactroy

SqlSessionFactory 是用来创建 SqlSession 的,相当于一个数据库连接池,每次创建 SqlSessionFactory 都会使用数据库资源,多次创建和销毁是对资源的浪费。所以 SqlSessionFactory 是应用级的生命周期,而且应该是单例的。

SqlSession

SqlSession 相当于 JDBC 中的 Connection,SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的生命周期是一次请求或一个方法。

Mapper

映射器是一些绑定映射语句的接口。映射器接口的实例是从 SqlSession 中获得的,它的生命周期在 sqlsession 事务方法之内,一般会控制在方法级

 在Mapper中如何传递多个参数?

 方法 1:顺序传参法

public User selectUser(String name, int deptId);<select id="selectUser" resultMap="UserResultMap">select * from userwhere user_name = #{0} and dept_id = #{1}
</select>
  • \#{}里面的数字代表传入参数的顺序。
  • 这种方法不建议使用,sql 层表达不直观,且一旦顺序调整容易出错。

方法 2:@Param 注解传参法

public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);<select id="selectUser" resultMap="UserResultMap">select * from userwhere user_name = #{userName} and dept_id = #{deptId}
</select>
  • \#{}里面的名称对应的是注解@Param 括号里面修饰的名称。
  • 这种方法在参数不多的情况还是比较直观的,(推荐使用)。

方法 3:Map 传参法

public User selectUser(Map<String, Object> params);<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">select * from userwhere user_name = #{userName} and dept_id = #{deptId}
</select>

\#{}里面的名称对应的是 Map 里面的 key 名称。
这种方法适合传递多个参数,且参数易变能灵活传递的情况。

方法 4:Java Bean 传参法

public User selectUser(User user);

<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>

 

  • \#{}里面的名称对应的是 User 类里面的成员属性。
  • 这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用。(推荐使用)。

实体类属性名和表中字段名不一样?

  • 第 1 种: 通过在查询的 SQL 语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
  • 第 2 种: 通过 resultMap 中的<result>来映射字段名和实体类属性名的一一对应的关系。

#{}和${}的区别

#{} 是预编译处理,${} 是字符串替换。

当使用 #{} 时,MyBatis 会在 SQL 执行之前,将占位符替换为问号 ?,并使用参数值来替代这些问号。由于 #{} 使用了预处理,所以能有效防止 SQL 注入,确保参数值在到达数据库之前被正确地处理和转义。

②、当使用 ${} 时,参数的值会直接替换到 SQL 语句中去,而不会经过预处理。存在sql注入的风险

模糊查询like怎么写?

  • 1 ’%${question}%’ 可能引起 SQL 注入,不推荐
  • "%"#{question}"%" 注意:因为#{…}解析成 sql 语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。
  • CONCAT('%',#{question},'%') 使用 CONCAT()函数,(推荐 ✨)
  • 4 使用 bind 标签(不推荐)

MyBatis是否支持延迟加载

  • Mybatis 支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
  • 它的原理是,使用 CGLIB 创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用 a.getB().getName(),拦截器 invoke()方法发现 a.getB()是 null 值,那么就会单独发送事先保存好的查询关联 B 对象的 sql,把 B 查询上来,然后调用 a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName()方法的调用。这就是延迟加载的基本原理。                                                        

 MyBatis支持动态sql吗

  • if

根据条件来组成 where 子句

<select id="findActiveBlogWithTitleLike"resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">AND title like #{title}
</if>
</select>
  • choose (when, otherwise)

这个和 Java 中的 switch 语句有点像

<select id="findActiveBlogLike"resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose><when test="title != null">AND title like #{title}</when><when test="author != null and author.name != null">AND author_name like #{author.name}</when><otherwise>AND featured = 1</otherwise>
</choose>
</select>
  • trim (where, set)

  • <where>可以用在所有的查询条件都是动态的情况

<select id="findActiveBlogLike"resultType="Blog">
SELECT * FROM BLOG
<where><if test="state != null">state = #{state}</if><if test="title != null">AND title like #{title}</if><if test="author != null and author.name != null">AND author_name like #{author.name}</if>
</where>
</select>
  • <set> 可以用在动态更新的时候
<update id="updateAuthorIfNecessary">update Author<set><if test="username != null">username=#{username},</if><if test="password != null">password=#{password},</if><if test="email != null">email=#{email},</if><if test="bio != null">bio=#{bio}</if></set>where id=#{id}
</update>
  • foreach

    看到名字就知道了,这个是用来循环的,可以对集合进行遍历

<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where><foreach item="item" index="index" collection="list"open="ID in (" separator="," close=")" nullable="true">#{item}</foreach>
</where>
</select>

 MyBatis如何执行批量操作

第一种:使用foreach标签 

foreach 的主要用在构建 in 条件中,它可以在 SQL 语句中进行迭代一个集合。foreach 标签的属性主要有 item,index,collection,open,separator,close。

  • item   表示集合中每一个元素进行迭代时的别名,随便起的变量名;
  • index   指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用;
  • open   表示该语句以什么开始,常用“(”;
  • separator 表示在每次进行迭代之间以什么符号作为分隔符,常用“,”;
  • close   表示以什么结束,常用“)”。

在使用 foreach 的时候最关键的也是最容易出错的就是 collection 属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有以下 3 种情况:

  1. 如果传入的是单参数且参数类型是一个 List 的时候,collection 属性值为 list
  2. 如果传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为 array
  3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个 Map 了,当然单参数也可以封装成 map,实际上如果你在传入参数的时候,在 MyBatis 里面也是会把它封装成一个 Map 的,map 的 key 就是参数名,所以这个时候 collection 属性值就是传入的 List 或 array 对象在自己封装的 map 里面的 key
<!-- MySQL下批量保存,可以foreach遍历 mysql支持values(),(),()语法 --> //推荐使用
<insert id="addEmpsBatch">INSERT INTO emp(ename,gender,email,did)VALUES<foreach collection="emps" item="emp" separator=",">(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})</foreach>
</insert>
<!-- 这种方式需要数据库连接属性allowMutiQueries=true的支持如jdbc.url=jdbc:mysql://localhost:3306/mybatis?allowMultiQueries=true -->
<insert id="addEmpsBatch"><foreach collection="emps" item="emp" separator=";">INSERT INTO emp(ename,gender,email,did)VALUES(#{emp.eName},#{emp.gender},#{emp.email},#{emp.dept.id})</foreach>
</insert>

 第二种方法:使用 ExecutorType.BATCH

  • Mybatis 内置的 ExecutorType 有 3 种,默认为 simple,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交 sql;而 batch 模式重复使用已经预处理的语句,并且批量执行所有更新语句,显然 batch 性能将更优; 但 batch 模式也有自己的问题,比如在 Insert 操作时,在事务没有提交之前,是没有办法获取到自增的 id,在某些情况下不符合业务的需求。

MyBatis的一级和二级缓存

  1. 一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 SqlSession,各个 SqlSession 之间的缓存相互隔离,当 Session flush 或 close 之后,该 SqlSession 中的所有 Cache 就将清空,MyBatis 默认打开一级缓存。
  2. 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同之处在于其存储作用域为 Mapper(Namespace),可以在多个 SqlSession 之间共享,并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态),可在它的映射文件中配置。

MyBatis的工作原理

 

MyBatis的功能架构

 

API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层-接收到调用请求就会调用数据处理层来完成具体的数据处理

数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作

基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑

Mapper接口没有实现类的原因

动态代理

Mapper类的获取过程

Executor执行器

 

 

SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象

ResuseExecutor:执行update或select,以SQL作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是防止于Map<String,Statement>内,供下一次使用。简而言之,就是重复使用Statement对象

BatchExecutor:执行update,将所有SQL都添加到批处理中,它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同
 

 指定Executor

在 Mybatis 配置文件中,在设置(settings)可以指定默认的 ExecutorType 执行器类型,也可以手动给 DefaultSqlSessionFactory 的创建 SqlSession 的方法传递 ExecutorType 类型参数,如SqlSession openSession(ExecutorType execType)。

配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。
 

MyBatis的插件运行原理

分页的实现

MyBatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页。可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。

分页插件的原理
分页插件的基本原理是使用Mybatis提供的插件接口,实现自定义插件,实现Executor的query方法

在执行查询的时候,拦截待执行的SQL,然后重写SQL,根据dialect方言,添加对应的物理分页语句和物理分页参数

例子:select * from student,拦截 sql 后重写为:select t.* from (select * from student) t limit 0, 10
 

JDBC的执行流程

1.加载数据库驱动

在于数据库建立连接之前,首先需要通过Class.forName()方法加载对应的数据库驱动。这一步确保JDNC驱动注册到了DriverManager类中

Class.forName("com.mysql.cj.jdbc.Driver");

2.建立数据库连接

使用DriverManager.getConnection()方法建立到数据库的连接。这一步需要提供数据库的URL、用户名和密码作为参数

Connection conn = DriverManager.getConnection(
    "jdbc:mysql://localhost:3306/databaseName", "username", "password");

3.创建Statement对象

通过建立的数据库连接对象Connection创建Statement、PreparedStatement或CallableStatement对象,用于执行SQL语句

Statement stmt = conn.createStatement();

或者创建PreparedStatement对象(预编译SQL语句,适用于带参数的SQL):

PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM tableName WHERE column = ?");
pstmt.setString(1, "value");

4.执行SQL语句

使用Statement或PreparedStatement对象执行SQL语句

执行查询语句时,使用executeQuery()方法,它返回ResultSet对象

执行更新语句时,使用executeUpdate()方法,它返回一个整数表示受影响的行数

ResultSet rs = stmt.executeQuery("SELECT * FROM tableName");

或者 

int affectedRows = stmt.executeUpdate("UPDATE tableName SET column = 'value' WHERE condition");

5.处理结果集

如果执行的是查询操作,需要处理ResultSet对象来获取数据

6.关闭资源最后,需要依次关闭ResultSet、Statement和Connection等资源,释放数据库连接等资源

Statement和PreparedStatement的区别

Statement和PreparedStatement都是用于执行 SQL 语句的接口,但它们之间存在几个关键的区别:

①、每次执行Statement对象的executeQuery或executeUpdate方法时,SQL 语句在数据库端都需要重新编译和执行。这适用于一次性执行的 SQL 语句。

Statement 不支持参数化查询。如果需要在 SQL 语句中插入变量,通常需要通过字符串拼接的方式来实现,这会增加 SQL 注入攻击的风险。

②、PreparedStatement 代表预编译的 SQL 语句的对象。这意味着 SQL 语句在PreparedStatement对象创建时就被发送到数据库进行预编译。

之后,可以通过设置参数值来多次高效地执行这个 SQL 语句。这不仅减少了数据库编译 SQL 语句的开销,也提高了性能,尤其是对于重复执行的 SQL 操作。

PreparedStatement 支持参数化查询,即可以在 SQL 语句中使用问号(?)作为参数占位符。通过setXxx方法(如setString、setInt)设置参数,可以有效防止 SQL 注入。

总的来说,PreparedStatement相比Statement有着更好的性能和更高的安全性,是执行 SQL 语句的首选方式,尤其是在处理含有用户输入的动态查询时。
 

http://www.dtcms.com/a/310527.html

相关文章:

  • 烽火HG680-KX-海思MV320芯片-2+8G-安卓9.0-强刷卡刷固件包
  • 电子电气架构 --- 加速48V技术应用的平衡之道
  • 机器学习sklearn:处理缺失值
  • 应用分层
  • 菜鸟教程Shell笔记 数组 运算符 echo命令
  • Qwen2 RotaryEmbedding 位置编码仅仅是第一层有吗
  • 深度学习-梯度爆炸与梯度消失
  • Node.js的用途和安装方法
  • flutter——ColorScheme
  • 第13届蓝桥杯Python青少组中/高级组选拔赛(STEMA)2021年10月24日真题
  • Class28批量归一化
  • java下载word
  • 第七章 愿景14 数据规划
  • 吃透 B + 树:MySQL 索引的底层逻辑与避坑指南
  • SpringMVC全局异常处理+拦截器使用+参数校验
  • Bootstap Vue 之b-form-radio-group 不显示选中状态问题
  • 高并发爬虫的限流策略:aiohttp实现方案
  • 8.1 开始新的学习历程
  • 深入理解 Linux 进程地址空间
  • 一体化智能截流井市场报告:深度解析行业现状与未来增长潜力
  • 【Dart 教程系列第 51 篇】Iterable 中 reduce 函数的用法
  • Vue2 项目实现 Gzip 压缩全攻略:从配置到部署避坑指南
  • 静电释放检测漏报率↓85%!陌讯多模态融合算法在电子厂ESD防护实战解析
  • 【数据可视化-77】中国历年GDP数据可视化分析:Python + Pyecharts 深度洞察(含完整数据、代码)
  • QT中的window()方法/获取到控件最顶部容器
  • Effective C++ 条款16: 成对使用new和delete时要采用相同形式
  • 1、【C语言】【进阶】数组,指针与退化
  • 【Node.js安装注意事项】-安装路径不能有空格
  • Go 语言中 ​10 个高频实用写法
  • C语言:20250801学习(构造类型)