How MybatisPlus Pro Supercharges CRUD Development Efficiency
The article explains how MybatisPlus Pro extends MybatisPlus to eliminate repetitive Service and Controller code, provides a ready‑to‑use BaseController, automatic QueryWrapper generation, and deepens the understanding of its dynamic proxy, interceptor chain and SQL injection mechanisms, while also outlining its strengths, limitations, suitable scenarios and common pitfalls.
Why MybatisPlus Pro Is Needed
Typical single‑table CRUD with MyBatis requires writing XML mappers and repetitive Service/Controller code, leading to high code duplication and low development efficiency. The author lists four pain points: redundant Service methods, repetitive Controllers, inconsistent code quality, and duplicated condition‑query logic.
What MybatisPlus Pro Is
MybatisPlus Pro builds on MybatisPlus, adding a set of ready‑made templates and a BaseController that eliminates Service and Controller boilerplate, allowing developers to focus on business logic.
Two interpretations exist: a community‑driven extension (the version demonstrated) and the official Pro‑level features. This article focuses on the former.
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 SpringBoot 4.0.0 and Jackson 3.0.
Step 2 – Utility Class
The utility provides three core functions:
Camel‑case ↔ snake‑case conversion for automatic field‑column mapping.
Reflection‑based extraction of entity field values.
Automatic generation of QueryWrapper<E> from non‑null fields.
public class ApprenticeUtil {
private static Pattern humpPattern = Pattern.compile("[A-Z]");
private static Pattern linePattern = Pattern.compile("_(\\w)");
// camel → snake
public static String humpToLine(String str) { ... }
// snake → camel
public static String lineToHump(String str) { ... }
// generate QueryWrapper from entity
public static <E> QueryWrapper<E> getQueryWrapper(E entity) { ... }
}The method iterates over declared fields, skips final fields, makes them accessible, reads non‑null values, converts the field name to snake case, and adds an eq condition to the wrapper.
Step 3 – 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));
}
}All CRUD, pagination and list endpoints are provided without writing a single line of SQL.
Step 4 – Real‑World Usage
@Data
@TableName("product")
public class Product {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private BigDecimal price;
private Integer stock;
private LocalDateTime createTime;
}
public interface ProductService extends IService<Product> {}
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {}
@RestController
@RequestMapping("/product")
public class ProductController extends BaseController<ProductService, Product> {}Only the class declaration is needed; the entire CRUD API is ready.
Deep Dive into the Underlying Mechanism
The author explains how MybatisPlus Pro knows which table to operate on and how SQL is generated.
SQL Execution Chain
A flow diagram (image below) shows the path from Controller to database.
BaseMapper Injection Mechanism
MyBatis reads mybatis-config.xml to build a SqlSessionFactory. MybatisPlus adds a SqlInjector that generates MappedStatement objects for all BaseMapper methods during startup, and registers them in MyBatis’ configuration, eliminating the need for XML.
Four key techniques are highlighted:
Dynamic Proxy: Spring creates JDK proxies for all interfaces extending BaseMapper. Calls like productMapper.selectById(1L) are intercepted by MapperProxy, which determines the intended operation.
MappedStatement Auto‑Generation: The injector creates the SQL template, parameter and result metadata for each generic method.
Interceptor Chain: MybatisPlusInterceptor injects pagination, optimistic lock, multi‑tenant and other enhancements before the executor runs.
Method Resolution: The framework distinguishes between generic and custom methods and fetches the appropriate MappedStatement.
Wrapper Working Principle
When building a condition query, the wrapper converts method references (e.g., User::getName) into type‑safe column references, avoiding hard‑coded strings and compile‑time errors.
Pros and Cons
Advantages
Exponential boost in development speed – a module can be functional with only a BaseController inheritance, reducing code by >80%.
Uniform code style simplifies onboarding and maintenance.
Zero XML configuration for CRUD; Pro further removes Controller boilerplate.
Feature‑complete out‑of‑the‑box: built‑in pagination, rich condition builder (eq, like, between, orderBy).
Non‑intrusive – raw MyBatis can still be used for complex queries.
Disadvantages
Complex multi‑table joins become cumbersome with the Wrapper API.
Default select * can cause performance issues; developers must explicitly select needed columns.
String‑based wrappers hide column‑name changes; LambdaWrapper is recommended.
Over‑encapsulation may limit fine‑grained control in performance‑critical paths.
Usage Scenarios & Selection Guidance
Recommended for small‑to‑medium projects, rapid‑iteration back‑office systems, and any application where CRUD dominates (>80% of operations). Not advised for high‑throughput transaction systems, complex reporting, or security‑sensitive environments that require hand‑crafted SQL auditability.
Pitfall Guide
Avoid overusing generic selectList with SELECT *; specify fields for high‑frequency queries.
Prefer LambdaQueryWrapper over string‑based QueryWrapper for type safety.
Always use MybatisPlus’s pagination plugin instead of manual LIMIT.
For three‑table or more joins, write native SQL in XML rather than forcing the Wrapper.
Follow camel‑case naming for entity fields to keep automatic mapping reliable.
Conclusion
MybatisPlus Pro’s core goal is to free developers from repetitive code, delivering a “zero‑code” experience for most CRUD scenarios while preserving MyBatis’s flexibility for complex cases. It dramatically improves productivity for the majority of simple data‑driven modules, though it is not a silver bullet for all performance‑critical or multi‑join use cases.
MybatisPlus official documentation: https://baomidou.com MybatisPlusPro project repository: https://gitee.com/nirui-gitee/mybatis-plus-pro
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.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
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.
