背景
传统的JDBC开发有什么问题
- 频繁的创建数据库连接对象,释放对象,容易造成系统资源的浪费,影响系统性能
- sql语句定义参数设置,耦合度较高,如果涉及参数修改,则需要重新修改Java代码,系统就需要重新编译,重新发布。代价较高
- 结果集处理存在重复代码,处理麻烦
mybatis如何处理jdbc的通病
数据库连接创建,频繁的释放会影响系统的性能问题,mybatis-config.xml 中配置 数据连接池,使用连接池管理数据库的连接
-
SQL语句写在代码中造成代码不易维护
- 解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离
-
向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参
数一一对应。- 解决: Mybatis自动将java对象映射至sql语句。
-
对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装
成pojo对象解析比较方便。- 解决:Mybatis自动将sql执行结果映射至java对象。
ORM 定义
- 对象关系映射,是一种为了解决关系型数据库数据与简单java对象 POJO 的映射关系的技术
- 简单来说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中
熟知的ORM框架
- Hibernate:
- Hibernate是Java领域最著名的ORM框架之一。它提供了全功能的ORM实现,包括对象关系映射、数据查询、事务处理等。Hibernate支持多种数据库,并具有良好的扩展性和性能。
- MyBatis:
- MyBatis是另一个在Java领域广泛使用的ORM框架。与Hibernate相比,MyBatis更强调SQL的灵活性,它允许开发者编写原生SQL语句,并通过映射文件将结果映射为Java对象。MyBatis适合对SQL性能有较高要求的场景。
- Entity Framework:
- Entity Framework是.NET平台上的ORM框架。它提供了丰富的功能,包括数据模型设计、查询、变更跟踪等。Entity Framework支持LINQ(Language Integrated Query)查询,使得数据操作更加直观和简洁。
为什么说Mybatis是半自动ORM映射工具
MyBatis在SQL控制和对象映射方面提供了较高的灵活性和可控性。相较于全自动ORM工具,如Hibernate,MyBatis的主要特点体现在以下几个方面:
- SQL的明确控制:
- 在MyBatis中,开发人员可以直接书写SQL语句,通过绑定参数和结果集映射来实现对象关系映射的功能。这种方式使得开发人员能够根据自己的需求和对性能的要求,编写高效的SQL语句。
- 与此相比,全自动ORM工具如Hibernate则通常根据对象的属性自动生成SQL语句,这在某些复杂查询或性能要求较高的场景下可能存在一定的局限性。
- 对象映射的自定义:
- MyBatis将查询结果映射成Java对象,但不会自动建立对象之间的关系。开发者需要手动控制对象之间的关系,从而实现关系型数据库和面向对象程序的映射。这种方式更加的灵活性,使得开发者能够应对各种复杂的映射需求。
- 而全自动ORM工具则会自动处理这些映射关系。
- 延迟加载机制:
- MyBatis提供了延迟加载机制,这使得大量数据查询不会同时加载到内存中。当需要使用某个关联对象时,MyBatis才会通过代理模式实现对该对象的延迟加载。这种机制有助于优化性能,特别是在处理大量数据时。
半自动与全自动的区别在哪里?
- 灵活性:
- 全自动ORM:全自动ORM工具通常提供了较为固定的映射方式和查询机制,对于复杂的查询和映射需求可能存在一定的局限性。此外,由于自动生成的SQL语句可能不是最优的,因此在某些性能要求较高的场景下可能需要进行额外的优化。
- 半自动ORM:半自动ORM工具则允许开发者根据实际需求编写和优化SQL语句,从而实现更高效的数据库操作。同时,由于开发者可以自定义映射关系,因此能够更好地处理复杂的数据结构和关系。
- 配置复杂性:
- 全自动ORM:全自动ORM工具通常通过注解或配置文件来定义实体类和映射关系,配置相对简单且直观。
- 半自动ORM:半自动ORM工具则需要开发者手动编写SQL语句和映射配置,配置过程相对繁琐。
- 性能优化:
- 全自动ORM:全自动ORM工具在性能方面可能存在一定的局限性,自动生成的SQL语句可能不是最优的。由于高度抽象化的操作,可能在某些情况下导致额外的性能开销。
- 半自动ORM:半自动ORM工具允许开发者直接编写和优化SQL语句,因此能够更好地控制性能。开发者可以根据实际需求进行索引优化、批量操作等性能提升措施。
mybatis 定义
MyBatis 是一个优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通的 Java 对象)为数据库中的记录。以下是 MyBatis 的主要优点和缺点:
-
优点
-
灵活性:MyBatis 允许开发者直接编写 SQL 语句,这使得开发者能够根据自己的需求精确地控制数据库操作。无论是简单的 CRUD 操作还是复杂的查询,MyBatis 都能提供灵活的支持。
-
解耦:MyBatis 将 SQL 语句从== Java 代码中分离出来,使得业务逻辑与数据库操作更加清晰==、易于维护。这有助于降低代码的耦合度,提高代码的可读性和可维护性。
-
性能优化:由于 MyBatis 允许开发者直接编写 SQL,因此可以更容易地进行性能优化。开发者可以根据实际需求进行索引优化、批量操作等性能提升措施。
-
动态 SQL:MyBatis 提供了强大的动态 SQL 功能,可以根据不同的条件生成不同的 SQL 语句。这使得开发者能够更灵活地处理各种复杂的查询需求。
-
易于集成:MyBatis 可以与多种数据库和 Java Web 框架(如 Spring)进行集成,使得开发者能够更加方便地使用它进行数据库操作。
-
-
缺点
-
SQL 语句的编写和维护:虽然 MyBatis 提供了灵活的 SQL 编写方式,但同时也增加了 SQL 语句的编写和维护成本。开发者需要手动编写和测试 SQL 语句,确保它们的正确性和性能。
-
学习成本:对于初学者来说,学习 MyBatis 的配置和使用可能需要一定的时间和努力。尽管 MyBatis 的文档比较完善,但理解其核心概念和最佳实践仍然需要一定的实践经验。
-
错误处理:由于 MyBatis 允许直接操作数据库,因此开发者需要更加注意错误处理。如果 SQL 语句编写不当或数据库连接出现问题,可能会导致程序崩溃或数据丢失。
-
XML 配置的复杂性:MyBatis 的 XML 配置虽然强大,但也可能变得相当复杂,特别是在大型项目中。管理和维护大量的 XML 文件可能会成为一个挑战。
-
底层原理
Mybatis的工作原理
MyBatis通过读取配置文件、构建SqlSessionFactory、创建SqlSession、使用执行器执行SQL命令、映射处理以及返回结果等步骤,实现了对数据库的持久层操作。其工作原理充分利用了动态代理和反射机制,使得开发者可以更加灵活和方便地进行数据库操作。
- 读取配置文件:MyBatis首先加载配置文件,包括全局配置文件(如SqlMapConfig.xml)和SQL映射文件(如Mapper.xml)。全局配置文件主要配置MyBatis的运行环境信息,如数据源、事务等;而SQL映射文件则配置与SQL执行相关的信息。
- 构建SqlSessionFactory:MyBatis通过读取这些配置文件的信息,构造出SqlSessionFactory,即会话工厂。SqlSessionFactory是MyBatis的核心类,它提供了创建SqlSession的方法。
- 创建SqlSession:通过SqlSessionFactory,MyBatis可以创建出SqlSession对象。SqlSession是一个接口,它包含了执行SQL命令所需的所有方法,如select、insert、update和delete等。
- 执行器(Executor):由于SqlSession本身并不能直接操作数据库,因此它会使用一个执行器(Executor)来帮它执行操作。执行器会根据SqlSession提供的信息,生成对应的SQL语句,并通过JDBC等数据库驱动与数据库进行交互。
- 映射处理:MyBatis会根据映射文件中的配置,将查询结果映射成Java对象,或者将Java对象转换为SQL语句。这一过程通过动态代理和反射机制实现。(MyBatis在映射处理时,可能会使用JDK动态代理或CGLIB代理,这取决于被代理的对象是接口还是类。)
- 返回结果:MyBatis将处理完的结果返回给调用者,完成整个数据库操作过程。
- 简易版:
- 读取
mybatis
配置文件,mybatis-config.xml
加载运行环境和映射文件 - 构建会话工厂
SqlSessionFactory
- 会话工厂创建
SqlSession
对象,包含了执行SQL语句的所有方法 - 操作数据库的接口,Executor执行器,同时负责缓存维护
Executor
接口的执行方法中有一个MappedStatement
类型的参数,封装了映射信息- 输入参数映射
- 输出结果映射
- 读取
Mybatis是如何进行分页的?分页插件的原理是什么?
Mybatis进行分页主要依赖于分页插件,该插件通过拦截器的方式,在SQL执行前将分页参数(如当前页、每页显示的数据条数等)注入到SQL语句中,从而生成对应的分页SQL,实现分页查询。
-
分页插件的核心类是PageInterceptor,其工作原理如下:
- 在Mybatis的配置文件中,添加PageInterceptor类,并设置相应的参数,如SQL ID、数据库类型以及分页SQL的分类方式(如正则表达式)等。
- 当查询请求经过SqlSessionFactory并关闭SqlSession时,会代理执行plugin.wrap方法,将当前分页拦截器注入Executor对象的链式调用中,拦截Query操作并按分页逻辑返回结果。
- 当Executor的查询操作触发时,PageInterceptor会拦截这个操作,并对查询语句进行重新构造,以符合分页的需求。具体来说,它会通过拼接limit和offset来实现翻页逻辑。
- 在分页插件的帮助下,Mybatis可以更加高效和灵活地处理分页查询。开发者只需要配置好分页插件,然后在编写SQL语句时无需关心分页逻辑,Mybatis会自动处理并生成相应的分页SQL。
注意:分页插件的实现原理可能因Mybatis版本的不同而有所差异。
Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理吗?
-
Mybatis动态SQL主要用于根据不同的条件动态生成SQL语句,实现灵活的条件查询和更新操作。通过动态SQL,开发者可以避免编写大量的固定SQL查询,而是根据实际需求构建可变的SQL语句,从而提高代码的重用性和灵活性,并减少SQL注入的风险。
-
Mybatis中提供了多种动态SQL标签,主要包括:
<where>
: 用于在生成的SQL语句中添加WHERE子句,自动处理条件语句的前缀,并在有条件语句存在时添加WHERE关键字。<set>
: 用于在生成的SQL语句中添加SET子句。<foreach>
: 用于在生成的SQL语句中进行循环操作。<if>
: 用于在生成的SQL语句中添加条件判断。<choose>, <when>, <otherwise>
: 类似于Java中的switch语句,根据条件选择执行不同的SQL语句片段。<bind>
: 用于将表达式的结果绑定到一个变量上。
-
执行原理
- 基于解析XML映射文件和处理SQL语句的方式
- 当Mybatis解析XML映射文件时,它会检测到包含动态SQL的语句,并将其转换为内部的数据结构(MappedStatement)。
- 在运行时,Mybatis会根据参数的值和配置的条件逻辑,动态地构建最终的SQL语句。
- 这个过程包括解析XML映射文件,识别动态SQL标签,根据条件动态生成SQL语句,并执行生成的SQL语句。
- 基于解析XML映射文件和处理SQL语句的方式
-
动态SQL采用了预编译的方式来拼接SQL语句,能够有效防止SQL注入攻击。
简述Mybatis的插件运行原理,以及如何编写一个插件。
MyBatis的插件运行原理主要基于责任链模式(Chain of ResponsibilityPattern),允许开发者在MyBatis的核心处理流程中插入自定义的逻辑,从而在不修改MyBatis源代码的情况下扩展其功能。插件在MyBatis中的作用非常强大,几乎可以影响到MyBatis的任意行为。
-
插件的运行原理如下:
-
插件加载与注册:在MyBatis初始化过程中,会加载并注册插件。这个过程通常是通过配置文件中的标签来完成的。每个插件都需要实现Interceptor接口,并提供一个拦截器链(Interceptor Chain)中的位置信息。
-
创建代理对象:当MyBatis需要执行某个操作时(如创建SqlSession、执行SQL语句等),它会检查是否存在针对该操作的插件。如果存在,MyBatis会使用动态代理技术为相关对象创建代理对象,包装真实对象,并在调用真实对象的方法前后插入插件的逻辑。
-
调用插件逻辑:当代理对象的方法被调用时,它会首先执行插件的intercept方法(执行自定义的逻辑,如修改参数、改变SQL语句等)。然后,插件会调用proceed方法继续执行原始操作。
-
-
责任链传递:如果有多个插件注册到同一个拦截点,它们会按照配置的顺序形成一个责任链。当一个操作发生时,MyBatis会按照顺序依次调用每个插件的intercept方法,直到所有插件都执行完毕,最后执行原始操作。
-
如何编写一个MyBatis插件:
-
实现Interceptor接口:编写一个类实现MyBatis的Interceptor接口。这个接口定义了一个intercept方法,你需要在这个方法中编写你的自定义逻辑。
-
定义@Intercepts注解:在插件类上使用@Intercepts注解,并指定要拦截的方法。@Intercepts注解内部可以包含多个@Signature注解,每个@Signature注解代表一个拦截点。你需要指定拦截的方法类型(如Executor、StatementHandler等)、方法名以及参数类型。
-
编写自定义逻辑:在intercept方法中,你可以获取到被拦截方法的参数,并根据你的需求修改这些参数或者执行其他操作。最后,你需要调用proceed方法继续执行原始操作。
-
注册插件:在MyBatis的配置文件中使用标签注册你的插件。指定插件类的全限定名,并配置其他相关参数(如插件的属性)。
- 插件示例:它会在执行SQL语句前打印出SQL内容:
import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.plugin.*; import java.sql.Connection; import java.util.Properties; @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) public class SqlPrintInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); String sql = statementHandler.getBoundSql().getSql(); System.out.println("执行的SQL语句是:" + sql); return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可以设置插件的属性,这里暂时不用 } }
在MyBatis的配置文件中注册这个插件:
<configuration> <!-- 其他配置 --> <plugins> <plugin interceptor="com.example.SqlPrintInterceptor"> <!-- 如果插件有属性,可以在这里配置 --> </plugin> </plugins> </configuration>
-
架构设计
MyBatis的功能架构
-
MyBatis的功能架构主要可以分为三层:
- API接口层:
这一层主要提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。 - 数据处理层:
负责具体的SQL查找、SQL解析、SQL执行以及执行结果映射处理等。它的主要目的是根据调用的请求完成一次数据库操作。 - 基础支撑层:
- 负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理。
- 类型转换模块和日志模块
- 类型转换模块主要负责Java类型与JDBC类型之间的相互转换,这在SQL模板绑定用户传入实参的场景以及将ResultSet映射成结果对象时都有所体现。
- 日志模块则用于集成Java生态中的第三方日志框架,如Log4j、Log4j2、slf4j等,以提供灵活的日志功能。
- API接口层:
Xml映射文件中,除了常见的select|insert|updae|delete标签之外,还有哪些标签?
-
<resultMap>
:定义结果集映射规则,将数据库查询结果与Java对象属性进行映射。 -
<parameterMap>
:(已被弃用)用于定义参数映射,但在较新版本的MyBatis中,这个标签通常被省略,因为MyBatis可以自动处理大多数常见的参数类型。 -
<sql>
:定义可重用的SQL片段,这些片段可以在其他的<select>、<insert>、<update>
或<delete>
标签中通过<include>
标签引用。 -
<include>
:引用之前定义的<sql>
标签中的SQL片段。 -
<selectKey>
:用于获取数据库生成的主键,特别是不支持自增主键的数据库。 -
动态SQL标签:这些标签用于构建动态SQL语句,可以根据条件参数动态生成不同的SQL。
<choose>、<when>、<otherwise>
:类似于Java中的switch-case结构,根据条件选择执行不同的SQL片段。<trim>、<where>、<set>
:对SQL片段进行前后空格或某些字符的去除、合并等操作,特别是处理SQL语句中的条件部分。<if>
:根据条件判断是否包含某个SQL片段。<bind>
:绑定变量到上下文,供后续SQL使用。<foreach>
:用于处理集合类型的参数,通常用于IN查询。
Mybatis的一级、二级缓存
MyBatis 提供了一级缓存和二级缓存机制,优化了数据库访问性能,减少不必要的数据库查询。
-
一级缓存
也称为SqlSession 级别的缓存,它是默认开启的,并且不能被关闭。一级缓存的作用域是一个 SqlSession,当在同一个 SqlSession 中执行两次相同的 SQL 语句时,第一次执行完毕会将结果放到缓存中,第二次查询时会从缓存中获取数据,而不是再次发送 SQL 到数据库。-
特点:
- 生命周期:与 SqlSession 一致,当 SqlSession 关闭或清空时,一级缓存也会被清空。
- 缓存数据范围:只缓存同一个 SqlSession 中执行的 SQL 语句和结果。
- 缓存存储位置:在内存中,因此读取速度快。
-
基于PerpetualCache的HashMap本地缓存,其缓存作用域为Session,当Session进行flush或close 之后,该Session中的所有Cache就将会被清空,默认打开一级缓存
-
-
二级缓存
也称为 Mapper 级别的缓存,它可以跨多个 SqlSession,因此其作用域更大。多个SqlSession 可以共享一个二级缓存区域。需要手动开启,并且是针对namespace 进行的缓存,也就是针对 mapper 进行的缓存。不同的 mapper 查出的数据会放在自己对应的缓存区域中。-
特点:
- 生命周期:与 namespace 绑定,只要 namespace 没有被销毁,缓存数据就存在。
- 缓存数据范围:跨多个 SqlSession,多个 SqlSession 可以共享二级缓存的数据。
- 缓存存储位置:默认是在堆内存中,但也可以配置成其他的存储方式,如 Redis、Memcached 等。
-
基于namespace 和mapper的作用域起作用的,不是依赖于SqlSession默认也是采用PerpetualCache HashMap存储,需要单独开启,一个是核心配置,一个是mapper映射文件
-
二级缓存默认是关闭的
-
开启方式
- 全文配置文件
<settings> <settings name = "cacheEnabled" value = "true"> </settings>
- 映射文件
使用<cache/>
标签让当前mapper生效二级缓存
- 全文配置文件
-
-
使用注意事项
- 当 SqlSession 提交或回滚事务时,会清空一级缓存。
- 当执行增删改操作时,一级缓存和二级缓存都会被清空,因为增删改操作可能会影响到缓存中的数据一致性。
- 二级缓存可能会导致脏读,因此在高并发场景下使用时需要谨慎,确保缓存数据的实时性和一致性。
- 只有会话提交或者关闭之后,一级缓存中的数据才会转移到二级缓存中
- 在使用二级缓存时,建议对查询结果集进行序列化,以避免因对象不一致而导致的问题。
- 如何开启二级缓存:在 MyBatis 的配置文件中,可以通过设置
<settings>
标签的 cacheEnabled 属性为 true 来开启二级缓存。同时,在需要缓存的 mapper XML 文件中,使用 标签来声明开启该 mapper 的二级缓存。
- 如何开启二级缓存:在 MyBatis 的配置文件中,可以通过设置
-
总结
MyBatis 的一级缓存和二级缓存机制可以有效地提高数据库访问性能,减少不必要的数据库查询。但在使用时需要注意缓存数据的一致性和实时性问题,以及在高并发场景下的潜在风险。
Mybatis都有哪些Executor执行器?它们之间的区别是什么?
- SimpleExecutor(简单执行器):
- 默认的执行器类型。它每次执行都会创建一个Statement对象,并立即执行SQL语句。
- 不支持事务,每次都会关闭Statement对象,适用于简单的查询场景。
- SimpleExecutor会进行两次预编译,每次都会构造一个PreparmentStatement对象,因此效率相对较低。
- ReuseExecutor(重用执行器):
- 重用预处理的Statement对象,它会缓存Statement对象在map中,key为sql,value为Statement对象。
- 当需要执行相同的SQL语句时,会直接使用缓存的Statement对象,而不是每次都创建新的对象。
- 不支持事务。因此,执行相同的sql语句只会进行一次预编译,效率更高。
- BatchExecutor(批处理执行器):
- 这种执行器用于批量操作,可以一次执行多个SQL语句。执行update(JDBC批处理不支持select)时,会把所有sql添加到批处理中(addBatch()),等待统一批处理(executorBatch())。
- 缓存了多个Statement对象,每一个Statement都是addBatch()后等待进行executorBatch()批处理。
- BatchExecutor查询时与SimpleExecutor相同会进行多次编译,更新或删除时,会批量进行,需要手动提交。
综上所述,三种执行器的主要区别在于它们处理SQL语句的方式和效率。
- SimpleExecutor适用于简单的查询场景,但效率相对较低;
- ReuseExecutor通过重用Statement对象提高了效率,但同样不支持事务;
- BatchExecutor则专注于批量操作,适用于大量数据的处理场景。
Mybatis中如何指定使用哪一种Executor执行器?
在MyBatis中,执行器(Executor)的类型是由配置决定的,而不是直接在代码中指定的。
-
SIMPLE:这是MyBatis的默认执行器类型,它执行完一次update或select之后,会关闭Statement。
-
REUSE:执行器会重用预处理语句(PreparedStatement)。
-
BATCH:执行器会重复使用语句和批量更新,主要用于批量操作,减少了预处理语句的创建次数,提升了性能。
-
实例:
-
MyBatis的配置文件(mybatis-config.xml)中设置默认的执行器类型。
<settings> <setting name="defaultExecutorType" value="REUSE"/> </settings>
-
通过编程方式动态地改变执行器的类型。例如,你可以通过SqlSessionFactory的openSession方法传递一个ExecutorType参数来指定所需的执行器类型。
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)) { // 在这个session中,MyBatis将使用REUSE类型的执行器 // ... }
-
Mybatis延迟加载的实现原理是什么?
- 延迟加载,也被称为懒加载,主要思想是在用到数据时才进行查询加载,不需要用到时不进行加载。这种策略可以提高程序的性能,节省系统资源,并避免数据重复加载。
-
解析
- 使用GCLIB动态代理创建目标对象的代理对象
- 当调用目标方法时,进行拦截器invoke方法,发现目标方法是null值,执行sql查询
- 获取数据之后,调用set方法设置属性值,再继续查询目标方法,就有值了
-
Mybatis的延迟加载主要是基于嵌套查询来实现的,具体实现原理如下:
-
生成代理对象:当主对象(例如一个实体对象)被查询时,MyBatis会生成一个代理对象,这个代理对象包含了对关联对象的引用。
-
触发时机:当访问代理对象中的关联属性时,延迟加载机制会被触发。此时,MyBatis会拦截对关联属性的访问,并判断该属性是否已经被加载
- 如果未加载,则触发新的SQL查询去加载该属性
- 如果已加载,则直接返回该属性的值。
-
填充数据:查询完成后,MyBatis会将查询到的数据填充到代理对象中,使得关联属性变得可用。
MyBatis的动态代理机制是实现延迟加载的关键。通过动态代理,MyBatis能够在运行时生成代理对象,以拦截对关联属性的访问,并在需要时执行额外的查询。这样,系统就能够根据实际需求,智能地加载数据,避免在一开始就加载过多不必要的信息。
-
-
配置使用
- mybatis支持一对一关联对象和一对多关联集合对象的延迟加载
- 在mybatis配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false 默认是关闭的
-
场景考虑
大批量的数据查询时,由于每次访问关联属性都需要进行数据库查询,可能会消耗较多的时间,造成用户请求响应慢,影响用户体验。- 在一对多、多对多的关系中,通常推荐使用延迟加载;
- 在一对一、多对一的关系中,可以考虑使用立即加载。
映射器
{}和${}的区别
-
处理参数的方式:
#{}
:MyBatis在处理时,会将SQL语句中的#{}替换为?,即占位符,然后使用PreparedStatement的set方法来赋值。${}
:MyBatis在处理时,会直接将SQL语句中的${}替换为参数的值。
-
SQL注入问题:
- 由于
#{}
是预编译处理,因此它不会出现通过SQL注入改变SQL语句结构的问题。 - 而
${}
是直接替换,所以可能会出现SQL注入的问题。
- 由于
-
使用场景:
#{}
适用于大多数情况,特别是当参数是基本数据类型或对象属性时。${}
则主要用于一些特殊场景,如动态表名、列名等。因为在这些情况下,预编译处理可能无法正确识别和处理这些动态元素。
mapper中如何传递多个参数
-
使用 @Param 注解
使用 @Param 注解来标识每个参数。在 XML 映射文件中,通过参数名来引用这些参数。- Mapper 接口示例:
public interface UserMapper { User selectUserByIdAndName(@Param("id") int id, @Param("name") String name); }
- XML 映射文件示例:
<select id="selectUserByIdAndName" resultType="User"> SELECT * FROM user WHERE id = #{id} AND name = #{name} </select>
-
使用 Map 传递参数
创建一个 Map 对象,将多个参数作为键值对放入其中,然后传递给 Mapper 方法。- Mapper 接口示例:
public interface UserMapper { User selectUser(Map<String, Object> params); }
- XML 映射文件示例:
<select id="selectUser" resultType="User"> SELECT * FROM user WHERE id = #{id} AND name = #{name} </select>
-
使用自定义类型
创建一个自定义的 Java 类型,将多个参数封装到这个类型中,然后在 Mapper 接口和 XML 映射文件中使用这个类型。- 自定义类型示例:
public class UserSearchCriteria { private int id; private String name; // getters and setters }
- Mapper 接口示例:
public interface UserMapper { User selectUser(UserSearchCriteria criteria); }
- XML 映射文件示例:
<select id="selectUser" resultType="User"> SELECT * FROM user WHERE id = #{id} AND name = #{name} </select>
-
使用 @Param 和自定义类型结合
可以将 @Param 注解和自定义类型结合使用,在 XML 映射文件中既使用自定义类型,又使用单独的参数。- Mapper 接口示例:
public interface UserMapper { User selectUserWithCustomAndParam(@Param("criteria") UserSearchCriteria criteria, @Param("extraParam") String extraParam); }
- XML 映射文件中
- 通过 #{criteria.id} 引用自定义类型中的属性
- 通过 #{extraParam} 引用单独的参数。
- 综合分析
- @Param 注解通常是最简单和直接的方式,
- 使用自定义类型则可以在复杂的查询中提供更好的封装和组织。
具体操作
mybatis 编程的步骤
MyBatis 编程的核心是创建 SqlSessionFactory、SqlSession,然后通过 Mapper 接口执行 SQL 语句。在此基础上,可以根据实际需求进行扩展和优化。
- 创建 SqlSessionFactory:这是MyBatis的核心配置,它包含数据库连接池信息和MyBatis系统核心设置等。创建 SqlSessionFactory 的方式是通过 SqlSessionFactoryBuilder 从 XML 配置文件(或使用 Java API 编程方式)中构建出 SqlSessionFactory 实例。
- 创建 SqlSession:SqlSession 是应用程序与数据库交互的可重用和线程安全的接口。它是执行命令、获取映射器实例和获取事务管理器的核心接口。
- 获取 Mapper:Mapper 是 MyBatis 中最重要的组件之一,它是由 Java 接口和 XML 文件(或注解)共同组成的。Mapper 接口的全限名要和 XML 文件中的 namespace 一致,接口中的方法名和 XML 文件中的 statement 的 id 一致,这样 MyBatis 才能找到并执行对应的 SQL 语句。
- 执行 SQL:通过 Mapper 接口的方法执行 SQL 语句,MyBatis 会根据接口方法名和 XML 文件中的配置找到对应的 SQL 语句并执行,然后返回结果。
-
注意事项:
- 确保数据库驱动已正确添加至项目中,并配置了正确的数据库连接信息。
- 在 XML 配置文件中,需要正确配置== mapper 映射文件的位置==,以便 MyBatis 能找到并加载它们。
- 使用注解或 XML 配置 SQL 语句时,要确保 SQL 语句的正确性,并防止SQL 注入等安全问题。
- 对于复杂的 SQL 语句或查询逻辑,建议使用XML 配置方式,因为这种方式更具灵活性和可读性。
如何执行批量操作
-
配置MyBatis以使用BATCH执行器(两种方式)
-
在MyBatis的配置文件(mybatis-config.xml)中,可以设置默认的执行器类型为BATCH:
<settings> <setting name="defaultExecutorType" value="BATCH"/> </settings>
-
在创建SqlSession时,可以显式地指定ExecutorType.BATCH:
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) { // 执行批量操作 }
-
-
编写Mapper接口和映射文件
在Mapper接口中定义批量插入、更新或删除的方法。然后,在映射文件中编写相应的SQL语句。- 定义一个批量插入的方法:
public interface UserMapper { void insertUsers(List<User> userList); }
- 在映射文件(UserMapper.xml):
<mapper namespace="com.example.mapper.UserMapper"> <insert id="insertUsers" parameterType="list"> INSERT INTO user (id, name, age) VALUES <foreach collection="list" item="user" separator=","> (#{user.id}, #{user.name}, #{user.age}) </foreach> </insert> </mapper>
- 执行批量操作
使用SqlSession调用Mapper接口中定义的批量操作方法,并传入包含多个实体的列表。
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) { UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> userList = // ... 填充用户列表 ... userMapper.insertUsers(userList); // 提交事务以执行所有批量操作 sqlSession.commit(); // 清除缓存,这一步是可选的,取决于你的配置和需要 sqlSession.clearCache(); }
-
注意:在执行完所有批量操作后,需要调用sqlSession.commit()来提交事务,以确保所有的更改都被保存到数据库中。如果你使用的是自动提交模式(即每个语句执行后都自动提交),则不需要显式调用commit()方法。
-
sqlSession.clearCache()用于清除MyBatis的一级缓存,这在某些情况下可能是必要的,以确保后续查询不会返回旧的或已删除的数据。但是,这取决于你的具体需求和MyBatis的配置。
MyBatis中批量操作的优缺点是什么
-
优点:
-
性能提升:批量操作通过减少与数据库的交互次数,显著提高了性能。传统的逐条插入或更新操作需要多次与数据库通信,而批量操作可以一次性发送多条SQL语句到数据库执行,减少了网络传输的开销和数据库处理的次数。
-
资源利用:批量操作减少了数据库连接的建立和关闭次数,从而提高了资源的利用效率。这对于需要处理大量数据的应用程序尤为重要。
-
事务处理:在批量操作中,所有操作都在同一个事务中执行,这有助于保证数据的一致性和完整性。通过事务的控制,可以确保批量操作要么全部成功,要么全部失败,避免了部分成功导致的数据不一致问题。
-
-
缺点:
-
内存消耗:批量操作通常需要在内存中积累一定数量的数据后才进行批量处理。这可能会增加内存消耗,特别是在处理大量数据时,需要更多的内存来存储待处理的数据。
-
错误处理:批量操作在出现错误时可能难以定位具体的问题所在。由于多个操作被打包在一起执行,一旦出错,可能难以确定是哪个操作导致了问题。这增加了错误排查和处理的难度。
-
SQL语句长度限制:某些数据库可能对单个SQL语句的长度有限制。如果批量操作生成的SQL语句过长,可能会超过数据库的限制,导致操作失败。
-
不适合小量数据:对于少量的数据操作,使用批量操作可能并不会带来明显的性能提升,反而可能由于额外的内存消耗和错误处理复杂性而得不偿失。
-
-
总结:
- 对于大量数据的处理,批量操作通常是一个很好的选择;
- 而对于少量数据的操作,则可能需要考虑其他更轻量级的方法。
如何获取生成的主键
- 如果使用useGeneratedKeys属性并设置其值为true,MyBatis会在插入操作后自动将生成的主键设置回实体对象的对应属性。
<insert id="insertUser" parameterType="com.example.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, age) VALUES (#{name}, #{age})
</insert>
- 执行插入操作后,可以直接从实体对象中获取主键值。
User user = new User();
user.setName("John Doe");
user.setAge(30);
userMapper.insertUser(user);
// 主键值现在已经被设置回user对象的id属性中
long id = user.getId();
System.out.println("Generated ID: " + id);
当实体类中的属性名和表中的字段名不一样 ,怎么办
- 可以使用来映射数据库表的字段到实体类的属性。
<resultMap id="UserResultMap" type="com.example.User">
<id property="id" column="user_id" />
<result property="name" column="user_name" />
</resultMap>
<select id="selectUser" resultMap="UserResultMap">
SELECT * FROM user
</select>
- 使用别名:
在SQL查询中,你可以使用别名来确保查询结果的列名与实体类的属性名相匹配。不过,这种方式通常需要在每个查询中手动指定,不够灵活。
SELECT user_id AS id, user_name AS name FROM user
- 使用@Field注解(MyBatis-Plus):
MyBatis-Plus,提供了一个@Field注解,指定数据库中的字段名。
public class User {
@TableId
@Field("user_id") // 指定数据库中的字段名为user_id
private Long id;
@Field("user_name") // 指定数据库中的字段名为user_name
private String name;
// getters and setters
}
Mapper 编写有哪几种方式?
-
基于XML的Mapper编写方式:
- 需要编写XML映射文件来定义SQL语句和结果映射。每个XML映射文件对应一个Mapper接口,其中XML文件中的namespace属性需要设置为Mapper接口的全路径。
- 在XML文件中,你可以使用各种标签(如
<select>、<insert>、<update>、<delete>
等)来定义SQL语句,并通过参数类型和结果类型来指定输入和输出的数据类型。 - 这种方式的优点是SQL语句和Java代码分离,便于管理和维护。同时,XML文件具有很好的可读性和扩展性,可以方便地添加和修改SQL语句。
-
基于注解的Mapper编写方式:
- 直接在Mapper接口的方法上使用注解来定义SQL语句。例如,使用@Select注解来定义查询语句,使用@Insert注解来定义插入语句等。
- 优点是减少了XML文件的数量,使得项目结构更加简洁。同时,注解方式更加直观,可以在Java代码中直接看到SQL语句的定义。
- 缺点是当SQL语句较为复杂或者需要动态生成时,注解方式可能会变得不够灵活和易于管理。
MyBatis的接口绑定概念?有哪些实现方式?
MyBatis的接口绑定概念是指在MyBatis框架中,将Java接口与SQL映射文件进行关联,实现接口方法与SQL语句的对应关系。通过这种方式,程序员可以更加便捷地调用接口方法,从而执行相应的SQL操作。
-
实现方式主要有两种:
- 通过XML映射文件实现接口绑定:这种方式需要提供一个与接口对应的mapper.xml文件。在XML文件中,使用标签将接口与XML文件关联,并使用、、、等标签定义相应的SQL操作。然后,在接口中声明与XML文件中定义的SQL操作相匹配的方法。MyBatis会自动根据接口和对应的XML文件创建一个接口的实现类,从而实现接口与SQL语句的绑定。
- 通过注解实现接口绑定:MyBatis允许在接口的方法上使用注解(如@Select、@Insert、@Update、@Delete等)来定义SQL语句。这些注解告诉MyBatis如何将接口方法与SQL语句进行绑定。使用注解的方式可以更加简洁地实现接口绑定,并且允许通过动态构建SQL语句实现更灵活的SQL操作。
-
注意:
- 确保XML文件名或注解中的namespace与接口的全限定名保持一致,保证MyBatis能够正确地将接口与SQL语句进行绑定。
- 接口中的方法名应与XML文件中的SQL语句id或注解中定义的SQL语句名相对应,返回值类型和参数类型也应与方法的返回值和参数保持一致。
Mybatis能执行一对多,一对一的关联查询吗,有哪些实现方法
-
一对多(One-to-Many)
一对多关系通常用于表示如订单和订单项(Order-OrderItems)这样的关系。- 使用和标签
你可以在MyBatis的映射文件中使用元素来定义结果映射,并使用元素来处理一对多的关系。
<resultMap id="OrderResultMap" type="Order"> <id property="id" column="order_id" /> <result property="orderDate" column="order_date" /> <collection property="orderItems" ofType="OrderItem" resultMap="OrderItemResultMap" /> </resultMap> <resultMap id="OrderItemResultMap" type="OrderItem"> <id property="id" column="item_id" /> <result property="productName" column="product_name" /> <!-- 其他OrderItem属性 --> </resultMap> <select id="selectOrderWithItems" resultMap="OrderResultMap"> SELECT * FROM orders o LEFT JOIN order_items oi ON o.order_id = oi.order_id WHERE o.order_id = #{orderId} </select>
- 使用嵌套查询(Nested Queries)
你也可以使用嵌套查询来处理一对多关系。这涉及到两个独立的查询,一个用于获取主对象,另一个用于获取关联对象列表。
<select id="selectOrder" resultType="Order"> SELECT * FROM orders WHERE order_id = #{orderId} </select> <select id="selectOrderItemsByOrderId" resultType="OrderItem"> SELECT * FROM order_items WHERE order_id = #{orderId} </select>
- 使用和标签
-
一对一(One-to-One)
一对一关系通常用于表示如用户和用户详情(User-UserDetails)这样的关系。-
使用和标签
与一对多类似,你可以使用标签来处理一对一关系。<resultMap id="UserResultMap" type="User"> <id property="id" column="user_id" /> <result property="username" column="username" /> <association property="userDetails" javaType="UserDetails" resultMap="UserDetailsResultMap" /> </resultMap> <resultMap id="UserDetailsResultMap" type="UserDetails"> <id property="id" column="details_id" /> <result property="email" column="email" /> <!-- 其他UserDetails属性 --> </resultMap> <select id="selectUserWithDetails" resultMap="UserResultMap"> SELECT u.*, d.* FROM users u LEFT JOIN user_details d ON u.user_id = d.user_id WHERE u.user_id = #{userId} </select>
-
使用嵌套查询(Nested Queries)
与一对多类似,你也可以使用嵌套查询来处理一对一关系。<select id="selectUser" resultType="User"> SELECT * FROM users WHERE user_id = #{userId} </select> <select id="selectUserDetailsByUserId" resultType="UserDetails"> SELECT * FROM user_details WHERE user_id = #{userId} </select>
-
-
注意事项
- 当处理关联查询时,要注意SQL查询的性能优化,特别是当关联的数据量很大时。
使用和/标签时,确保属性名和列名的映射是正确的。 - 对于复杂的关联查询,可能需要使用更高级的SQL特性,如子查询、连接查询等。考虑到性能和可读性,有时候可能需要权衡是否使用关联查询,或者是否使用懒加载(Lazy Loading)等策略来延迟加载关联数据。
- 当处理关联查询时,要注意SQL查询的性能优化,特别是当关联的数据量很大时。
相关问题
使用MyBatis的mapper接口调用时有哪些要求?
-
需要遵循以下要求和规范:
- 接口命名规范:Mapper接口的命名应遵循一定的规范,通常以Mapper作为后缀,如UserMapper、OrderMapper等,以便于理解该接口的用途和作用。
- 方法命名规范:Mapper接口中的方法名应与SQL映射文件中定义的SQL语句的id一致。同时,方法名应具备一定的语义,以便于开发者理解方法的作用。
- 参数传递方式:Mapper接口的方法可以接收多个参数。参数传递可以通过注解方式(@Param)直接在方法参数中使用注解来指定参数名称,或者通过Map、对象等方式传递参数。
- 映射SQL语句:Mapper接口中的方法并不直接包含SQL语句,而是使用注解或者XML映射文件将SQL语句与方法进行关联。这意味着你需要在XML映射文件中定义SQL语句,并通过namespace和id等属性与Mapper接口进行匹配。
- 类型匹配:Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型应该相同,同时,Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型也应该相同。
此外,当在应用代码中不结合Spring来使用MyBatis时,你需要通过SqlSession获取mapper接口对应的代理对象(如MapperProxy),然后通过该代理对象来调用并执行mapper接口的方法。
Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?
-
在MyBatis的XML映射文件中,不同的XML映射文件中的id不可以重复。每个id必须在其所在的命名空间中唯一。
- MyBatis通过namespace和id的组合来唯一标识一个SQL语句。namespace通常是Mapper接口的全路径名,而id则是Mapper接口中方法的名称。
- 有两个不同的XML映射文件,
namespace
不同,id可以相同。namespace
相同,id必须唯一。
- 有两个不同的XML映射文件,
- MyBatis通过namespace和id的组合来唯一标识一个SQL语句。namespace通常是Mapper接口的全路径名,而id则是Mapper接口中方法的名称。
-
示例:
- 两个
Mapper
接口UserMapper
和OrderMapper
,都有一个名为findById
的方法,可以在UserMapper.xml中定义一个id为findById的SQL语句,同时在OrderMapper.xml中也定义一个id为findById的SQL语句-
如果它们的
namespace
分别是UserMapper
和OrderMapper
的全路径,不会产生冲突。 -
但是,如果在UserMapper.xml中定义两个或更多id为
findById
的SQL语句,将无法区分它们,从而导致运行时错误。
-
- 两个
Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?
-
XML 映射文件的基本结构,包含以下元素:
<mapper>
:根元素,用于定义命名空间(namespace),通常与对应的 Mapper 接口的全限定名一致。<select>、<insert>、<update>、<delete>
:用于定义 SQL 语句,分别对应查询、插入、更新和删除操作。<resultMap>
:用于定义结果集的映射关系,包括如何将数据库字段映射到 Java 对象的属性。<parameterType> 和 <resultType>
:用于指定 SQL 语句的参数类型和返回类型。
-
内部数据结构
- Configuration:MyBatis 的核心配置类,包含了所有的配置信息,如环境信息、别名、类型处理器、映射器等。
- MapperStatement:代表一个映射语句,包含了 SQL 语句、参数类型、返回类型等信息。
- ResultMap:表示结果集的映射关系,用于将查询结果映射到 Java 对象。
- ParameterHandler:处理 SQL 语句的参数。
- ResultSetHandler:处理 SQL 语句的结果集。
-
映射关系
- 命名空间与 Mapper 接口:XML 映射文件的 元素定义的命名空间通常与对应的 Mapper 接口的全限定名一致。这样,MyBatis 就可以通过命名空间找到对应的 Mapper 接口,进而找到对应的 SQL 语句。
- SQL 语句与 MapperStatement:XML 映射文件中的
<select>、<insert>、<update>、<delete>
元素定义的 SQL 语句会被解析为 MapperStatement 对象,存储在 Configuration 中。这些 MapperStatement 对象包含了 SQL 语句、参数类型、返回类型等信息。 - 结果集映射与 ResultMap:XML 映射文件中的 元素定义的结果集映射关系会被解析为 ResultMap 对象,同样存储在 Configuration 中。当执行查询操作时,MyBatis 会使用 ResultMap 将查询结果映射到 Java 对象。
- 参数类型与 ParameterHandler:XML 映射文件中通过 或注解指定的参数类型会被用于创建 ParameterHandler 对象,该对象负责处理 SQL 语句的参数。
- 返回类型与 ResultSetHandler:XML 映射文件中通过 或 指定的返回类型会被用于创建 ResultSetHandler 对象,该对象负责处理查询结果集,并将其映射到 Java 对象。
-
总结
MyBatis 通过 XML 映射文件定义了 SQL 语句与 Java 对象之间的映射关系,并将这些映射关系解析为内部数据结构(如MapperStatement、ResultMap 等)进行存储和处理。当执行数据库操作时,MyBatis 会根据这些内部数据结构生成相应的 SQL 语句,并处理参数和结果集,从而实现数据的持久化操作。
Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
1、封装为目标对象并返回
-
过程主要涉及到SQL映射文件、ResultMap以及SQL执行。
-
SQL映射文件:
- 通常是XML格式,包含了要执行的SQL语句以及与这些语句关联的Java对象。
- SQL语句通过
<select>、<insert>、<update>或<delete>
等元素进行定义。
-
ResultMap:
- ResultMap定义了如何从查询结果中提取数据,并将其映射到Java对象的属性上。
- 当查询结果返回时,MyBatis会自动根据ResultMap的定义,将结果映射到正确的Java类中。
-
SQL执行:
- 执行前:需要先创建SqlSession并将其绑定到适当的DataSource(连接数据库的连接池工厂)。
- 执行中:可以使用MyBatis的API来执行SQL语句。
- 执行后:根据映射文件中定义的ResultMap,将SQL执行结果封装为目标对象并返回。
-
2、映射形式
-
使用resultType属性:你可以将查询结果的列直接映射到Java对象的属性上。例如,在XML映射文件中,这样定义查询:MyBatis会自动将查询结果的每一列映射到User对象的对应属性上。
<select id="selectUser" resultType="com.example.User">SELECT * FROM user WHERE id=#{id}</select>
-
使用resultMap属性:这种方式提供了更灵活的映射方式。可以通过
<resultMap>
标签,逐一定义列名和对象属性名之间的映射关系。这样,可以更精确地控制如何将数据库字段映射到Java对象的属性上。 -
使用SQL列的别名功能:通过在SQL查询中使用列的别名,并将别名设置为Java对象的属性名,来实现映射。MyBatis会智能地找到与别名对应的Java对象属性名,并完成映射。
Mybatis映射文件中,如果A标签通过include引用了B标签的内容,AB标签之间的定义顺序是否受到影响?
-
MyBatis 在解析映射文件时,会先解析整个文件,并构建内部的映射结构。因此,不论被引用的 SQL 片段在文件中的位置如何,MyBatis 都能正确地找到并引用它。
-
举例子(尽管 SQL 片段 B 定义在引用它的 A 标签之前,但 MyBatis 仍然能够正确地解析并执行这个查询。)
<mapper namespace="com.example.MyMapper"> <!-- 定义被引用的 SQL 片段 B --> <sql id="B"> column1, column2 </sql> <!-- 定义引用 SQL 片段 B 的 A 标签 --> <select id="selectExample" resultType="com.example.Example"> SELECT <include refid="B" /> FROM some_table WHERE some_condition </select> </mapper>
-
同样地,也可以将 SQL 片段 B 放在 A 标签的后面,MyBatis 依然能够正常工作:
<mapper namespace="com.example.MyMapper"> <!-- 定义引用 SQL 片段 B 的 A 标签 --> <select id="selectExample" resultType="com.example.Example"> SELECT <include refid="B" /> FROM some_table WHERE some_condition </select> <!-- 定义被引用的 SQL 片段 B --> <sql id="B"> column1, column2 </sql> </mapper>
在这种情况下,由于 MyBatis 在解析时会先构建整个映射结构,所以 SQL 片段 B 仍然可以被 A
标签正确引用。因此,可以根据代码的组织和可读性需求,自由地在映射文件中安排 SQL 片段的置。
Mybatis是否支持映射Enum枚举类
-
可以使用TypeHandler接口来定义如何将Java枚举类型与数据库中的值进行转换。处理Java枚举类型与数据库中的值之间的映射。
-
MyBatis默认使用EnumTypeHandler将枚举值转换为其名称进行存储,即当枚举类型的字段被持久化到数据库时,它会被保存为枚举名称。
- 如果要自定义枚举类型的处理方式,比如按照枚举的序号(ordinal)或者自定义的属性来存储和读取,你可以编写自定义的TypeHandler来实现这些功能。
- MyBatis也提供了内置的EnumOrdinalTypeHandler,该处理器会将枚举的序号(从0开始的索引)与数据库中的数字字段进行映射。