MybatisPlus Pro: Supercharging CRUD Development Efficiency
This article analyzes MybatisPlus Pro, explaining how it eliminates repetitive CRUD code in MyBatis‑Plus projects by providing a BaseController and utility classes that auto‑generate service and controller layers, while also detailing its internal mechanisms, advantages, drawbacks, suitable scenarios, and common pitfalls.
Why MybatisPlus Pro is needed
Even after adopting MyBatis‑Plus, developers still write repetitive code in Service and Controller layers:
Service interfaces repeatedly declare findById, findAll, insert, update, delete methods that merely delegate to BaseMapper.
Each new module requires a copy‑paste of CRUD controller methods, leading to hours of boiler‑plate for dozens of modules.
Code quality varies between developers, causing inconsistent validation and error handling.
Conditional queries require almost identical QueryWrapper configuration in every controller.
The goal of MyBatis‑Plus Pro is to automate these patterns so that most effort can focus on business logic.
What MybatisPlus Pro provides
MyBatis‑Plus Pro adds a set of ready‑made base classes that automatically handle CRUD, pagination, list, and conditional queries without writing any XML or controller code. It builds on the existing MyBatis‑Plus core without altering its behavior.
Quick start
Step 1 – Add dependency
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.15</version>
</dependency>Version 3.5.15 (released 2025‑11‑30) supports Spring Boot 4.0.0 and Jackson 3.0.
Step 2 – Create utility class
The utility class supplies three core functions: camel‑case ↔ snake‑case conversion, reflection‑based extraction of entity field values, and automatic generation of a QueryWrapper from non‑null fields.
public class ApprenticeUtil {
private static Pattern humpPattern = Pattern.compile("[A-Z]");
private static Pattern linePattern = Pattern.compile("_(\\w)");
// camelCase → snake_case
public static String humpToLine(String str) {
Matcher matcher = humpPattern.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
}
matcher.appendTail(sb);
return sb.toString();
}
// snake_case → camelCase
public static String lineToHump(String str) {
str = str.toLowerCase();
Matcher matcher = linePattern.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}
// Build QueryWrapper from non‑null fields
public static <E> QueryWrapper<E> getQueryWrapper(E entity) {
Field[] fields = entity.getClass().getDeclaredFields();
QueryWrapper<E> wrapper = new QueryWrapper<>();
for (Field field : fields) {
if (Modifier.isFinal(field.getModifiers())) continue;
field.setAccessible(true);
try {
Object val = field.get(entity);
if (val != null) {
String name = humpToLine(field.getName());
wrapper.eq(name, val);
}
} catch (IllegalAccessException e) {
return null;
}
}
return wrapper;
}
}Step 3 – Implement BaseController
public class BaseController<S extends IService<T>, T> {
@Autowired
protected S baseService;
@PostMapping("add")
public Result add(@RequestBody T entity) {
return Result.success(baseService.save(entity));
}
@PostMapping("update")
public Result update(@RequestBody T entity) {
return Result.success(baseService.updateById(entity));
}
@GetMapping("delete")
public Result delete(String id) {
return Result.success(baseService.removeById(id));
}
@GetMapping("detail")
public Result detail(String id) {
return Result.success(baseService.getById(id));
}
@PostMapping("list")
public Result list(@RequestBody T entity) {
QueryWrapper<T> wrapper = ApprenticeUtil.getQueryWrapper(entity);
if (wrapper == null) wrapper = new QueryWrapper<>();
return Result.success(baseService.list(wrapper));
}
@PostMapping("page")
public Result page(@RequestBody PageDto<T> pageDto) {
T entity = pageDto.getEntity();
QueryWrapper<T> wrapper = ApprenticeUtil.getQueryWrapper(entity);
if (wrapper == null) wrapper = new QueryWrapper<>();
IPage<T> page = new Page<>(pageDto.getPageNo(), pageDto.getPageSize());
return Result.success(baseService.page(page, wrapper));
}
}Extending this class for a concrete entity reduces the controller to a single line:
@RestController
@RequestMapping("/product")
public class ProductController extends BaseController<ProductService, Product> { }Underlying mechanism
SQL execution chain
The flow from controller invocation to database response is illustrated below:
BaseMapper injection mechanism
Spring creates a JDK dynamic proxy for every interface extending BaseMapper. Calls are intercepted by MapperProxy, which determines the intended operation.
MyBatis‑Plus’s SqlInjector generates MappedStatement objects for all generic methods during application startup. MybatisPlusInterceptor builds an interceptor chain that injects pagination, optimistic lock, multi‑tenant, and other enhancements before executor execution.
The method‑resolution logic distinguishes generic from custom methods and locates the appropriate MappedStatement for execution.
Wrapper construction process
When ApprenticeUtil.getQueryWrapper(entity) is called, the utility reflects over the entity, converts each non‑null field name from camelCase to snake_case, and adds an eq condition. The resulting QueryWrapper is passed to the service layer, producing a zero‑code conditional query.
Pros and Cons
Advantages
Development speed increases dramatically; a module can be functional with only a few lines of code, reducing effort by over 80 % compared with raw MyBatis.
Uniform code style lowers maintenance cost and onboarding friction.
No XML configuration is required for either mapper or controller layers.
Built‑in pagination, conditional queries, and other common features work out‑of‑the‑box.
Non‑intrusive design—original MyBatis can still be used for complex SQL.
Disadvantages
Complex multi‑table joins become cumbersome with Wrapper; raw SQL may be clearer.
Generic methods use SELECT *, which can cause performance issues on large tables.
String‑based field names in wrappers are prone to runtime errors; LambdaQueryWrapper is recommended for type safety.
Over‑encapsulation may limit flexibility for highly specialized business logic.
Usage scenarios and selection guidance
Recommended for small‑to‑medium projects, rapid‑iteration back‑office systems, and any application where CRUD dominates the workload.
Not advised for high‑performance transaction systems, complex reporting, or security‑sensitive environments that require hand‑crafted SQL.
Pitfall avoidance
Avoid overusing generic selectList with SELECT *; specify fields for performance‑critical queries.
Prefer LambdaQueryWrapper over string‑based wrappers for compile‑time safety.
Use MyBatis‑Plus’s built‑in pagination plugin instead of manual LIMIT clauses.
For queries involving three or more tables, write native SQL in mapper XML.
Follow camelCase naming for entity fields to ensure automatic mapping works correctly.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
