Mybatis篇常见知识点(面试题在文章最后)
1.jdbc的缺点以及mybatis的优点,jdbc问题的解决思路
-
JDBC
-
数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能
-
Sql语句在代码中硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变 java代码
-
使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护
-
对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便
-
MyBatis
-
轻量级,性能出色
-
SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
-
开发效率稍逊于HIbernate,但是完全能够接受
JDBC问题解决思路
①使用数据库连接池初始化连接资源
②将sql语句抽取到xml配置文件中
③使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射
2.mybatis的核心配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--
MyBatis核心配置文件中,标签的顺序:
properties?,settings?,typeAliases?,typeHandlers?,
objectFactory?,objectWrapperFactory?,reflectorFactory?,
plugins?,environments?,databaseIdProvider?,mappers?
-->
<!--引入properties文件-->
<properties resource="jdbc.properties" />
<!--设置类型别名-->
<typeAliases>
<!--
typeAlias:设置某个类型的别名
属性:
type:设置需要设置别名的类型
alias:设置某个类型的别名,若不设置该属性,那么该类型拥有默认的别名,即类名且不区分大小写
-->
<!--<typeAlias type="com.xxx.pojo.User"></typeAlias>-->
<!--以包为单位,将包下所有的类型设置默认的类型别名,即类名且不区分大小写-->
<package name="com.xxx.pojo"/>
</typeAliases>
<!--
environments:配置多个连接数据库的环境
属性:
default:设置默认使用的环境的id
-->
<environments default="development">
<!--
environment:配置某个具体的环境
属性:
id:表示连接数据库的环境的唯一标识,不能重复
-->
<environment id="development">
<!--
transactionManager:设置事务管理方式
属性:
type="JDBC|MANAGED"
JDBC:表示当前环境中,执行SQL时,使用的是JDBC中原生的事务管理方式,事务的提交或回滚需要手动处理
MANAGED:被管理,例如Spring
-->
<transactionManager type="JDBC"/>
<!--
dataSource:配置数据源
属性:
type:设置数据源的类型
type="POOLED|UNPOOLED|JNDI"
POOLED:表示使用数据库连接池缓存数据库连接
UNPOOLED:表示不使用数据库连接池
JNDI:表示使用上下文中的数据源
-->
<dataSource type="POOLED">
<!--设置连接数据库的驱动-->
<property name="driver" value="${jdbc.driver}"/>
<!--设置连接数据库的连接地址-->
<property name="url" value="${jdbc.url}"/>
<!--设置连接数据库的用户名-->
<property name="username" value="${jdbc.username}"/>
<!--设置连接数据库的密码-->
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<!--<mapper resource="mappers/UserMapper.xml"/>-->
<!--
以包为单位引入映射文件
要求:
1、mapper接口所在的包要和映射文件所在的包一致
2、mapper接口要和映射文件的名字一致
-->
<package name="com.xxx.mapper"/>
</mappers>
</configuration>
3.mybatis中的动态sql
此处只写常用的,其他自己参考资料
3.1.动态模糊查询
/**
* 测试模糊查询
* @param name
* 改注解可以对参数起别名与sql语句中#{保持一致}(一般在多参数下使用,否则会默认使用命名
* param0,param1或者arr0,arr1)
* @return
* 注解的方式书写模糊查询语句,xml文件的方式书写动态sql
*/
@Select("select * from t_user where username like concat('%',#{username},'%')")
//@Select("select * from t_user where username like '%${username}%'")
List<User> selectAll(@Param("username") String username);
<select id="selectAll" resultType="com.xxx.pojo.User">
select * from t_user
<where>
<!-- if中test做属性值判断 字符串一下判断方式,Integer类型判断是否为null就可 -->
<if test="username !=null and username !='' ">
username like concat ('%',#{username},'%')
</if>
</where>
</select>
3.2.批量删除以及设置表名
一定要用${},不可以用#{}
-
${}
占位符:${}
占位符会直接将参数的值拼接到SQL语句中,相当于字符串的替换。这意味着在使用${}
时,参数的值将直接嵌入到SQL语句中,可能会导致SQL注入的风险。因此,${}
主要适用于对静态、不包含用户输入的参数进行替换。 -
#{}
占位符:#{}
占位符会使用预编译的方式,将参数的值放在SQL语句中的占位符位置,并使用JDBC的PreparedStatement进行参数注入。这样可以避免SQL注入的风险,并且能够处理参数的类型转换和特殊字符的转义,提高了应用的安全性和可靠性。因此,#{}
主要适用于动态、包含用户输入的参数。 -
在 MyBatis 中,使用
${}
来处理批量删除和设置表名是因为${}
是进行字符串替换的占位符,它会直接将参数的值插入到 SQL 语句中。这在某些情况下是必需的,因为#{}
是使用预编译的方式来处理参数的占位符,会自动进行参数值的转义和类型处理,而批量删除和设置表名这两个场景通常需要使用动态的字符串。
4 分步查询
不做过多解释,直接看代码(mybatis视频质料可取b站看尚硅谷的)
多对一
一对多
5.mybatis注解开发 不做解释与示例
一对多 多对一 多对多推荐使用xml文件
6.Mybatis中一级缓存和二级缓存
https://blog.csdn.net/zy_zhangruichen/article/details/122592504
缓存的意义是将用户经常查询的数据放入缓存(内存)中去,用户去查询数据的时候就不需要从磁盘(关系型数据库)中查询,直接从缓存中查询,从而提高了查询效率,解决了高并发中系统的性能问题。Mybatis中提供一级缓存与二级缓存。
一级缓存
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问
使一级缓存失效的四种情况:
1) 不同的SqlSession对应不同的一级缓存
2) 同一个SqlSession但是查询条件不同
3) 同一个SqlSession两次查询期间执行了任何一次增删改操作
4) 同一个SqlSession两次查询期间手动清空了缓存
二级缓存
二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被
缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启的条件:
a>在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
b>在映射文件中设置标签<cache/>
c>二级缓存必须在SqlSession关闭或提交之后有效
d>查询的数据所转换的实体类类型必须实现序列化的接口
使二级缓存失效的情况:
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
二级缓存的相关配置
在mapper配置文件中添加的cache标签可以设置一些属性:
①eviction属性:缓存回收策略,默认的是 LRU。
LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
②flushInterval属性:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
③size属性:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
④readOnly属性:只读, true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了 很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是
false。
MyBatis缓存查询的顺序
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭之后,一级缓存中的数据会写入二级缓存
7.MyBatis的逆向工程
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE generatorConfiguration PUBLIC
"-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" >
<generatorConfiguration>
<!-- 引入配置文件 数据库连接信息 -->
<!-- <properties resource="jdbc.properties"/>-->
<!-- 指定连接数据库的JDBC驱动包所在位置,指定到你本机的完整路径 -->
<!-- <classPathEntry location="src/main/resources/mysql-connector-java-8.0.26.jar"/>-->
<!-- 配置table表信息内容体,targetRuntime指定采用MyBatis3的版本 -->
<context id="context" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释,由于生成的注释都是英文的,可以不让它生成. true:是 : false:否 -->
<property name="suppressAllComments" value="true"/>
<!--去除timestamp -->
<property name="suppressDate" value="true"/>
</commentGenerator>
<!-- 配置数据库连接信息 connectionFactory与jdbcConnection只能配置其一-->
<!-- 数据库的相关配置 mysql8: com.mysql.cj.jdbc.Driver-->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/crm?serverTimezone=UTC"
userId="root"
password="root"/>
<!-- 配置数据库连接信息 -->
<!-- <connectionFactory>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="connectionURL" value="${jdbc.url}"/>
<property name="userId" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</connectionFactory>-->
<javaTypeResolver>
<property name="forceBigDecimals" value="false"/>
</javaTypeResolver>
<!-- 实体类生成的位置 -->
<javaModelGenerator targetPackage="com.xxx.pojo" targetProject="src/main/java">
<property name="enableSubPackages" value="false"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- *Mapper.xml 文件的位置 ,targetPackage:包名,targetProject:项目下的路径-->
<sqlMapGenerator targetPackage="com.xxx.mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="false"/>
</sqlMapGenerator>
<!-- Mapper 接口文件的位置 -->
<javaClientGenerator targetPackage="com.xxx.mapper" targetProject="src/main/java" type="XMLMAPPER">
<property name="enableSubPackages" value="false"/>
</javaClientGenerator>
<!-- 配置表信息 -->
<table tableName="t_customer_serve" domainObjectName="CustomerServe">
<!--是否生成有参的构造方法, 默认否-->
<property name="constructorBased" value="false"/>
<!--默认值为false,如果为true,在生成的sql语句中表名中不会加上catalog或schema-->
<property name="ignoreQualifiersAtRuntime" value="false"/>
<!--默认值为false,如果为true创建不可变类,生成包含所有field参数的构造方法,没有setter方法-->
<property name="immutable" value="false"/>
<!--是否只生成模型类,默认值为false-->
<property name="modelOnly" value="false"/>
<!--是否使用真实的字段名称作为类中的属性名称,默认值为false-->
<property name="useActualColumnNames" value="false"/>
<!--主键生成策略:可选值:MySql、SqlServer、SYBASE、DB2、Derby等
使用MySql即生成:SELECT LAST_INSERT_ID() 获取主键值
-->
<generatedKey column="id" sqlStatement="MySql"/>
</table>
<!-- 数据库中多个表 可以对table标签中的内容进行复制,修改标签中tableName和domainObjectName属性值即可-->
</context>
</generatorConfiguration>
pom坐标
<!-- 依赖MyBatis核心包 -->
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
8.mybatis的分页查询
分页基础知识点
limit index,pageSize
pageSize:每页显示的条数
pageNum:当前页的页码
index:当前页的起始索引,index=(pageNum-1)*pageSize
count:总记录数
totalPage:总页数
totalPage = count / pageSize;
if(count % pageSize != 0){
totalPage += 1;
}
pageSize=4,pageNum=1,index=0 limit 0,4
pageSize=4,pageNum=3,index=8 limit 8,4
pageSize=4,pageNum=6,index=20 limit 8,4
分页查询主要步骤
导入依赖坐标
<properties>
<pagehelper.version>5.1.2</pagehelper.version>
</properties>
<dependencies>
<!--mybatis分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>${pagehelper.version}</version>
</dependency>
</dependencies>
部分主要代码演示
/**
* PageData<T>编写的分页数据统一封装类 类似于模板思想
* UserQuery 就是实体类 只是简化了所需(表)字段(实体类属性)
* PageHelper.startPage:(pageNum,pageSize 去基础知识点看不理解的)
* public static <E> Page<E> startPage(int pageNum, int pageSize) {
* return startPage(pageNum, pageSize, true);
* }
*
* PageInfo 可去查看源码,类中属性大体就是基础知识点中的东西(仅展示部分属性)
*
*
*/
public PageData getAllPageSaleChance(UserQuery userQuery) {
//开启分页查询
PageHelper.startPage(userQuery.getPage(), userQuery.getLimit());
//条件查询
List<User> users = userMapper.selectByUserQuery(userQuery);
//封装分页数据
PageInfo<User> pageInfo = new PageInfo(users);
return PageData.data(pageInfo.getList(), pageInfo.getTotal());
}
总结
a>在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能
pageNum:当前页的页码
pageSize:每页显示的条数
b>在查询获取list集合之后,使用PageInfo<T> pageInfo = new PageInfo<>(List<T> list, int
navigatePages)获取分页相关数据
list:分页之后的数据
navigatePages:导航分页的页码数
c>分页相关数据
PageInfo{
pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30,
pages=8, reasonable=false, pageSizeZero=false},
prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,
hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,
navigatepageNums=[4, 5, 6, 7, 8]
}
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]
在后续springBoot中利用MybatisPlus更容易实现分页数据以及和分页绑定的查询功能(后续springboot大体总结中会详细提到),这里展示代码部分
/**
* 分页查询
*
*/
@Test
void testPage() {
IPage page = new Page(1, 5);
uSerDao.selectPage(page, null);
System.out.println(page.getCurrent()); //当前页
System.out.println(page.getSize()); //每页展示数据条数
System.out.println(page.getPages()); //多少页
System.out.println(page.getTotal()); //数据总数(表中数据总数,
// 不是分页后的数据总数不要搞混)
System.out.println(page.getRecords()); //分页数据存放
}
面试题
mybatis延迟加载的原理是什么
mybatis延迟加载一般仅在联合查询时(不懂的可以去看上面多表查询)在association关联对象(一对一、多对一)和collection关联集合对象(一对多,多对多 表之间的关系不在做叙述)中使用。
在mybatis的配置文件中可以根据标签中的属性来配置是否开启延迟加载lazyLoadingEnable=true|false
原理:使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器intercept()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用,以上就是延迟加载的基本原理。
什么是MyBatis?它的特点是什么?
MyBatis是一个开源的持久层框架,它提供了许多功能和特性来简化与数据库的交互。它将SQL语句与Java代码分离,通过配置文件实现数据库操作,从而降低了开发人员处理数据库的复杂性。
MyBatis的特点包括:
1. 简化数据库操作:MyBatis使用简单的接口和SQL映射文件,将Java对象与数据库表进行映射,开发人员无需手动编写JDBC代码,简化了数据库操作的过程。
2. 灵活的SQL编写:MyBatis支持原生的SQL编写,允许开发人员自由编写SQL语句,可灵活控制数据查询和更新的过程。同时,MyBatis还提供了动态SQL的功能,可以根据条件动态生成SQL语句,提高了SQL的灵活性和重用性。
3. 强大的结果映射:MyBatis提供了强大的结果映射功能,可以将查询结果自动映射到Java对象或集合中,大大简化了结果获取和封装的过程。
4. 缓存支持:MyBatis具有一级缓存和二级缓存的支持。一级缓存是默认开启的,它保存了在同一次会话中执行的SQL语句的结果,避免了重复查询。而二级缓存是跨会话的缓存,可以提高相同查询的性能。
5. 与多种数据库兼容:MyBatis支持多种主流数据库,如MySQL、Oracle、SQL Server等,并提供了相应的数据库方言,允许开发人员在不同的数据库平台中无缝切换。
6. 易于集成:MyBatis可以与Spring、Spring Boot等框架集成,通过注解或XML配置,实现与其他框架的无缝集成,提供更便捷的开发和部署方式。
总之,MyBatis通过简化数据库操作、提供灵活的SQL编写、强大的结果映射、缓存支持和集成性等特点,使得开发人员可以更高效地进行数据库交互,提高了开发效率和应用性能。
MyBatis的工作原理是什么?
MyBatis的工作原理可以简单概括为以下几个步骤:
1. 读取配置文件:MyBatis的配置文件(通常命名为`mybatis-config.xml`)包含了框架的全局配置信息,包括数据库连接、缓存配置、映射器配置等。MyBatis在启动时会读取配置文件,并创建全局唯一的SqlSessionFactory对象。
2. 创建SqlSessionFactory:SqlSessionFactory是MyBatis的核心对象,它负责创建SqlSession对象。SqlSessionFactory使用配置文件中的信息,通过构建者模式创建SqlSession对象所需的数据库连接池、缓存等资源。
3. 创建SqlSession:SqlSession是MyBatis与数据库交互的主要对象,它封装了对数据库的操作方法。通过SqlSessionFactory的openSession方法,可以创建SqlSession对象。
4. 加载映射器配置:映射器配置是MyBatis的关键部分,它负责定义Java对象和数据库表之间的映射关系。MyBatis会根据配置文件中的映射器配置,加载映射器接口和对应的映射文件(通常命名为`xxxMapper.xml`),建立Java对象与数据库操作语句的映射关系。
5. 执行数据库操作:通过SqlSession对象调用映射器接口中定义的方法,将操作请求转化为SQL语句,并通过JDBC发送给数据库执行。MyBatis提供了丰富的API和注解来编写SQL语句,开发人员可以根据需求选择合适的方式。
6. 处理结果集:数据库执行完SQL语句后,MyBatis将结果集封装为Java对象或集合,并返回给调用方。MyBatis提供了强大的结果映射功能,可以自动将结果集映射到对象中,简化了结果处理的过程。
7. 关闭资源:在使用完毕后,需要关闭SqlSession对象和相关资源,以释放连接和避免资源泄漏。可以通过调用SqlSession的close方法来关闭会话。
通过以上步骤,MyBatis实现了Java对象与数据库表的映射、SQL语句的生成和执行、结果集的处理等数据库操作的工作。该工作原理具有灵活性和易用性,使得开发人员可以方便地进行数据库交互。
MyBatis的一级缓存和二级缓存有什么区别?如何配置缓存?
MyBatis的一级缓存(Local Cache)和二级缓存(Global Cache)是两种不同级别的缓存机制。
一级缓存:
- 一级缓存是默认开启的,它是SqlSession级别的缓存,只在同一个SqlSession内有效。
- 当执行相同的查询时,MyBatis会首先检查一级缓存中是否有对应的结果。如果有,则直接从缓存中获取结果,而不需要再次查询数据库。
- 一级缓存是由SqlSession维护的,当SqlSession被关闭或提交事务时,一级缓存将失效。
二级缓存:
- 二级缓存是SessionFactory级别的缓存,可以被多个SqlSession共享。
- 当执行查询操作时,如果一级缓存中没有对应的结果,MyBatis会查询二级缓存,如果找到相应的结果,则直接从缓存中获取。
- 二级缓存通过配置文件中的`<cache>`元素进行配置,可以配置Cache的实现类、缓存策略、过期时间等。
- 默认情况下,二级缓存是不开启的,需要手动配置开启,并且需要在映射文件中明确标注开启二级缓存。
配置缓存:
要配置缓存,在MyBatis的配置文件(通常是`mybatis-config.xml`)中,可以使用`<cache>`元素来配置缓存。
1. 开启一级缓存:一级缓存是默认开启的,无需额外配置。
2. 开启二级缓存:
- 在`<cache>`元素中设置`enabled`属性为`true`表示开启二级缓存。
- 可以选择不同的缓存实现类,默认提供了多种实现,如`org.apache.ibatis.cache.impl.PerpetualCache`和`org.mybatis.caches.ehcache.EhcacheCache`等。
- 可以设置缓存的其他属性,如缓存策略(例如`FIFO`、`LRU`、`SOFT`、`WEAK`等)、过期时间等。
示例配置:
```xml
<!-- 在mybatis-config.xml中配置 -->
<configuration>
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
<!-- 或者使用默认的PerpetualCache -->
<!-- <cache type="org.apache.ibatis.cache.impl.PerpetualCache"/> -->
</configuration>
<!-- 在映射文件中标注启用二级缓存 -->
<mapper namespace="com.example.mapper.UserMapper" >
<cache/>
</mapper>
```
需要注意的是,缓存的使用需要慎重考虑,特别是在修改、删除、插入操作频繁的场景下。配置缓存时,需根据实际情况权衡缓存的使用,避免出现脏数据或缓存滥用导致的性能问题。
MyBatis中的延迟加载是什么?如何使用延迟加载?
在MyBatis中,延迟加载(Lazy Loading)是一种优化技术,它允许在需要时才加载关联对象的数据,减少数据库查询的数量,提高查询效率。
延迟加载的基本原理是:当访问一个对象关联字段时,不立即查询数据库获取关联对象的数据,而是生成一个代理对象并返回。只有当真正使用关联对象的数据时,才会触发数据库查询。
使用延迟加载需要注意以下几点:
1. 配置关联对象的延迟加载:在映射文件中,可以使用`<association>`(一对一关联)和`<collection>`(一对多关联)元素的`fetchType`属性设置延迟加载。将`fetchType`设置为`lazy`即可开启延迟加载。
示例:
```xml
<association property="user" column="user_id" fetchType="lazy" select="getUserById"/>
```
2. 延迟加载的触发时机:当访问关联对象时,例如调用关联对象的方法或访问关联对象的属性时,会触发实际的数据库查询,获取关联对象的数据。
3. 确保SqlSession处于打开状态:延迟加载需要保证SqlSession处于打开状态,因为在访问关联对象时,会触发实际的数据库查询,需要依赖SqlSession进行查询操作。
需要注意的是,延迟加载通常与二级缓存不兼容,因为延迟加载需要SqlSession处于打开状态才能触发数据库查询,而二级缓存的作用范围是SessionFactory级别的,无法保证SqlSession一直处于打开状态。
延迟加载的优点是能够减少数据库查询的次数,提高查询的效率。但在使用延迟加载时,需要避免出现N+1查询的问题,即在遍历关联集合或访问关联集合中的对象时,会多次触发数据库查询。可以使用MyBatis的关联预加载功能或手动调整查询策略,以避免N+1查询的性能问题。
MyBatis中的事务控制是如何实现的?
在MyBatis中,事务控制是通过基于JDBC的事务管理实现的。MyBatis本身并不提供事务管理功能,而是利用底层的JDBC连接来完成事务控制。
MyBatis使用以下对象实现事务控制:
1. SqlSessionFactory:在MyBatis的配置文件(`mybatis-config.xml`)中配置数据源和事务管理器。SqlSessionFactory负责创建和管理数据库连接。
2. SqlSession:SqlSession是与数据库交互的主要接口,它提供了执行SQL语句和管理事务的方法。
3. Transaction:Transaction接口是MyBatis用来控制事务的核心接口。MyBatis通过Transaction接口提供了开始事务、提交事务、回滚事务等方法来进行事务管理。
事务控制的基本使用方式如下:
1. 手动管理事务:通过SqlSession的`beginTransaction()`方法手动开始事务。
2. 提交事务:通过SqlSession的`commit()`方法手动提交事务。在提交事务之前,所有的数据库操作都会在一个事务中执行。
3. 回滚事务:通过SqlSession的`rollback()`方法手动回滚事务。当出现异常或需要撤销之前的操作时,可以回滚事务以保持数据的一致性。
示例代码:
```java
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
// 手动开始事务
sqlSession.beginTransaction();
// 执行数据库操作
// 手动提交事务
sqlSession.commit();
} catch (Exception e) {
// 出现异常,回滚事务
sqlSession.rollback();
} finally {
// 关闭SqlSession
sqlSession.close();
}
```
需要注意的是,MyBatis并不强制要求使用手动管理事务,可以通过配置Transaction的行为来实现自动提交或自动回滚事务。可以在MyBatis的配置文件中配置事务管理器(TransactionManager),通过配置文件设定自动提交、读写分离等事务特性。
通过上述方式,MyBatis实现了对事务的管理和控制,使得开发人员可以方便地进行事务操作,保证数据的一致性和完整性。
MyBatis的常见性能调优手段有哪些?
对于MyBatis的性能调优,以下是一些常见的手段和技巧:
1. 合理设计SQL语句:
- 编写高效的SQL语句,避免不必要的字段查询和重复查询。
- 使用索引来优化查询性能,确保数据库表格上存在合适的索引。
- 避免在循环中执行SQL查询,尽量通过批量操作或者Join语句减少数据库访问。
2. 使用缓存:
- 配置合适的缓存机制,如二级缓存,以减少对数据库的查询次数。
- 根据业务场景设置合适的缓存过期策略和调整缓存大小。
3. 单独查询和延迟加载:
- 使用关联查询(Join)避免N+1查询问题,减少数据库访问。
- 合理使用延迟加载(Lazy Loading),在需要时才加载关联对象,减少不必要的查询。
4. 批量插入和更新:
- 对于批量插入或更新的场景,使用MyBatis提供的批量操作功能,减少数据库访问次数。
5. 数据库连接池优化:
- 配置合适的数据库连接池,如HikariCP或Druid,调整连接池大小和参数,以提高连接获取和释放的效率。
6. 预编译SQL语句:
- 预编译SQL语句可以提高SQL的执行性能,避免每次执行都编译SQL。
7. 使用分页查询:
- 对于大数据量的查询结果,使用分页查询,只加载需要的数据,减轻数据库负担。
8. 防止SQL注入:
- 使用预编译SQL语句或者使用MyBatis提供的参数处理功能,以防止SQL注入攻击。
9. 监控和日志:
- 配置合适的日志级别,记录关键操作的日志以进行排查和优化。
- 使用监控工具如Druid监控,监控数据库连接池和SQL执行统计信息。
需要根据具体的应用场景和需求进行性能调优,综合考虑数据库结构、SQL语句设计、连接池优化等方面来提高MyBatis的性能。同时,通过分析应用的瓶颈和使用监控工具来定位潜在的性能问题。
MyBatis中如何处理批量操作?
在MyBatis中,可以使用批量操作功能来优化批量插入(batchInsert)、批量更新(batchUpdate)、批量删除(batchDelete)等操作的性能。MyBatis提供了两种方式来实现批量操作:
1. 使用 `<foreach>` 元素:
- 创建一个 `<foreach>` 元素,设置 `collection` 属性为要循环的集合。
- 在 `<foreach>` 元素内部编写SQL语句,并通过 `${}` 语法引用集合中的元素。
- 使用 SQL 的 `VALUES` 关键字来批量插入或 `UPDATE` 的 `CASE WHEN` 语句来批量更新。
示例:
```xml
<!-- 批量插入 -->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO table_name (column1, column2)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.field1}, #{item.field2})
</foreach>
</insert>
<!-- 批量更新 -->
<update id="batchUpdate" parameterType="java.util.List">
UPDATE table_name
<set>
<foreach collection="list" item="item" separator=",">
column1 = #{item.field1},
column2 = #{item.field2}
</foreach>
</set>
WHERE id IN
<foreach collection="list" item="item" separator="," open="(" close=")">
#{item.id}
</foreach>
</update>
<!-- 批量删除 -->
<delete id="batchDelete" parameterType="java.util.List">
DELETE FROM table_name
WHERE id IN
<foreach collection="list" item="item" separator="," open="(" close=")">
#{item}
</foreach>
</delete>
```
2. 使用内置的批量操作方法:
- 使用 `SqlSession` 的 `insert`、`update` 和 `delete` 方法,并传入带有相同ID的SQL语句和相应的参数列表。
- 这些方法会自动将批量操作参数拆分并提交给JDBC驱动程序进行批量处理。
示例:
```java
List<YourObject> list = new ArrayList<>();
// 添加要操作的对象到 list
// 批量插入
sqlSession.insert("yourMapper.batchInsert", list);
// 批量更新
sqlSession.update("yourMapper.batchUpdate", list);
// 批量删除
sqlSession.delete("yourMapper.batchDelete", list);
```
需要注意的是,具体使用哪种方式取决于个人偏好和具体实现场景。使用 `<foreach>` 元素的方式更为灵活,适用于复杂的批量操作,而内置的批量操作方法则更为简单和快捷,适用于简单的批量操作需求。