《能省事"。SpringBoot+MyBatis-Plus:开发效率提升10倍!》
我是小坏,今天咱们聊点实在的。你还在手写增删改查吗?还在写那些重复的Mapper XML吗?还在为分页头疼吗?试试MyBatis-Plus,让你少写80%的代码!
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
一、开发者的真实痛点
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
看看这些场景你熟不熟:
场景1:新项目开始,建表、建实体、建Mapper、建Service、建Controller...一套下来几十个文件。
场景2:写个条件查询,XML里写一堆if标签:
xml
<select id="findUsers" resultType="User">
SELECT * FROM user WHERE 1=1
<if test="name != null">AND name like #{name}</if>
<if test="age != null">AND age = #{age}</if>
<if test="status != null">AND status = #{status}</if>
</select>
场景3:分页查询,每写一次都要:
java
// 手写分页SQL
String sql = "SELECT * FROM user LIMIT " + (page-1)*size + "," + size;
// 再查总数
String countSql = "SELECT COUNT(*) FROM user";
用MyBatis-Plus能解决啥:
- 自动生成基础代码
- 条件查询不用写XML
- 分页一行代码搞定
- 内置逻辑删除、字段填充
- 支持多租户数据隔离
二、3分钟快速上手
2.1 加个依赖
xml
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
2.2 写个配置
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
yaml
# application.yml
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段名
logic-delete-value: 1 # 逻辑已删除值
logic-not-delete-value: 0 # 逻辑未删除值
2.3 定义实体
java
@Data
@TableName("user") // 指定表名
public class User {
@TableId(type = IdType.AUTO) // 主键自增
private Long id;
private String name;
private Integer age;
private String email;
@TableLogic // 逻辑删除字段
private Integer deleted;
@TableField(fill = FieldFill.INSERT) // 插入时自动填充
private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时填充
private LocalDateTime updateTime;
}
2.4 写个Mapper(最简单版)
java
@Mapper
public interface UserMapper extends BaseMapper<User> {
// 继承BaseMapper就有了所有基础CRUD方法
// 不用写XML,不用写SQL
}
2.5 马上就能用
java
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserMapper userMapper;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// 直接查询
return userMapper.selectById(id);
}
@PostMapping
public String addUser(@RequestBody User user) {
// 直接插入
userMapper.insert(user);
return "添加成功";
}
}
三、核心功能:解放生产力
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
3.1 代码生成器(效率提升80%)
以前 :手动创建实体、Mapper、Service、Controller 现在:一键生成所有文件
java
public class CodeGenerator {
public static void main(String[] args) {
AutoGenerator generator = new AutoGenerator();
// 数据库配置
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/test");
dataSourceConfig.setUsername("root");
dataSourceConfig.setPassword("123456");
generator.setDataSource(dataSourceConfig);
// 全局配置
GlobalConfig globalConfig = new GlobalConfig();
globalConfig.setOutputDir(System.getProperty("user.dir") + "/src/main/java");
globalConfig.setAuthor("小坏");
globalConfig.setOpen(false); // 生成后不打开文件夹
generator.setGlobalConfig(globalConfig);
// 包配置
PackageConfig packageConfig = new PackageConfig();
packageConfig.setParent("com.example.demo");
packageConfig.setEntity("entity");
packageConfig.setMapper("mapper");
packageConfig.setService("service");
packageConfig.setController("controller");
generator.setPackageInfo(packageConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("user", "order", "product"); // 要生成的表
strategy.setNaming(NamingStrategy.underline_to_camel); // 下划线转驼峰
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true); // 使用Lombok
strategy.setRestControllerStyle(true); // REST风格Controller
generator.setStrategy(strategy);
// 执行生成
generator.execute();
}
}
运行后生成:
- User.java(实体类)
- UserMapper.java(Mapper接口)
- UserService.java(Service接口)
- UserServiceImpl.java(Service实现)
- UserController.java(Controller)
3.2 条件构造器(告别XML)
以前 :写一堆if标签的XML 现在:链式调用,可读性强
java
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 复杂条件查询
public List<User> findUsers(String name, Integer minAge, Integer maxAge, Integer status) {
QueryWrapper<User> wrapper = new QueryWrapper<>();
if (StringUtils.isNotBlank(name)) {
wrapper.like("name", name); // name like '%?%'
}
if (minAge != null) {
wrapper.ge("age", minAge); // age >= ?
}
if (maxAge != null) {
wrapper.le("age", maxAge); // age <= ?
}
if (status != null) {
wrapper.eq("status", status); // status = ?
}
wrapper.orderByDesc("create_time");
return userMapper.selectList(wrapper);
}
// 更简洁的Lambda写法(推荐)
public List<User> findUsersLambda(String name, Integer age) {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(name), User::getName, name)
.eq(age != null, User::getAge, age)
.orderByDesc(User::getCreateTime);
return userMapper.selectList(wrapper);
}
}
3.3 分页插件(一行代码搞定)
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
// 使用
@Service
public class UserService {
public Page<User> getUsersByPage(int pageNum, int pageSize, String keyword) {
Page<User> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotBlank(keyword), User::getName, keyword);
return userMapper.selectPage(page, wrapper);
}
}
3.4 自动填充(减少重复代码)
java
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 插入时自动填充
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "createBy", String.class, getCurrentUsername());
}
@Override
public void updateFill(MetaObject metaObject) {
// 更新时自动填充
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
this.strictUpdateFill(metaObject, "updateBy", String.class, getCurrentUsername());
}
}
3.5 逻辑删除(数据不真删)
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
java
// 实体类字段加上注解
@TableLogic
private Integer deleted;
// 使用时自动过滤已删除数据
List<User> users = userMapper.selectList(null); // 只查 deleted=0 的数据
// 删除变成更新
userMapper.deleteById(1L); // 实际上执行:UPDATE user SET deleted=1 WHERE id=1
// 想查被删除的数据
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("deleted", 1);
List<User> deletedUsers = userMapper.selectList(wrapper);
四、高级功能:解决业务痛点
4.1 多租户数据隔离
java
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户插件
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
@Override
public Expression getTenantId() {
// 从当前上下文中获取租户ID
String tenantId = TenantContext.getCurrentTenantId();
return new StringValue(tenantId);
}
@Override
public String getTenantIdColumn() {
return "tenant_id"; // 租户字段名
}
@Override
public boolean ignoreTable(String tableName) {
// 忽略不需要租户隔离的表
return tableName.startsWith("sys_"); // 系统表不隔离
}
});
interceptor.addInnerInterceptor(tenantInterceptor);
return interceptor;
}
}
4.2 乐观锁(防并发修改)
java
// 实体类添加版本字段
@Version
private Integer version;
// 更新时自动带版本号
User user = userMapper.selectById(1L);
user.setName("新名字");
userMapper.updateById(user); // 自动带上 version 条件
// SQL: UPDATE user SET name=?, version=? WHERE id=? AND version=?
4.3 枚举类型处理
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
java
// 定义枚举
@Getter
public enum UserStatus {
ENABLED(1, "启用"),
DISABLED(0, "禁用"),
LOCKED(2, "锁定");
private final Integer code;
private final String desc;
UserStatus(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
}
// 实体类使用
@TableField(value = "status", typeHandler = EnumTypeHandler.class)
private UserStatus status;
// 查询时直接使用枚举
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, UserStatus.ENABLED);
4.4 字段加解密
java
// 自定义TypeHandler
public class EncryptTypeHandler extends BaseTypeHandler<String> {
private final Encryptor encryptor = new AESEncryptor();
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) {
// 存数据库时加密
ps.setString(i, encryptor.encrypt(parameter));
}
@Override
public String getNullableResult(ResultSet rs, String columnName) {
// 取数据时解密
String value = rs.getString(columnName);
return value != null ? encryptor.decrypt(value) : null;
}
}
// 实体类使用
@TableField(value = "phone", typeHandler = EncryptTypeHandler.class)
private String phone;
五、实战:订单系统CRUD
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
5.1 传统写法 vs MyBatis-Plus写法
传统写法(100+行代码):
java
// OrderMapper.java(接口)
List<Order> findByCondition(OrderQuery query);
int countByCondition(OrderQuery query);
// OrderMapper.xml(XML,50+行)
<select id="findByCondition" resultType="Order">
SELECT * FROM orders WHERE 1=1
<if test="userId != null">AND user_id = #{userId}</if>
<if test="status != null">AND status = #{status}</if>
<if test="startTime != null">AND create_time >= #{startTime}</if>
<if test="endTime != null">AND create_time <= #{endTime}</if>
ORDER BY create_time DESC
LIMIT #{offset}, #{limit}
</select>
<select id="countByCondition" resultType="int">
SELECT COUNT(*) FROM orders WHERE 1=1
<!-- 重复一遍条件判断 -->
</select>
// OrderService.java(业务逻辑,30+行)
public PageResult<Order> queryOrders(OrderQuery query) {
int total = orderMapper.countByCondition(query);
List<Order> list = orderMapper.findByCondition(query);
return new PageResult<>(total, list);
}
MyBatis-Plus写法(20行代码):
java
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
public Page<Order> queryOrders(OrderQuery query) {
// 构造查询条件
LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(query.getUserId() != null, Order::getUserId, query.getUserId())
.eq(query.getStatus() != null, Order::getStatus, query.getStatus())
.ge(query.getStartTime() != null, Order::getCreateTime, query.getStartTime())
.le(query.getEndTime() != null, Order::getCreateTime, query.getEndTime())
.orderByDesc(Order::getCreateTime);
// 分页查询(自动统计总数)
Page<Order> page = new Page<>(query.getPageNum(), query.getPageSize());
return orderMapper.selectPage(page, wrapper);
}
// 统计各状态订单数量
public Map<String, Long> countByStatus() {
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.select("status, COUNT(*) as count")
.groupBy("status");
List<Map<String, Object>> list = orderMapper.selectMaps(wrapper);
// 转换结果...
}
}
5.2 复杂查询示例
java
// 1. 联表查询
public List<OrderVO> getOrderWithUser(Long userId) {
QueryWrapper<Order> wrapper = new QueryWrapper<>();
wrapper.select("o.*, u.name as userName")
.eq("o.user_id", userId)
.eq("o.deleted", 0)
.orderByDesc("o.create_time");
return orderMapper.selectOrderWithUser(wrapper);
}
// 自定义Mapper方法
@Select("SELECT o.*, u.name as userName FROM orders o " +
"LEFT JOIN user u ON o.user_id = u.id " +
"${ew.customSqlSegment}") // 使用Wrapper条件
List<OrderVO> selectOrderWithUser(@Param(Constants.WRAPPER) QueryWrapper<Order> wrapper);
// 2. 批量操作
public void batchUpdateStatus(List<Long> orderIds, Integer status) {
UpdateWrapper<Order> wrapper = new UpdateWrapper<>();
wrapper.in("id", orderIds)
.set("status", status)
.set("update_time", LocalDateTime.now());
orderMapper.update(null, wrapper); // 批量更新
}
// 3. 存在则更新,不存在则插入
public void saveOrUpdateOrder(Order order) {
// 自动判断:有id则更新,无id则插入
orderMapper.insertOrUpdate(order);
}
六、避坑指南
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
坑1:字段名不对应
java
// ❌ 错误:数据库是user_name,实体是userName,但没配置映射
private String userName;
// ✅ 正确1:使用@TableField指定
@TableField("user_name")
private String userName;
// ✅ 正确2:配置全局下划线转驼峰
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
坑2:分页失效
java
// ❌ 错误:没配置分页插件
public List<User> getUsers() {
Page<User> page = new Page<>(1, 10);
return userMapper.selectPage(page, null); // 不会分页!
}
// ✅ 正确:配置分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
坑3:逻辑删除字段类型
java
// ❌ 错误:用String类型,但配置的是Integer
@TableLogic
private String deleted; // 配置是1/0,这里是String
// ✅ 正确:类型匹配
@TableLogic
private Integer deleted; // 用Integer
// 或
@TableLogic
private Boolean deleted; // 用Boolean,配置true/false
七、性能优化建议
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
7.1 查询优化
java
// 只查询需要的字段
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.select(User::getId, User::getName, User::getAge) // 只查3个字段
.eq(User::getStatus, 1);
7.2 批量操作
java
// 批量插入(性能提升10倍)
List<User> userList = new ArrayList<>();
// ...添加数据
userMapper.insertBatchSomeColumn(userList); // 批量插入方法
// 批量更新
List<Long> ids = Arrays.asList(1L, 2L, 3L);
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.in("id", ids)
.set("status", 2);
userMapper.update(null, wrapper);
7.3 缓存使用
java
@Cacheable(value = "user", key = "#id")
public User getUserById(Long id) {
return userMapper.selectById(id);
}
@CacheEvict(value = "user", key = "#user.id")
public void updateUser(User user) {
userMapper.updateById(user);
}
八、今日要点总结
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
- 代码生成器:新项目、新表一键生成所有代码
- 条件构造器:告别XML,链式调用更清晰
- 分页插件:一行代码搞定分页查询
- 自动填充:创建时间、更新时间自动维护
- 逻辑删除:数据不真删,便于恢复和审计
- 高级功能:多租户、乐观锁、枚举处理
- 性能优化:字段选择、批量操作、缓存
对比一下:
| 功能 | 传统MyBatis | MyBatis-Plus | 效率提升 |
|---|---|---|---|
| 基础CRUD | 写XML,4个方法 | 继承BaseMapper,0行代码 | ∞ |
| 条件查询 | XML里写if标签 | 链式调用 | 3倍 |
| 分页查询 | 写2个SQL,手动计算 | selectPage一行代码 | 5倍 |
| 逻辑删除 | 每个查询加where条件 | @TableLogic自动过滤 | 10倍 |
| 字段填充 | 每个方法里set | 自动填充处理器 | 8倍 |
九、思考题
场景:你要开发一个电商后台管理系统,有50张表
- 如何快速开始开发?
- 如何实现统一的数据权限(不同人看不同数据)?
- 如何优化大批量数据导出的性能?
- 如何实现操作日志自动记录?
评论区聊聊你的方案,明儿咱们讲WebSocket。
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
明天预告:《SpringBoot+WebSocket:在线聊天室从0到1》
今日福利:关注后回复"MP代码生成",获取完整代码生成器配置和模板文件。
运营小贴士:
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
💡 互动:
- 你现在用的是什么ORM框架?有什么痛点?
- 投票:你会尝试MyBatis-Plus吗?
- 留言分享你的效率提升技巧
🎯 转发话术: "少写80%的代码!MyBatis-Plus让CRUD开发快到飞起!"
🎁 福利:
- 留言区抽3人送《MyBatis-Plus实战教程》
- 转发截图送企业级代码生成器模板