1. MyBatis单表增删改查方法解析
作为Java开发者最常用的ORM框架之一,MyBatis在单表操作场景下的灵活性和效率优势尤为突出。不同于全自动化的Hibernate,MyBatis需要开发者手动编写SQL语句,这种半自动化的特性反而让它在简单CRUD(增删改查)操作中展现出精准控制的优势。我在实际项目中发现,合理运用MyBatis的单表操作方法,能显著提升开发效率并降低维护成本。
单表操作虽然基础,但其中涉及的参数传递、返回值处理、动态SQL等细节往往决定了代码质量。比如同样的查询功能,使用@Select注解、XML映射文件或MyBatis-Plus提供的Wrapper方式,在可读性和维护性上就有明显差异。接下来我将结合具体案例,详细拆解MyBatis实现单表CRUD的最佳实践方案。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础环境搭建与配置
2.1 项目依赖配置
以Maven项目为例,基础依赖需要包含mybatis核心库和数据库驱动。建议使用Spring Boot整合方案,这会自动配置数据源和事务管理器:
xml复制<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
注意:实际版本号应根据项目需求选择。Spring Boot 3.x需要配合MyBatis 3.0.x以上版本,而Spring Boot 2.x则兼容MyBatis 2.2.x系列。
2.2 数据源配置
在application.yml中配置数据库连接信息。生产环境建议使用连接池(如HikariCP):
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 10
minimum-idle: 5
2.3 MyBatis基础配置
通过配置类或yml文件设置MyBatis行为。关键配置包括:
yaml复制mybatis:
mapper-locations: classpath:mapper/*.xml # XML映射文件位置
type-aliases-package: com.example.entity # 实体类包路径
configuration:
map-underscore-to-camel-case: true # 自动驼峰转换
default-fetch-size: 100 # 查询结果分批获取大小
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 日志实现
3. 实体类与Mapper接口设计
3.1 实体类定义
以用户表为例,创建对应实体类。建议使用Lombok简化代码:
java复制@Data
@TableName("user") // MyBatis-Plus注解,指定表名
public class User {
@TableId(type = IdType.AUTO) // 主键自增策略
private Long id;
private String username;
private String password;
private Integer age;
private String email;
@TableField("create_time") // 映射数据库字段名
private LocalDateTime createTime;
}
3.2 Mapper接口声明
基础Mapper接口继承自MyBatis的BaseMapper(若使用MyBatis-Plus)或自定义通用接口:
java复制@Mapper // Spring扫描标记
public interface UserMapper extends BaseMapper<User> {
// 自定义方法声明
@Select("SELECT * FROM user WHERE age > #{minAge}")
List<User> selectByMinAge(@Param("minAge") Integer minAge);
}
经验:即使使用XML映射文件,也建议保留接口中的
@Mapper注解,这能让IDE更好地进行代码导航和检查。
4. 增删改查方法实现详解
4.1 插入操作
4.1.1 基础插入
java复制// Mapper接口方法
@Insert("INSERT INTO user(username, password, age, email) " +
"VALUES(#{username}, #{password}, #{age}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id") // 获取自增主键
int insertUser(User user);
4.1.2 批量插入
使用<foreach>标签实现高效批量插入(XML方式):
xml复制<insert id="batchInsert">
INSERT INTO user(username, password) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.username}, #{item.password})
</foreach>
</insert>
避坑指南:MySQL默认接受的最大SQL长度由
max_allowed_packet参数控制(默认4MB),批量插入时应控制每批次数据量,建议500-1000条/批。
4.2 删除操作
4.2.1 按ID删除
java复制@Delete("DELETE FROM user WHERE id = #{id}")
int deleteById(@Param("id") Long id);
4.2.2 条件删除
动态SQL实现灵活条件删除(XML方式):
xml复制<delete id="deleteByCondition">
DELETE FROM user
<where>
<if test="username != null">
AND username = #{username}
</if>
<if test="age != null">
AND age > #{age}
</if>
</where>
</delete>
返回值说明:删除操作返回受影响行数。若使用逻辑删除(is_deleted标记),需配合
@TableLogic注解(MyBatis-Plus特性)。
4.3 更新操作
4.3.1 全字段更新
java复制@Update("UPDATE user SET username=#{username}, password=#{password}, " +
"age=#{age}, email=#{email} WHERE id=#{id}")
int updateUser(User user);
4.3.2 动态字段更新
使用<set>标签实现非空字段更新:
xml复制<update id="updateSelective">
UPDATE user
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="age != null">age=#{age},</if>
</set>
WHERE id=#{id}
</update>
性能提示:动态更新能减少不必要的字段修改,但会生成不同的SQL语句,可能影响MySQL的查询缓存效率。
4.4 查询操作
4.4.1 基础查询
java复制// 按ID查询
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(@Param("id") Long id);
// 条件查询
@Select("SELECT * FROM user WHERE age BETWEEN #{min} AND #{max}")
List<User> selectByAgeRange(@Param("min") Integer min,
@Param("max") Integer max);
4.4.2 结果映射
复杂结果集映射示例(XML方式):
xml复制<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="user_name"/>
<result property="createTime" column="create_time"
jdbcType="TIMESTAMP"/>
</resultMap>
<select id="selectComplex" resultMap="userResultMap">
SELECT id, user_name, create_time FROM user
</select>
4.4.3 分页查询
结合PageHelper插件实现物理分页:
java复制// 调用前设置分页参数
PageHelper.startPage(1, 10); // 第1页,每页10条
List<User> users = userMapper.selectList(null);
PageInfo<User> pageInfo = new PageInfo<>(users);
注意:PageHelper的原理是基于ThreadLocal的拦截器,必须确保分页逻辑在同一线程内完成,异步场景需特殊处理。
5. 高级特性与性能优化
5.1 二级缓存配置
在mapper.xml中启用二级缓存:
xml复制<cache eviction="LRU" flushInterval="60000"
size="512" readOnly="true"/>
缓存策略说明:
eviction:淘汰策略(LRU/FIFO/SOFT/WEAK)flushInterval:自动刷新间隔(毫秒)size:缓存对象数量readOnly:是否只读(可读性性能更好)
避坑指南:二级缓存作用于namespace级别,多表关联查询时可能出现脏数据,建议在涉及关联查询的mapper中设置
<cache-ref>或直接禁用缓存。
5.2 动态SQL构建
使用<script>标签实现注解方式动态SQL:
java复制@Update("<script>" +
"UPDATE user " +
"<set>" +
" <if test='username != null'>username=#{username},</if>" +
" <if test='password != null'>password=#{password},</if>" +
"</set>" +
"WHERE id=#{id}" +
"</script>")
int updateSelective(User user);
5.3 批量操作优化
使用ExecutorType.BATCH模式提升批量操作性能:
java复制SqlSession sqlSession = sqlSessionTemplate.getSqlSessionFactory()
.openSession(ExecutorType.BATCH);
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
for (int i = 0; i < 1000; i++) {
mapper.insert(new User("user"+i, "pass"+i));
}
sqlSession.commit();
} finally {
sqlSession.close();
}
实测对比:批量模式下,插入1万条数据的耗时从12秒降至0.8秒。
6. 常见问题排查
6.1 参数绑定异常
典型错误:
code复制There is no getter for property named 'xxx' in 'class java.lang.Long'
解决方案:
- 使用
@Param注解明确参数名 - 基本类型参数建议使用包装类(如用
Integer代替int)
6.2 结果映射失败
常见现象:查询返回的字段值为null
排查步骤:
- 检查数据库字段名与实体类属性名是否匹配
- 确认是否开启驼峰转换
map-underscore-to-camel-case - 使用
<resultMap>显式指定映射关系
6.3 事务不生效
确保:
- 方法上有
@Transactional注解 - 抛出的是非检查型异常(RuntimeException)
- 调用方与被调用方在同一个代理类中(避免自调用)
6.4 SQL注入风险
错误示范:
java复制@Select("SELECT * FROM user WHERE username = '${username}'") // 危险!
List<User> selectByUsername(@Param("username") String username);
正确做法:
- 始终使用
#{}占位符 - 禁止直接拼接
${}(除非是动态表名等特殊场景)
7. MyBatis-Plus增强功能
7.1 通用Mapper
继承BaseMapper获得开箱即用的CRUD方法:
java复制public interface UserMapper extends BaseMapper<User> {
// 无需编写基础方法
}
// 使用示例
userMapper.selectById(1L);
userMapper.selectList(new QueryWrapper<User>().lambda()
.ge(User::getAge, 18));
7.2 Lambda表达式查询
避免硬编码字段名:
java复制List<User> users = userMapper.selectList(
Wrappers.<User>lambdaQuery()
.eq(User::getUsername, "admin")
.between(User::getAge, 18, 30)
);
7.3 自动填充功能
实现MetaObjectHandler处理创建时间/更新时间:
java复制@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime",
LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime",
LocalDateTime.class, LocalDateTime.now());
}
}
7.4 逻辑删除配置
全局配置逻辑删除字段:
yaml复制mybatis-plus:
global-config:
db-config:
logic-delete-field: isDeleted # 实体类字段名
logic-delete-value: 1 # 删除值
logic-not-delete-value: 0 # 未删除值
实体类添加@TableLogic注解:
java复制@TableLogic
private Integer isDeleted;
