前言
在Java持久层框架领域,MyBatis以其灵活的SQL映射和优秀的性能表现,成为了众多企业的首选方案。而MyBatis Plus在MyBatis基础上进一步简化了开发流程,提供了强大的增强功能。本文将全面解析这两个框架的核心原理、高级特性以及面试中的高频问题,助你轻松应对技术面试。
一、MyBatis核心架构与工作原理
1.1 MyBatis整体架构
MyBatis采用分层架构设计,主要包含以下组件:
-
基础支撑层:事务管理、连接池、缓存、日志等
-
核心处理层:配置解析、参数映射、SQL解析、结果映射
-
接口层:提供API给应用层使用
1.2 核心工作原理
// MyBatis执行流程伪代码
public Object execute(SqlSession sqlSession, MappedStatement ms, Object parameter) {
// 1. 获取BoundSql
BoundSql boundSql = ms.getBoundSql(parameter);
// 2. 创建缓存Key
CacheKey key = createCacheKey(ms, parameter, boundSql);
// 3. 查询缓存
Object result = queryFromCache(ms, key);
if (result != null) {
return result;
}
// 4. 执行数据库查询
result = doQuery(ms, parameter, boundSql);
// 5. 缓存结果
cacheResult(ms, key, result);
return result;
}
二、MyBatis核心配置详解
2.1 配置文件结构
<!-- mybatis-config.xml -->
<configuration>
<!-- 属性配置 -->
<properties resource="db.properties"/>
<!-- 设置 -->
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<!-- 类型别名 -->
<typeAliases>
<typeAlias type="com.example.User" alias="User"/>
</typeAliases>
<!-- 环境配置 -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<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>
</environments>
<!-- 映射器 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
2.2 重要配置项说明
|
配置项 |
说明 |
推荐值 |
|---|---|---|
|
cacheEnabled |
全局缓存开关 |
true |
|
lazyLoadingEnabled |
延迟加载开关 |
true |
|
aggressiveLazyLoading |
aggressive延迟加载 |
false |
|
mapUnderscoreToCamelCase |
自动驼峰命名 |
true |
|
defaultExecutorType |
默认执行器 |
SIMPLE |
三、MyBatis映射器深度解析
3.1 XML映射文件
<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 结果映射 -->
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="createTime" column="create_time"/>
<association property="department" javaType="Department">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
</association>
<collection property="roles" ofType="Role">
<id property="id" column="role_id"/>
<result property="name" column="role_name"/>
</collection>
</resultMap>
<!-- 查询语句 -->
<select id="selectUserWithAssociations" resultMap="userResultMap">
SELECT u.*, d.name as dept_name, r.id as role_id, r.name as role_name
FROM user u
LEFT JOIN department d ON u.dept_id = d.id
LEFT JOIN user_role ur ON u.id = ur.user_id
LEFT JOIN role r ON ur.role_id = r.id
WHERE u.id = #{id}
</select>
<!-- 动态SQL -->
<select id="selectByCondition" resultType="User">
SELECT * FROM user
<where>
<if test="userName != null">
AND user_name LIKE CONCAT('%', #{userName}, '%')
</if>
<if test="status != null">
AND status = #{status}
</if>
<choose>
<when test="orderBy == 'name'">
ORDER BY user_name
</when>
<otherwise>
ORDER BY create_time DESC
</otherwise>
</choose>
</where>
</select>
</mapper>
3.2 注解方式映射
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
@Insert("INSERT INTO user(user_name, email) VALUES(#{userName}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@Update("UPDATE user SET user_name=#{userName} WHERE id=#{id}")
int update(User user);
@Delete("DELETE FROM user WHERE id=#{id}")
int delete(Long id);
// 复杂查询使用Provider
@SelectProvider(type = UserSqlProvider.class, method = "selectByCondition")
List<User> selectByCondition(@Param("condition") UserCondition condition);
}
四、MyBatis高级特性
4.1 一级缓存与二级缓存
一级缓存(SqlSession级别)
-
默认开启,在同一个SqlSession中有效
-
执行update/commit/rollback操作时会清空缓存
二级缓存(Mapper级别)
<!-- 开启二级缓存 -->
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>
缓存对比:
|
特性 |
一级缓存 |
二级缓存 |
|---|---|---|
|
作用范围 |
SqlSession级别 |
Mapper级别 |
|
默认开启 |
是 |
否 |
|
存储方式 |
内存 |
可配置(内存/Redis等) |
|
共享性 |
不能共享 |
多个SqlSession共享 |
4.2 插件开发(Interceptor)
@Intercepts({
@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class SqlCostInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
Object result = invocation.proceed();
long end = System.currentTimeMillis();
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
String methodName = ms.getId();
System.out.println("方法 " + methodName + " 执行耗时: " + (end - start) + "ms");
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// 设置属性
}
}
五、MyBatis Plus核心特性
5.1 MyBatis Plus架构优势
MyBatis Plus在MyBatis基础上提供:
-
自动代码生成:快速生成Entity、Mapper、Service
-
强大的CRUD操作:内置通用Mapper和Service
-
分页插件:支持多种数据库的分页查询
-
性能分析插件:输出SQL语句和执行时间
-
全局拦截插件:支持全表更新、删除操作阻断
5.2 快速入门配置
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
5.3 通用CRUD操作
// Entity定义
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userName;
private String email;
@Version
private Integer version;
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
// Service层
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
public Page<User> selectPage(Page<User> page, UserCondition condition) {
return lambdaQuery()
.like(StringUtils.isNotBlank(condition.getUserName()), User::getUserName, condition.getUserName())
.eq(condition.getStatus() != null, User::getStatus, condition.getStatus())
.page(page);
}
}
六、MyBatis Plus高级功能
6.1 条件构造器详解
// QueryWrapper示例
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.like("user_name", "张")
.between("age", 20, 30)
.isNotNull("email")
.orderByDesc("create_time");
// LambdaQueryWrapper示例
LambdaQueryWrapper<User> lambdaWrapper = Wrappers.lambdaQuery();
lambdaWrapper.like(User::getUserName, "张")
.ge(User::getAge, 18)
.nested(i -> i.eq(User::getStatus, 1).or().eq(User::getStatus, 2));
6.2 分页插件配置与使用
// 分页查询
Page<User> page = new Page<>(1, 10);
IPage<User> userPage = userMapper.selectPage(page, wrapper);
// 自定义分页
@Select("SELECT * FROM user ${ew.customSqlSegment}")
IPage<User> selectCustomPage(IPage<User> page, @Param(Constants.WRAPPER) Wrapper<User> wrapper);
6.3 代码生成器配置
public class CodeGenerator {
public static void main(String[] args) {
AutoGenerator generator = new AutoGenerator();
// 全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
globalConfig.setAuthor("Author");
globalConfig.setOpen(false);
// 数据源配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/test");
dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("password");
// 包配置
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("com.example");
packageConfig.setEntity("entity");
packageConfig.setMapper("mapper");
packageConfig.setService("service");
generator.setGlobalConfig(globalConfig);
generator.setDataSource(dataSourceConfig);
generator.setPackageInfo(packageConfig);
generator.execute();
}
}
七、面试高频问题解析
7.1 MyBatis与MyBatis Plus的区别
|
特性 |
MyBatis |
MyBatis Plus |
|---|---|---|
|
CRUD操作 |
需要手动编写SQL |
内置通用方法 |
|
代码生成 |
需要第三方工具 |
内置代码生成器 |
|
条件构造 |
需要手写SQL条件 |
强大的条件构造器 |
|
分页支持 |
需要自定义分页 |
内置分页插件 |
|
学习曲线 |
相对陡峭 |
更加平缓 |
7.2 #{}和${}的区别
#{}(预编译)
-
使用PreparedStatement的参数占位符
-
防止SQL注入
-
会自动添加引号
${}(字符串替换)
-
直接进行字符串替换
-
存在SQL注入风险
-
常用于动态表名、列名
7.3 如何解决N+1查询问题?
方案一:使用关联查询
<select id="selectUserWithRoles" resultMap="userWithRoles">
SELECT u.*, r.id as role_id, r.name as role_name
FROM user u
LEFT JOIN user_role ur ON u.id = ur.user_id
LEFT JOIN role r ON ur.role_id = r.id
WHERE u.id = #{id}
</select>
方案二:使用批量查询
@Select("<script>" +
"SELECT * FROM role WHERE id IN " +
"<foreach collection='ids' item='id' open='(' separator=',' close=')'>" +
"#{id}" +
"</foreach>" +
"</script>")
List<Role> selectRolesByIds(@Param("ids") List<Long> ids);
7.4 动态SQL的编写技巧
<!-- 使用trim替代where -->
<select id="selectUsers" resultType="User">
SELECT * FROM user
<trim prefix="WHERE" prefixOverrides="AND|OR">
<if test="name != null">
AND name = #{name}
</if>
<if test="status != null">
AND status = #{status}
</if>
</trim>
</select>
<!-- 使用set动态更新 -->
<update id="updateUser">
UPDATE user
<set>
<if test="name != null">name = #{name},</if>
<if test="email != null">email = #{email},</if>
update_time = NOW()
</set>
WHERE id = #{id}
</update>

被折叠的 条评论
为什么被折叠?



