Mybatis学习笔记(五)
分页插件与性能优化
分页插件配置
简要描述:MybatisPlus分页插件是基于物理分页实现的高性能分页解决方案,支持多种数据库的分页语法,能够自动识别数据库类型并生成对应的分页SQL。
核心概念:
- 物理分页:直接在SQL层面进行分页,性能优异
- 自动识别:自动识别数据库类型,生成对应分页语法
- 插件机制:基于Mybatis拦截器实现
- 多数据库支持:支持MySQL、PostgreSQL、Oracle、SQL Server等
- 性能优化:支持count查询优化、分页参数合理性检查
基础配置
/*** MybatisPlus配置类*/
@Configuration
@MapperScan("com.example.mapper")
public class MybatisPlusConfig {/*** 分页插件配置*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 分页插件PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();// 设置数据库类型(可选,会自动识别)paginationInterceptor.setDbType(DbType.MYSQL);// 设置最大单页限制数量,默认500条,-1不受限制paginationInterceptor.setMaxLimit(1000L);// 溢出总页数后是否进行处理(默认不处理)paginationInterceptor.setOverflow(false);// 生成countSql优化掉join现象paginationInterceptor.setOptimizeJoin(true);interceptor.addInnerInterceptor(paginationInterceptor);return interceptor;}
}
高级配置
/*** 高级分页配置*/
@Configuration
public class AdvancedPaginationConfig {/*** 自定义分页插件配置*/@Beanpublic MybatisPlusInterceptor advancedMybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 分页插件PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor() {@Overridepublic boolean willDoQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {// 自定义分页逻辑,可以在这里添加权限检查等IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);if (page != null && page.getSize() > 1000) {throw new RuntimeException("分页大小不能超过1000条");}return super.willDoQuery(executor, ms, parameter, rowBounds, resultHandler, boundSql);}};// 数据库类型paginationInterceptor.setDbType(DbType.MYSQL);// 最大单页限制paginationInterceptor.setMaxLimit(1000L);// 溢出处理paginationInterceptor.setOverflow(true);// count查询优化paginationInterceptor.setOptimizeJoin(true);interceptor.addInnerInterceptor(paginationInterceptor);// 乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());// 阻断插件(防止全表更新与删除)interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());return interceptor;}/*** 自定义分页方言(如果需要支持特殊数据库)*/@Beanpublic IDialect customDialect() {return new IDialect() {@Overridepublic boolean willDoCount(IPage<?> page, List<MappedStatement> countMs, BoundSql countSql, Object parameter) {// 自定义count查询逻辑return page.searchCount();}@Overridepublic String buildPaginationSql(String originalSql, long offset, long limit) {// 自定义分页SQL构建return originalSql + " LIMIT " + offset + ", " + limit;}};}
}
多数据源分页配置
/*** 多数据源分页配置*/
@Configuration
public class MultiDataSourcePaginationConfig {/*** 主数据源分页配置*/@Bean("masterPaginationInterceptor")public MybatisPlusInterceptor masterPaginationInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();paginationInterceptor.setDbType(DbType.MYSQL);paginationInterceptor.setMaxLimit(1000L);paginationInterceptor.setOptimizeJoin(true);interceptor.addInnerInterceptor(paginationInterceptor);return interceptor;}/*** 从数据源分页配置*/@Bean("slavePaginationInterceptor")public MybatisPlusInterceptor slavePaginationInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();paginationInterceptor.setDbType(DbType.MYSQL);paginationInterceptor.setMaxLimit(2000L); // 从库可以设置更大的限制paginationInterceptor.setOptimizeJoin(true);interceptor.addInnerInterceptor(paginationInterceptor);return interceptor;}
}
分页参数配置
# application.yml
mybatis-plus:configuration:# 分页相关配置map-underscore-to-camel-case: truecache-enabled: falsecall-setters-on-nulls: truejdbc-type-for-null: 'null'global-config:db-config:# 逻辑删除配置logic-delete-field: deletedlogic-delete-value: 1logic-not-delete-value: 0# 分页配置banner: false# 分页插件配置pagination:# 默认分页大小default-page-size: 10# 最大分页大小max-page-size: 1000# 是否启用分页合理化reasonable: true# 是否支持接口参数来传递分页参数support-methods-arguments: true# 分页参数名称page-size-zero: false
自定义分页处理器
/*** 自定义分页处理器*/
@Component
public class CustomPaginationHandler {/*** 分页参数预处理*/public <T> IPage<T> preprocessPage(IPage<T> page) {// 参数合理性检查if (page.getCurrent() <= 0) {page.setCurrent(1);}if (page.getSize() <= 0) {page.setSize(10);}// 最大分页限制if (page.getSize() > 1000) {page.setSize(1000);}return page;}/*** 分页结果后处理*/public <T> IPage<T> postprocessPage(IPage<T> page) {// 计算总页数long pages = page.getTotal() / page.getSize();if (page.getTotal() % page.getSize() != 0) {pages++;}page.setPages(pages);// 处理溢出情况if (page.getCurrent() > pages && pages > 0) {page.setCurrent(pages);}return page;}/*** 创建分页对象*/public <T> Page<T> createPage(Integer current, Integer size) {current = current != null && current > 0 ? current : 1;size = size != null && size > 0 ? size : 10;size = Math.min(size, 1000); // 限制最大分页大小return new Page<>(current, size);}/*** 创建分页对象(带排序)*/public <T> Page<T> createPage(Integer current, Integer size, String orderBy, String orderDirection) {Page<T> page = createPage(current, size);if (StringUtils.isNotBlank(orderBy)) {boolean isAsc = "asc".equalsIgnoreCase(orderDirection);// 安全的排序字段检查if (isValidOrderField(orderBy)) {OrderItem orderItem = new OrderItem();orderItem.setColumn(orderBy);orderItem.setAsc(isAsc);page.addOrder(orderItem);}}return page;}/*** 验证排序字段是否安全*/private boolean isValidOrderField(String field) {// 白名单验证,防止SQL注入Set<String> allowedFields = Set.of("id", "name", "age", "email", "phone", "create_time", "update_time", "status");return allowedFields.contains(field.toLowerCase());}
}
分页工具类
/*** 分页工具类*/
@UtilityClass
public class PageUtils {/*** 构建分页对象*/public static <T> Page<T> buildPage(PageRequest pageRequest) {Integer current = pageRequest.getCurrent();Integer size = pageRequest.getSize();current = current != null && current > 0 ? current : 1;size = size != null && size > 0 ? size : 10;size = Math.min(size, 1000);Page<T> page = new Page<>(current, size);// 处理排序if (CollectionUtils.isNotEmpty(pageRequest.getOrders())) {for (PageRequest.Order order : pageRequest.getOrders()) {if (isValidOrderField(order.getField())) {page.addOrder(new OrderItem(order.getField(), "asc".equalsIgnoreCase(order.getDirection())));}}}return page;}/*** 转换分页结果*/public static <T, R> IPage<R> convertPage(IPage<T> sourcePage, Function<T, R> converter) {Page<R> targetPage = new Page<>(sourcePage.getCurrent(), sourcePage.getSize(), sourcePage.getTotal());targetPage.setPages(sourcePage.getPages());targetPage.setSearchCount(sourcePage.searchCount());List<R> records = sourcePage.getRecords().stream().map(converter).collect(Collectors.toList());targetPage.setRecords(records);return targetPage;}/*** 批量转换分页结果*/public static <T, R> IPage<R> convertPageBatch(IPage<T> sourcePage, Function<List<T>, List<R>> batchConverter) {Page<R> targetPage = new Page<>(sourcePage.getCurrent(), sourcePage.getSize(), sourcePage.getTotal());targetPage.setPages(sourcePage.getPages());targetPage.setSearchCount(sourcePage.searchCount());List<R> records = batchConverter.apply(sourcePage.getRecords());targetPage.setRecords(records);return targetPage;}/*** 创建空分页结果*/public static <T> IPage<T> emptyPage(long current, long size) {return new Page<>(current, size, 0);}/*** 验证排序字段*/private static boolean isValidOrderField(String field) {if (StringUtils.isBlank(field)) {return false;}// 检查是否包含危险字符String dangerousChars = "';--/**/union|select|insert|update|delete|drop|create|alter|exec|execute";String lowerField = field.toLowerCase();for (String dangerous : dangerousChars.split("\\|")) {if (lowerField.contains(dangerous)) {return false;}}// 只允许字母、数字、下划线return field.matches("^[a-zA-Z_][a-zA-Z0-9_]*$");}/*** 计算偏移量*/public static long calculateOffset(long current, long size) {return (current - 1) * size;}/*** 计算总页数*/public static long calculatePages(long total, long size) {if (size == 0) {return 0;}return (total + size - 1) / size;}
}/*** 分页请求DTO*/
@Data
public class PageRequest {private Integer current = 1;private Integer size = 10;private List<Order> orders;@Datapublic static class Order {private String field;private String direction = "asc";}
}
Page分页对象
简要描述:Page是MybatisPlus提供的分页对象,封装了分页查询的所有参数和结果,支持链式调用和灵活的分页配置。
核心概念:
- 分页参数:当前页、每页大小、总记录数、总页数
- 排序支持:支持多字段排序和动态排序
- 优化查询:支持禁用count查询提升性能
- 结果封装:自动计算分页相关信息
- 类型安全:泛型支持,类型安全
基础使用
@Service
public class UserService extends ServiceImpl<UserMapper, User> {/*** 基础分页查询*/public IPage<User> basicPageQuery(Integer current, Integer size) {// 创建分页对象Page<User> page = new Page<>(current, size);// 执行分页查询return this.page(page);}/*** 带条件的分页查询*/public IPage<User> pageQueryWithCondition(Integer current, Integer size, String name, Integer status) {Page<User> page = new Page<>(current, size);LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.like(StringUtils.isNotBlank(name), User::getName, name).eq(status != null, User::getStatus, status).eq(User::getDeleted, 0);return this.page(page, wrapper);}/*** 带排序的分页查询*/public IPage<User> pageQueryWithOrder(Integer current, Integer size, String orderBy, String orderDirection) {Page<User> page = new Page<>(current, size);// 添加排序if (StringUtils.isNotBlank(orderBy)) {boolean isAsc = "asc".equalsIgnoreCase(orderDirection);page.addOrder(new OrderItem(orderBy, isAsc));}return this.page(page);}/*** 多字段排序分页查询*/public IPage<User> pageQueryWithMultiOrder(Integer current, Integer size, List<OrderField> orderFields) {Page<User> page = new Page<>(current, size);// 添加多个排序字段if (CollectionUtils.isNotEmpty(orderFields)) {for (OrderField orderField : orderFields) {boolean isAsc = "asc".equalsIgnoreCase(orderField.getDirection());page.addOrder(new OrderItem(orderField.getField(), isAsc));}}return this.page(page);}
}
Page对象详解
/*** Page对象使用示例*/
@Service
public class PageExampleService {/*** Page对象属性详解*/public void pagePropertiesExample() {Page<User> page = new Page<>();// 基础属性设置page.setCurrent(1); // 当前页,从1开始page.setSize(10); // 每页大小page.setTotal(100); // 总记录数(通常由框架自动设置)page.setPages(10); // 总页数(通常由框架自动计算)// 优化设置page.setSearchCount(true); // 是否查询总记录数,默认truepage.setOptimizeCountSql(true); // 是否优化count查询,默认truepage.setMaxLimit(1000L); // 单页最大限制// 排序设置page.addOrder(OrderItem.asc("name")); // 升序page.addOrder(OrderItem.desc("create_time")); // 降序// 获取分页信息long current = page.getCurrent(); // 当前页long size = page.getSize(); // 每页大小long total = page.getTotal(); // 总记录数long pages = page.getPages(); // 总页数List<User> records = page.getRecords(); // 当前页数据// 分页计算boolean hasNext = page.hasNext(); // 是否有下一页boolean hasPrevious = page.hasPrevious(); // 是否有上一页long offset = page.offset(); // 偏移量}/*** 禁用count查询(提升性能)*/public IPage<User> pageWithoutCount(Integer current, Integer size) {Page<User> page = new Page<>(current, size);// 禁用count查询,适用于不需要总数的场景page.setSearchCount(false);LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getDeleted, 0).orderByDesc(User::getCreateTime);return userMapper.selectPage(page, wrapper);}/*** 自定义count查询*/public IPage<User> pageWithCustomCount(Integer current, Integer size, String keyword) {Page<User> page = new Page<>(current, size);// 构建查询条件LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getDeleted, 0);if (StringUtils.isNotBlank(keyword)) {wrapper.and(w -> w.like(User::getName, keyword).or().like(User::getEmail, keyword));}// 先执行count查询Long total = userMapper.selectCount(wrapper);page.setTotal(total);if (total > 0) {// 有数据才执行分页查询page.setSearchCount(false); // 禁用自动countList<User> records = userMapper.selectList(wrapper.last("LIMIT " + page.offset() + ", " + page.getSize()));page.setRecords(records);}return page;}/*** 流式分页查询(大数据量)*/public void streamPageQuery(Consumer<List<User>> processor) {int pageSize = 1000;int current = 1;while (true) {Page<User> page = new Page<>(current, pageSize);page.setSearchCount(false); // 禁用count查询LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getDeleted, 0).orderByAsc(User::getId); // 确保排序稳定IPage<User> result = userMapper.selectPage(page, wrapper);if (CollectionUtils.isEmpty(result.getRecords())) {break; // 没有更多数据}// 处理当前页数据processor.accept(result.getRecords());if (result.getRecords().size() < pageSize) {break; // 最后一页}current++;}}
}
分页结果处理
/*** 分页结果处理示例*/
@Service
public class PageResultService {/*** 分页结果转换*/public IPage<UserVO> convertPageResult(IPage<User> userPage) {// 方式1:使用工具类转换return PageUtils.convertPage(userPage, this::convertToVO);}/*** 批量转换分页结果(性能更好)*/public IPage<UserVO> batchConvertPageResult(IPage<User> userPage) {return PageUtils.convertPageBatch(userPage, users -> {// 批量查询关联数据List<Long> deptIds = users.stream().map(User::getDeptId).distinct().collect(Collectors.toList());Map<Long, Dept> deptMap = deptService.listByIds(deptIds).stream().collect(Collectors.toMap(Dept::getId, Function.identity()));// 批量转换return users.stream().map(user -> {UserVO vo = convertToVO(user);vo.setDeptName(Optional.ofNullable(deptMap.get(user.getDeptId())).map(Dept::getName).orElse(""));return vo;}).collect(Collectors.toList());});}/*** 分页结果包装*/public PageResult<UserVO> wrapPageResult(IPage<User> userPage) {IPage<UserVO> voPage = convertPageResult(userPage);return PageResult.<UserVO>builder().current(voPage.getCurrent()).size(voPage.getSize()).total(voPage.getTotal()).pages(voPage.getPages()).records(voPage.getRecords()).hasNext(voPage.hasNext()).hasPrevious(voPage.hasPrevious()).build();}/*** 实体转VO*/private UserVO convertToVO(User user) {UserVO vo = new UserVO();BeanUtils.copyProperties(user, vo);// 其他转换逻辑return vo;}
}/*** 自定义分页结果包装类*/
@Data
@Builder
public class PageResult<T> {private Long current; // 当前页private Long size; // 每页大小private Long total; // 总记录数private Long pages; // 总页数private List<T> records; // 当前页数据private Boolean hasNext; // 是否有下一页private Boolean hasPrevious; // 是否有上一页public static <T> PageResultBuilder<T> builder() {return new PageResultBuilder<>();}
}
自定义分页实现
简要描述:当MybatisPlus内置的分页功能无法满足复杂业务需求时,可以通过自定义分页实现来扩展分页功能,支持复杂查询、多表关联、聚合统计等场景。
核心概念:
- 自定义SQL:编写复杂的分页查询SQL
- 手动分页:手动计算偏移量和限制条件
- 结果映射:自定义结果集映射
- 性能优化:针对特定场景的性能优化
- 扩展功能:支持复杂的业务逻辑
自定义Mapper分页
/*** 自定义分页Mapper*/
@Mapper
public interface CustomUserMapper extends BaseMapper<User> {/*** 自定义分页查询(注解方式)*/@Select("SELECT u.*, d.name as dept_name " +"FROM user u LEFT JOIN dept d ON u.dept_id = d.id " +"WHERE u.deleted = 0 " +"${ew.customSqlSegment}")IPage<UserWithDeptVO> selectUserWithDeptPage(IPage<UserWithDeptVO> page, @Param(Constants.WRAPPER) Wrapper<User> wrapper);/*** 自定义count查询*/@Select("SELECT COUNT(*) FROM user u " +"LEFT JOIN dept d ON u.dept_id = d.id " +"WHERE u.deleted = 0 " +"${ew.customSqlSegment}")Long selectUserWithDeptCount(@Param(Constants.WRAPPER) Wrapper<User> wrapper);/*** 复杂统计分页查询*/@Select("SELECT u.dept_id, d.name as dept_name, " +"COUNT(*) as user_count, " +"AVG(u.age) as avg_age, " +"SUM(u.salary) as total_salary " +"FROM user u " +"LEFT JOIN dept d ON u.dept_id = d.id " +"WHERE u.deleted = 0 " +"${ew.customSqlSegment} " +"GROUP BY u.dept_id, d.name")IPage<DeptStatVO> selectDeptStatPage(IPage<DeptStatVO> page, @Param(Constants.WRAPPER) Wrapper<User> wrapper);/*** XML方式自定义分页查询*/IPage<UserDetailVO> selectUserDetailPage(IPage<UserDetailVO> page, @Param("query") UserQueryDTO query);/*** 手动分页查询*/List<UserWithDeptVO> selectUserWithDeptList(@Param("query") UserQueryDTO query, @Param("offset") Long offset, @Param("limit") Long limit);/*** 手动count查询*/Long selectUserWithDeptListCount(@Param("query") UserQueryDTO query);
}
XML自定义分页
<!-- CustomUserMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.CustomUserMapper"><!-- 结果映射 --><resultMap id="UserDetailVOMap" type="com.example.vo.UserDetailVO"><id column="id" property="id"/><result column="name" property="name"/><result column="email" property="email"/><result column="phone" property="phone"/><result column="age" property="age"/><result column="salary" property="salary"/><result column="status" property="status"/><result column="create_time" property="createTime"/><result column="dept_name" property="deptName"/><result column="role_names" property="roleNames"/><result column="permission_count" property="permissionCount"/></resultMap><!-- 复杂分页查询 --><select id="selectUserDetailPage" resultMap="UserDetailVOMap">SELECT u.id,u.name,u.email,u.phone,u.age,u.salary,u.status,u.create_time,d.name as dept_name,GROUP_CONCAT(r.name) as role_names,COUNT(DISTINCT p.id) as permission_countFROM user uLEFT JOIN dept d ON u.dept_id = d.idLEFT JOIN user_role ur ON u.id = ur.user_idLEFT JOIN role r ON ur.role_id = r.idLEFT JOIN role_permission rp ON r.id = rp.role_idLEFT JOIN permission p ON rp.permission_id = p.id<where>u.deleted = 0<if test="query.name != null and query.name != ''">AND u.name LIKE CONCAT('%', #{query.name}, '%')</if><if test="query.email != null and query.email != ''">AND u.email LIKE CONCAT('%', #{query.email}, '%')</if><if test="query.status != null">AND u.status = #{query.status}</if><if test="query.deptIds != null and query.deptIds.size() > 0">AND u.dept_id IN<foreach collection="query.deptIds" item="deptId" open="(" separator="," close=")">#{deptId}</foreach></if><if test="query.minAge != null">AND u.age >= #{query.minAge}</if><if test="query.maxAge != null">AND u.age <= #{query.maxAge}</if><if test="query.createTimeStart != null">AND u.create_time >= #{query.createTimeStart}</if><if test="query.createTimeEnd != null">AND u.create_time <= #{query.createTimeEnd}</if></where>GROUP BY u.id, u.name, u.email, u.phone, u.age, u.salary, u.status, u.create_time, d.name<if test="query.orderBy != null and query.orderBy != ''">ORDER BY <choose><when test="query.orderBy == 'name'">u.name</when><when test="query.orderBy == 'age'">u.age</when><when test="query.orderBy == 'salary'">u.salary</when><when test="query.orderBy == 'createTime'">u.create_time</when><otherwise>u.create_time</otherwise></choose><if test="query.orderDirection != null and query.orderDirection == 'asc'">ASC</if><if test="query.orderDirection == null or query.orderDirection != 'asc'">DESC</if></if></select><!-- 手动分页查询 --><select id="selectUserWithDeptList" resultType="com.example.vo.UserWithDeptVO">SELECT u.id,u.name,u.email,u.phone,u.age,u.status,u.create_time,d.name as deptNameFROM user uLEFT JOIN dept d ON u.dept_id = d.id<where>u.deleted = 0<if test="query.name != null and query.name != ''">AND u.name LIKE CONCAT('%', #{query.name}, '%')</if><if test="query.status != null">AND u.status = #{query.status}</if><if test="query.deptIds != null and query.deptIds.size() > 0">AND u.dept_id IN<foreach collection="query.deptIds" item="deptId" open="(" separator="," close=")">#{deptId}</foreach></if></where>ORDER BY u.create_time DESCLIMIT #{offset}, #{limit}</select><!-- 手动count查询 --><select id="selectUserWithDeptListCount" resultType="java.lang.Long">SELECT COUNT(*)FROM user uLEFT JOIN dept d ON u.dept_id = d.id<where>u.deleted = 0<if test="query.name != null and query.name != ''">AND u.name LIKE CONCAT('%', #{query.name}, '%')</if><if test="query.status != null">AND u.status = #{query.status}</if><if test="query.deptIds != null and query.deptIds.size() > 0">AND u.dept_id IN<foreach collection="query.deptIds" item="deptId" open="(" separator="," close=")">#{deptId}</foreach></if></where></select></mapper>
自定义分页Service
/*** 自定义分页Service*/
@Service
public class CustomPageService {@Autowiredprivate CustomUserMapper customUserMapper;/*** 复杂关联查询分页*/public IPage<UserWithDeptVO> pageUserWithDept(Integer current, Integer size, UserQueryDTO query) {Page<UserWithDeptVO> page = new Page<>(current, size);// 构建查询条件LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName()).eq(query.getStatus() != null, User::getStatus, query.getStatus()).in(CollectionUtils.isNotEmpty(query.getDeptIds()), User::getDeptId, query.getDeptIds());return customUserMapper.selectUserWithDeptPage(page, wrapper);}/*** 手动分页实现*/public IPage<UserWithDeptVO> manualPageUserWithDept(Integer current, Integer size, UserQueryDTO query) {// 计算偏移量long offset = (current - 1) * size;// 查询总数Long total = customUserMapper.selectUserWithDeptListCount(query);// 查询数据List<UserWithDeptVO> records = Collections.emptyList();if (total > 0) {records = customUserMapper.selectUserWithDeptList(query, offset, (long) size);}// 构建分页结果Page<UserWithDeptVO> page = new Page<>(current, size, total);page.setRecords(records);return page;}/*** 分步查询分页(先查主表,再查关联数据)*/public IPage<UserDetailVO> stepPageUserDetail(Integer current, Integer size, UserQueryDTO query) {// 第一步:分页查询用户基础信息Page<User> userPage = new Page<>(current, size);LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName()).eq(query.getStatus() != null, User::getStatus, query.getStatus()).eq(User::getDeleted, 0);IPage<User> userResult = userService.page(userPage, wrapper);// 第二步:批量查询关联数据List<UserDetailVO> detailList = Collections.emptyList();if (CollectionUtils.isNotEmpty(userResult.getRecords())) {List<Long> userIds = userResult.getRecords().stream().map(User::getId).collect(Collectors.toList());// 批量查询部门信息List<Long> deptIds = userResult.getRecords().stream().map(User::getDeptId).filter(Objects::nonNull).distinct().collect(Collectors.toList());Map<Long, Dept> deptMap = Collections.emptyMap();if (CollectionUtils.isNotEmpty(deptIds)) {deptMap = deptService.listByIds(deptIds).stream().collect(Collectors.toMap(Dept::getId, Function.identity()));}// 批量查询角色信息Map<Long, List<Role>> userRoleMap = roleService.getUserRoleMap(userIds);// 组装结果final Map<Long, Dept> finalDeptMap = deptMap;detailList = userResult.getRecords().stream().map(user -> {UserDetailVO detail = new UserDetailVO();BeanUtils.copyProperties(user, detail);// 设置部门信息Optional.ofNullable(finalDeptMap.get(user.getDeptId())).ifPresent(dept -> detail.setDeptName(dept.getName()));// 设置角色信息List<Role> roles = userRoleMap.getOrDefault(user.getId(), Collections.emptyList());detail.setRoleNames(roles.stream().map(Role::getName).collect(Collectors.joining(",")));return detail;}).collect(Collectors.toList());}// 构建分页结果Page<UserDetailVO> resultPage = new Page<>(userResult.getCurrent(), userResult.getSize(), userResult.getTotal());resultPage.setRecords(detailList);return resultPage;}/*** 游标分页(适用于大数据量场景)*/public CursorPageResult<User> cursorPageQuery(Long lastId, Integer size) {size = Math.min(size, 1000); // 限制最大查询数量LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getDeleted, 0).gt(lastId != null, User::getId, lastId).orderByAsc(User::getId).last("LIMIT " + (size + 1)); // 多查一条用于判断是否有下一页List<User> records = userService.list(wrapper);boolean hasNext = records.size() > size;if (hasNext) {records = records.subList(0, size); // 移除多查的一条}Long nextCursor = null;if (CollectionUtils.isNotEmpty(records)) {nextCursor = records.get(records.size() - 1).getId();}return CursorPageResult.<User>builder().records(records).hasNext(hasNext).nextCursor(nextCursor).size(size).build();}
}/*** 游标分页结果*/
@Data
@Builder
public class CursorPageResult<T> {private List<T> records; // 当前页数据private Boolean hasNext; // 是否有下一页private Long nextCursor; // 下一页游标private Integer size; // 页大小
}/*** 用户详情VO*/
@Data
public class UserDetailVO {private Long id;private String name;private String email;private String phone;private Integer age;private BigDecimal salary;private Integer status;private LocalDateTime createTime;private String deptName;private String roleNames;private Integer permissionCount;
}/*** 用户部门VO*/
@Data
public class UserWithDeptVO {private Long id;private String name;private String email;private String phone;private Integer age;private Integer status;private LocalDateTime createTime;private String deptName;
}/*** 部门统计VO*/
@Data
public class DeptStatVO {private Long deptId;private String deptName;private Long userCount;private Double avgAge;private BigDecimal totalSalary;
}
分页性能优化
简要描述:分页查询是系统中最常见的操作之一,合理的性能优化可以显著提升系统响应速度和用户体验,特别是在大数据量场景下。
核心概念:
- 索引优化:合理使用索引提升查询效率
- Count优化:减少或优化count查询
- 深分页优化:解决深分页性能问题
- 缓存策略:合理使用缓存减少数据库压力
- SQL优化:优化查询语句结构
索引优化策略
/*** 分页查询索引优化*/
@Service
public class PageOptimizationService {/*** 基于索引的分页查询优化*/public IPage<User> optimizedPageQuery(Integer current, Integer size, UserQueryDTO query) {Page<User> page = new Page<>(current, size);LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();// 优化1:使用复合索引 (status, create_time, id)wrapper.eq(User::getStatus, query.getStatus()).ge(query.getCreateTimeStart() != null, User::getCreateTime, query.getCreateTimeStart()).le(query.getCreateTimeEnd() != null, User::getCreateTime, query.getCreateTimeEnd()).eq(User::getDeleted, 0).orderByDesc(User::getCreateTime, User::getId); // 确保排序字段有索引return userService.page(page, wrapper);}/*** 覆盖索引优化(只查询索引字段)*/public IPage<UserSimpleVO> coveringIndexQuery(Integer current, Integer size, Integer status) {Page<UserSimpleVO> page = new Page<>(current, size);// 只查询索引覆盖的字段,避免回表QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.select("id", "name", "status", "create_time") // 这些字段在索引中.eq("status", status).eq("deleted", 0).orderByDesc("create_time");IPage<User> result = userService.page(new Page<>(current, size), wrapper);// 转换为VOList<UserSimpleVO> voList = result.getRecords().stream().map(user -> {UserSimpleVO vo = new UserSimpleVO();vo.setId(user.getId());vo.setName(user.getName());vo.setStatus(user.getStatus());vo.setCreateTime(user.getCreateTime());return vo;}).collect(Collectors.toList());Page<UserSimpleVO> voPage = new Page<>(result.getCurrent(), result.getSize(), result.getTotal());voPage.setRecords(voList);return voPage;}/*** 强制使用索引*/public IPage<User> forceIndexQuery(Integer current, Integer size, String indexName) {Page<User> page = new Page<>(current, size);QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.eq("deleted", 0).orderByDesc("create_time").last("FORCE INDEX (" + indexName + ")"); // 强制使用指定索引return userService.page(page, wrapper);}
}/*** 用户简单VO(用于覆盖索引查询)*/
@Data
public class UserSimpleVO {private Long id;private String name;private Integer status;private LocalDateTime createTime;
}
Count查询优化
/*** Count查询优化策略*/
@Service
public class CountOptimizationService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 禁用count查询(适用于不需要总数的场景)*/public IPage<User> pageWithoutCount(Integer current, Integer size) {Page<User> page = new Page<>(current, size);page.setSearchCount(false); // 禁用count查询LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getDeleted, 0).orderByDesc(User::getCreateTime);return userService.page(page, wrapper);}/*** 缓存count结果*/public IPage<User> pageWithCachedCount(Integer current, Integer size, UserQueryDTO query) {String countKey = "user:count:" + DigestUtils.md5Hex(JSON.toJSONString(query));// 尝试从缓存获取countLong cachedCount = (Long) redisTemplate.opsForValue().get(countKey);Page<User> page = new Page<>(current, size);if (cachedCount != null) {// 使用缓存的count,禁用自动count查询page.setTotal(cachedCount);page.setSearchCount(false);} else {// 启用count查询page.setSearchCount(true);}LambdaQueryWrapper<User> wrapper = buildQueryWrapper(query);IPage<User> result = userService.page(page, wrapper);// 缓存count结果(5分钟)if (cachedCount == null) {redisTemplate.opsForValue().set(countKey, result.getTotal(), 5, TimeUnit.MINUTES);}return result;}/*** 估算count(适用于大数据量场景)*/public IPage<User> pageWithEstimatedCount(Integer current, Integer size, UserQueryDTO query) {Page<User> page = new Page<>(current, size);// 对于大数据量,使用估算countif (current <= 10) { // 前10页使用精确countpage.setSearchCount(true);} else { // 后续页使用估算countpage.setSearchCount(false);// 估算总数(基于前10页的平均值)long estimatedTotal = estimateTotal(query);page.setTotal(estimatedTotal);}LambdaQueryWrapper<User> wrapper = buildQueryWrapper(query);return userService.page(page, wrapper);}/*** 分段count查询*/public IPage<User> pageWithSegmentedCount(Integer current, Integer size, UserQueryDTO query) {Page<User> page = new Page<>(current, size);// 只在第一页查询countif (current == 1) {page.setSearchCount(true);} else {page.setSearchCount(false);// 从缓存或session中获取总数Long totalFromCache = getTotalFromCache(query);if (totalFromCache != null) {page.setTotal(totalFromCache);}}LambdaQueryWrapper<User> wrapper = buildQueryWrapper(query);IPage<User> result = userService.page(page, wrapper);// 缓存总数if (current == 1) {cacheTotalCount(query, result.getTotal());}return result;}/*** 构建查询条件*/private LambdaQueryWrapper<User> buildQueryWrapper(UserQueryDTO query) {LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName()).eq(query.getStatus() != null, User::getStatus, query.getStatus()).eq(User::getDeleted, 0).orderByDesc(User::getCreateTime);return wrapper;}/*** 估算总数*/private long estimateTotal(UserQueryDTO query) {// 基于统计信息或采样估算// 这里简化处理,实际可以基于数据库统计信息return 100000L; // 示例值}/*** 从缓存获取总数*/private Long getTotalFromCache(UserQueryDTO query) {String key = "user:total:" + DigestUtils.md5Hex(JSON.toJSONString(query));return (Long) redisTemplate.opsForValue().get(key);}/*** 缓存总数*/private void cacheTotalCount(UserQueryDTO query, Long total) {String key = "user:total:" + DigestUtils.md5Hex(JSON.toJSONString(query));redisTemplate.opsForValue().set(key, total, 10, TimeUnit.MINUTES);}
}
深分页优化
/*** 深分页优化策略*/
@Service
public class DeepPageOptimizationService {/*** 游标分页(推荐用于深分页)*/public CursorPageResult<User> cursorBasedPagination(Long lastId, Integer size) {size = Math.min(size, 100); // 限制页大小LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getDeleted, 0).gt(lastId != null, User::getId, lastId).orderByAsc(User::getId).last("LIMIT " + (size + 1)); // 多查一条判断是否有下一页List<User> records = userService.list(wrapper);boolean hasNext = records.size() > size;if (hasNext) {records = records.subList(0, size);}Long nextCursor = null;if (!records.isEmpty()) {nextCursor = records.get(records.size() - 1).getId();}return CursorPageResult.<User>builder().records(records).hasNext(hasNext).nextCursor(nextCursor).size(size).build();}/*** 子查询优化深分页*/public IPage<User> subQueryOptimizedPage(Integer current, Integer size) {// 限制深分页if (current > 1000) {throw new BusinessException("分页深度不能超过1000页");}Page<User> page = new Page<>(current, size);// 使用子查询先获取ID,再关联查询完整数据String subQuerySql = "SELECT u.* FROM user u " +"INNER JOIN (" +" SELECT id FROM user " +" WHERE deleted = 0 " +" ORDER BY create_time DESC " +" LIMIT " + page.offset() + ", " + page.getSize() +") t ON u.id = t.id " +"ORDER BY u.create_time DESC";// 执行自定义SQLList<User> records = userMapper.selectByCustomSql(subQuerySql);// 单独查询总数LambdaQueryWrapper<User> countWrapper = new LambdaQueryWrapper<>();countWrapper.eq(User::getDeleted, 0);Long total = userService.count(countWrapper);page.setRecords(records);page.setTotal(total);page.setSearchCount(false);return page;}/*** 延迟关联优化*/public IPage<UserVO> deferredJoinPage(Integer current, Integer size, UserQueryDTO query) {Page<User> page = new Page<>(current, size);// 第一步:只查询主表ID和排序字段QueryWrapper<User> wrapper = new QueryWrapper<>();wrapper.select("id", "create_time").eq("deleted", 0).like(StringUtils.isNotBlank(query.getName()), "name", query.getName()).orderByDesc("create_time");IPage<User> idResult = userService.page(page, wrapper);// 第二步:根据ID批量查询完整数据List<UserVO> voList = Collections.emptyList();if (!idResult.getRecords().isEmpty()) {List<Long> ids = idResult.getRecords().stream().map(User::getId).collect(Collectors.toList());// 批量查询完整用户信息List<User> users = userService.listByIds(ids);// 保持原有排序Map<Long, User> userMap = users.stream().collect(Collectors.toMap(User::getId, Function.identity()));voList = ids.stream().map(userMap::get).filter(Objects::nonNull).map(this::convertToVO).collect(Collectors.toList());}Page<UserVO> voPage = new Page<>(idResult.getCurrent(), idResult.getSize(), idResult.getTotal());voPage.setRecords(voList);return voPage;}/*** 分片查询(超大数据量)*/public void shardedPageQuery(UserQueryDTO query, Consumer<List<User>> processor) {int shardSize = 10000; // 每片大小int pageSize = 1000; // 每页大小// 获取数据范围DateRange dateRange = getDateRange(query);List<DateRange> shards = splitDateRange(dateRange, shardSize);for (DateRange shard : shards) {int current = 1;while (true) {LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.eq(User::getDeleted, 0).ge(User::getCreateTime, shard.getStart()).lt(User::getCreateTime, shard.getEnd()).orderByAsc(User::getId).last("LIMIT " + ((current - 1) * pageSize) + ", " + pageSize);List<User> records = userService.list(wrapper);if (records.isEmpty()) {break;}processor.accept(records);if (records.size() < pageSize) {break;}current++;}}}private UserVO convertToVO(User user) {UserVO vo = new UserVO();BeanUtils.copyProperties(user, vo);return vo;}private DateRange getDateRange(UserQueryDTO query) {// 获取查询的日期范围return new DateRange(query.getCreateTimeStart(), query.getCreateTimeEnd());}private List<DateRange> splitDateRange(DateRange range, int shardSize) {// 将日期范围分片List<DateRange> shards = new ArrayList<>();// 实现分片逻辑return shards;}
}/*** 日期范围*/
@Data
@AllArgsConstructor
public class DateRange {private LocalDateTime start;private LocalDateTime end;
}
缓存优化策略
/*** 分页缓存优化*/
@Service
public class PageCacheOptimizationService {@Autowiredprivate RedisTemplate<String, Object> redisTemplate;/*** 分页结果缓存*/@Cacheable(value = "userPage", key = "#current + ':' + #size + ':' + #query.hashCode()")public IPage<UserVO> cachedPageQuery(Integer current, Integer size, UserQueryDTO query) {Page<User> page = new Page<>(current, size);LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();wrapper.like(StringUtils.isNotBlank(query.getName()), User::getName, query.getName()).eq(query.getStatus() != null, User::getStatus, query.getStatus()).eq(User::getDeleted, 0).orderByDesc(User::getCreateTime);IPage<User> result = userService.page(page, wrapper);// 转换为VOList<UserVO> voList = result.getRecords().stream().map(this::convertToVO).collect(Collectors.toList());Page<UserVO> voPage = new Page<>(result.getCurrent(), result.getSize(), result.getTotal());voPage.setRecords(voList);return voPage;}/*** 热点数据预加载*/@PostConstructpublic void preloadHotData() {// 预加载前几页热点数据CompletableFuture.runAsync(() -> {try {UserQueryDTO query = new UserQueryDTO();query.setStatus(1); // 活跃用户// 预加载前5页for (int i = 1; i <= 5; i++) {cachedPageQuery(i, 20, query);Thread.sleep(100); // 避免对数据库造成压力}} catch (Exception e) {log.error("预加载热点数据失败", e);}});}/*** 分层缓存策略*/public IPage<UserVO> tieredCachePageQuery(Integer current, Integer size, UserQueryDTO query) {String cacheKey = buildCacheKey(current, size, query);// L1缓存:本地缓存(Caffeine)IPage<UserVO> result = localCache.get(cacheKey);if (result != null) {return result;}// L2缓存:Redis缓存result = (IPage<UserVO>) redisTemplate.opsForValue().get(cacheKey);if (result != null) {// 回写到本地缓存localCache.put(cacheKey, result);return result;}// L3:数据库查询result = doPageQuery(current, size, query);// 写入缓存localCache.put(cacheKey, result);redisTemplate.opsForValue().set(cacheKey, result, 5, TimeUnit.MINUTES);return result;}/*** 缓存失效策略*/@CacheEvict(value = "userPage", allEntries = true)public void evictUserPageCache() {// 当用户数据发生变化时,清除相关缓存}/*** 智能缓存更新*/public void smartCacheUpdate(User user) {// 只清除受影响的缓存页String pattern = "userPage:*";Set<String> keys = redisTemplate.keys(pattern);if (keys != null) {for (String key : keys) {// 判断是否需要清除该缓存if (shouldEvictCache(key, user)) {redisTemplate.delete(key);}}}}private boolean shouldEvictCache(String cacheKey, User user) {// 根据缓存key和用户变更情况判断是否需要清除缓存// 实现具体的判断逻辑return true;}private String buildCacheKey(Integer current, Integer size, UserQueryDTO query) {return String.format("userPage:%d:%d:%s", current, size, DigestUtils.md5Hex(JSON.toJSONString(query)));}private IPage<UserVO> doPageQuery(Integer current, Integer size, UserQueryDTO query) {// 实际的数据库查询逻辑return cachedPageQuery(current, size, query);}private UserVO convertToVO(User user) {UserVO vo = new UserVO();BeanUtils.copyProperties(user, vo);return vo;}@Autowiredprivate Cache<String, IPage<UserVO>> localCache; // 本地缓存
}
分页插件源码分析
简要描述:深入理解MybatisPlus分页插件的实现原理,有助于更好地使用分页功能和进行性能调优。
核心概念:
- 拦截器机制:基于Mybatis拦截器实现
- SQL解析:解析和重写SQL语句
- 方言支持:支持多种数据库方言
- 插件链:支持多个插件协同工作
- 扩展机制:支持自定义扩展
分页插件核心组件
/*** 分页插件核心类分析*/
public class PaginationInnerInterceptor implements InnerInterceptor {/*** 数据库方言*/private IDialect dialect;/*** 溢出总页数后是否进行处理*/protected boolean overflow = false;/*** 单页分页条数限制*/protected Long maxLimit;/*** 数据库类型*/private DbType dbType;/*** 查询前处理*/@Overridepublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);if (page == null) {return;}// 处理参数handleParameter(ms, boundSql, page);// 处理分页SQLhandlePageSql(executor, ms, boundSql, page);}/*** 处理分页参数*/protected void handleParameter(MappedStatement ms, BoundSql boundSql, IPage<?> page) {// 检查分页参数合法性if (page.getSize() < 0) {throw new MybatisPlusException("分页大小不能小于0");}// 检查单页限制if (maxLimit != null && page.getSize() > maxLimit) {throw new MybatisPlusException("分页大小超过限制: " + maxLimit);}// 处理溢出if (overflow && page.getCurrent() > page.getPages()) {page.setCurrent(1);}}/*** 处理分页SQL*/protected void handlePageSql(Executor executor, MappedStatement ms, BoundSql boundSql, IPage<?> page) throws SQLException {// 1. 处理count查询if (page.isSearchCount()) {Long total = executeCountSql(executor, ms, boundSql, page);page.setTotal(total);// 如果总数为0,直接返回if (total == 0) {return;}}// 2. 构建分页SQLString originalSql = boundSql.getSql();String pageSql = buildPageSql(originalSql, page);// 3. 替换原始SQLPluginUtils.MPBoundSql mpBoundSql = PluginUtils.mpBoundSql(boundSql);mpBoundSql.sql(pageSql);}/*** 执行count查询*/protected Long executeCountSql(Executor executor, MappedStatement ms, BoundSql boundSql, IPage<?> page) throws SQLException {// 构建count SQLString countSql = buildCountSql(boundSql.getSql(), page);// 创建count查询的MappedStatementMappedStatement countMs = buildCountMappedStatement(ms, countSql);// 执行count查询List<Object> result = executor.query(countMs, ms.getParameterMap(), RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER, new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), boundSql.getParameterObject()));return result.isEmpty() ? 0L : Long.parseLong(result.get(0).toString());}/*** 构建count SQL*/protected String buildCountSql(String originalSql, IPage<?> page) {// 使用JSqlParser解析SQLtry {Select select = (Select) CCJSqlParserUtil.parse(originalSql);PlainSelect plainSelect = (PlainSelect) select.getSelectBody();// 移除ORDER BY子句(count查询不需要排序)plainSelect.setOrderByElements(null);// 检查是否有GROUP BY或DISTINCTif (hasGroupBy(plainSelect) || hasDistinct(plainSelect)) {// 使用子查询方式return "SELECT COUNT(*) FROM (" + select.toString() + ") tmp_count";} else {// 直接替换SELECT子句plainSelect.setSelectItems(Collections.singletonList(new SelectExpressionItem(new Column("COUNT(*)"), null)));return select.toString();}} catch (JSQLParserException e) {// 解析失败,使用简单包装return "SELECT COUNT(*) FROM (" + originalSql + ") tmp_count";}}/*** 构建分页SQL*/protected String buildPageSql(String originalSql, IPage<?> page) {// 根据数据库方言构建分页SQLreturn dialect.buildPaginationSql(originalSql, page.offset(), page.getSize());}/*** 检查是否有GROUP BY*/private boolean hasGroupBy(PlainSelect plainSelect) {return plainSelect.getGroupBy() != null && !plainSelect.getGroupBy().getGroupByExpressions().isEmpty();}/*** 检查是否有DISTINCT*/private boolean hasDistinct(PlainSelect plainSelect) {return plainSelect.getDistinct() != null;}/*** 构建count查询的MappedStatement*/private MappedStatement buildCountMappedStatement(MappedStatement ms, String countSql) {MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId() + "_COUNT", ms.getSqlSource(), SqlCommandType.SELECT);builder.resource(ms.getResource()).fetchSize(ms.getFetchSize()).timeout(ms.getTimeout()).statementType(ms.getStatementType()).keyGenerator(ms.getKeyGenerator()).keyProperty(StringUtils.join(ms.getKeyProperties(), ",")).keyColumn(StringUtils.join(ms.getKeyColumns(), ",")).databaseId(ms.getDatabaseId()).lang(ms.getLang()).resultOrdered(ms.isResultOrdered()).resultSets(StringUtils.join(ms.getResultSets(), ",")).resultMaps(Collections.singletonList(new ResultMap.Builder(ms.getConfiguration(), "count", Long.class, new ArrayList<>()).build())).flushCacheRequired(ms.isFlushCacheRequired()).useCache(ms.isUseCache()).cache(ms.getCache());return builder.build();}
}
数据库方言实现
/*** MySQL方言实现*/
public class MySqlDialect implements IDialect {@Overridepublic String buildPaginationSql(String originalSql, long offset, long limit) {StringBuilder sql = new StringBuilder(originalSql.length() + 20);sql.append(originalSql);if (offset == 0) {sql.append(" LIMIT ").append(limit);} else {sql.append(" LIMIT ").append(offset).append(", ").append(limit);}return sql.toString();}@Overridepublic DbType getDbType() {return DbType.MYSQL;}
}/*** Oracle方言实现*/
public class OracleDialect implements IDialect {@Overridepublic String buildPaginationSql(String originalSql, long offset, long limit) {StringBuilder sql = new StringBuilder();sql.append("SELECT * FROM (").append("SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM (").append(originalSql).append(") TMP_PAGE WHERE ROWNUM <= ").append(offset + limit).append(") WHERE ROW_ID > ").append(offset);return sql.toString();}@Overridepublic DbType getDbType() {return DbType.ORACLE;}
}/*** PostgreSQL方言实现*/
public class PostgreSqlDialect implements IDialect {@Overridepublic String buildPaginationSql(String originalSql, long offset, long limit) {StringBuilder sql = new StringBuilder(originalSql.length() + 20);sql.append(originalSql);sql.append(" LIMIT ").append(limit);if (offset > 0) {sql.append(" OFFSET ").append(offset);}return sql.toString();}@Overridepublic DbType getDbType() {return DbType.POSTGRE_SQL;}
}
自定义分页插件扩展
/*** 自定义分页插件扩展*/
@Component
public class CustomPaginationInterceptor implements InnerInterceptor {private static final Logger log = LoggerFactory.getLogger(CustomPaginationInterceptor.class);@Overridepublic void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {IPage<?> page = ParameterUtils.findPage(parameter).orElse(null);if (page == null) {return;}// 自定义逻辑:记录分页查询日志logPageQuery(ms, page, boundSql);// 自定义逻辑:分页参数校验validatePageParameters(page);// 自定义逻辑:性能监控monitorPageQuery(ms, page);}/*** 记录分页查询日志*/private void logPageQuery(MappedStatement ms, IPage<?> page, BoundSql boundSql) {if (log.isDebugEnabled()) {log.debug("分页查询 - Mapper: {}, 当前页: {}, 页大小: {}, SQL: {}", ms.getId(), page.getCurrent(), page.getSize(), boundSql.getSql());}}/*** 分页参数校验*/private void validatePageParameters(IPage<?> page) {// 检查页码if (page.getCurrent() < 1) {throw new IllegalArgumentException("页码不能小于1");}// 检查页大小if (page.getSize() < 1) {throw new IllegalArgumentException("页大小不能小于1");}if (page.getSize() > 1000) {throw new IllegalArgumentException("页大小不能超过1000");}// 检查深分页if (page.getCurrent() > 10000) {log.warn("检测到深分页查询,当前页: {}, 建议使用游标分页", page.getCurrent());}}/*** 性能监控*/private void monitorPageQuery(MappedStatement ms, IPage<?> page) {// 记录查询指标String metricName = "page.query." + ms.getId().replace(".", "_");// 记录分页深度Metrics.gauge(metricName + ".depth", page.getCurrent());// 记录页大小Metrics.gauge(metricName + ".size", page.getSize());// 计数器Metrics.counter(metricName + ".count").increment();}
}/*** 分页插件配置*/
@Configuration
public class PaginationConfig {/*** 配置分页插件*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加自定义分页插件interceptor.addInnerInterceptor(new CustomPaginationInterceptor());// 添加分页插件PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();paginationInterceptor.setDbType(DbType.MYSQL);paginationInterceptor.setMaxLimit(1000L); // 单页最大1000条paginationInterceptor.setOverflow(false); // 禁止溢出interceptor.addInnerInterceptor(paginationInterceptor);return interceptor;}/*** 自定义方言(如果需要)*/@Beanpublic IDialect customDialect() {return new CustomMySqlDialect();}
}/*** 自定义MySQL方言*/
public class CustomMySqlDialect extends MySqlDialect {@Overridepublic String buildPaginationSql(String originalSql, long offset, long limit) {// 添加自定义逻辑,比如SQL优化提示String optimizedSql = addSqlHints(originalSql);return super.buildPaginationSql(optimizedSql, offset, limit);}private String addSqlHints(String sql) {// 添加MySQL查询提示if (sql.toUpperCase().startsWith("SELECT")) {return sql.replaceFirst("(?i)SELECT", "SELECT /*+ USE_INDEX */");}return sql;}
}
分页插件调试工具
/*** 分页插件调试工具*/
@Component
public class PaginationDebugger {private static final Logger log = LoggerFactory.getLogger(PaginationDebugger.class);/*** 分析分页SQL性能*/public void analyzePaginationPerformance(String sql, IPage<?> page) {log.info("=== 分页SQL性能分析 ===");log.info("原始SQL: {}", sql);log.info("分页参数: 当前页={}, 页大小={}, 偏移量={}", page.getCurrent(), page.getSize(), page.offset());// 分析SQL复杂度analyzeSqlComplexity(sql);// 检查索引使用checkIndexUsage(sql);// 估算查询成本estimateQueryCost(sql, page);}/*** 分析SQL复杂度*/private void analyzeSqlComplexity(String sql) {int joinCount = StringUtils.countMatches(sql.toUpperCase(), "JOIN");int subQueryCount = StringUtils.countMatches(sql, "(");int functionCount = StringUtils.countMatches(sql.toUpperCase(), "COUNT(") + StringUtils.countMatches(sql.toUpperCase(), "SUM(") +StringUtils.countMatches(sql.toUpperCase(), "AVG(");log.info("SQL复杂度分析: JOIN数量={}, 子查询数量={}, 函数数量={}", joinCount, subQueryCount, functionCount);if (joinCount > 3) {log.warn("检测到多表关联({}个JOIN),建议优化查询", joinCount);}if (subQueryCount > 2) {log.warn("检测到复杂子查询,建议考虑分步查询");}}/*** 检查索引使用*/private void checkIndexUsage(String sql) {// 检查WHERE子句中的字段if (sql.toUpperCase().contains("WHERE")) {log.info("检测到WHERE条件,请确保相关字段有索引");}// 检查ORDER BY字段if (sql.toUpperCase().contains("ORDER BY")) {log.info("检测到ORDER BY子句,请确保排序字段有索引");}// 检查GROUP BY字段if (sql.toUpperCase().contains("GROUP BY")) {log.info("检测到GROUP BY子句,请确保分组字段有索引");}}/*** 估算查询成本*/private void estimateQueryCost(String sql, IPage<?> page) {// 简单的成本估算int baseCost = 1;if (sql.toUpperCase().contains("JOIN")) {baseCost *= 2;}if (sql.toUpperCase().contains("GROUP BY")) {baseCost *= 3;}if (page.offset() > 10000) {baseCost *= 5; // 深分页成本很高}log.info("估算查询成本: {} (1=低, 5=高)", Math.min(baseCost, 5));if (baseCost > 3) {log.warn("查询成本较高,建议优化");}}/*** 生成分页优化建议*/public List<String> generateOptimizationSuggestions(String sql, IPage<?> page) {List<String> suggestions = new ArrayList<>();// 深分页建议if (page.offset() > 10000) {suggestions.add("使用游标分页替代深分页");suggestions.add("考虑使用子查询优化深分页");}// 大页面建议if (page.getSize() > 100) {suggestions.add("减少单页数据量,提升用户体验");}// SQL优化建议if (sql.toUpperCase().contains("SELECT *")) {suggestions.add("避免使用SELECT *,只查询需要的字段");}if (StringUtils.countMatches(sql.toUpperCase(), "JOIN") > 2) {suggestions.add("考虑分步查询减少复杂关联");}return suggestions;}
}
代码生成器
简要描述:MybatisPlus代码生成器是一个强大的工具,能够根据数据库表结构自动生成Entity、Mapper、Service、Controller等代码,大大提升开发效率。
核心概念:
- 模板引擎:支持Velocity、Freemarker、Beetl等模板引擎
- 策略配置:灵活的代码生成策略
- 自定义模板:支持自定义代码模板
- 包结构配置:自动生成规范的包结构
- 字段映射:数据库字段到Java属性的映射
代码生成器配置
简要描述:通过配置代码生成器的各种参数,可以生成符合项目规范的代码结构。
核心概念:
- 全局配置:输出目录、作者信息、文件覆盖等
- 数据源配置:数据库连接信息
- 包配置:各层代码的包路径
- 策略配置:命名策略、字段策略等
- 模板配置:自定义模板路径
基础配置示例
/*** 代码生成器基础配置*/
@Component
public class CodeGeneratorConfig {/*** 基础代码生成器配置*/public void generateCode() {// 代码生成器AutoGenerator mpg = new AutoGenerator();// 全局配置GlobalConfig gc = new GlobalConfig();String projectPath = System.getProperty("user.dir");gc.setOutputDir(projectPath + "/src/main/java");gc.setAuthor("mybatis-plus");gc.setOpen(false); // 是否打开输出目录gc.setFileOverride(true); // 是否覆盖已有文件gc.setServiceName("%sService"); // service 命名方式gc.setServiceImplName("%sServiceImpl"); // service impl 命名方式gc.setMapperName("%sMapper"); // mapper 命名方式gc.setXmlName("%sMapper"); // mapper xml 命名方式gc.setControllerName("%sController"); // controller 命名方式gc.setIdType(IdType.ASSIGN_ID); // 主键策略gc.setDateType(DateType.ONLY_DATE); // 日期类型gc.setSwagger2(true); // 实体属性 Swagger2 注解mpg.setGlobalConfig(gc);// 数据源配置DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8");dsc.setDriverName("com.mysql.cj.jdbc.Driver");dsc.setUsername("root");dsc.setPassword("password");mpg.setDataSource(dsc);// 包配置PackageConfig pc = new PackageConfig();pc.setModuleName("system"); // 模块名pc.setParent("com.example"); // 父包名pc.setEntity("entity"); // Entity包名pc.setMapper("mapper"); // Mapper包名pc.setService("service"); // Service包名pc.setServiceImpl("service.impl"); // ServiceImpl包名pc.setController("controller"); // Controller包名mpg.setPackageInfo(pc);// 自定义配置InjectionConfig cfg = new InjectionConfig() {@Overridepublic void initMap() {// 自定义属性注入Map<String, Object> map = new HashMap<>();map.put("date", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));this.setMap(map);}};// 自定义输出配置List<FileOutConfig> focList = new ArrayList<>();// 自定义配置会被优先输出focList.add(new FileOutConfig("/templates/mapper.xml.vm") {@Overridepublic String outputFile(TableInfo tableInfo) {// 自定义输出文件名return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;}});cfg.setFileOutConfigList(focList);mpg.setCfg(cfg);// 配置模板TemplateConfig templateConfig = new TemplateConfig();templateConfig.setXml(null); // 配置自定义输出模板,指定自定义模板路径mpg.setTemplate(templateConfig);// 策略配置StrategyConfig strategy = new StrategyConfig();strategy.setNaming(NamingStrategy.underline_to_camel); // 数据库表映射到实体的命名策略strategy.setColumnNaming(NamingStrategy.underline_to_camel); // 数据库表字段映射到实体的命名策略strategy.setEntityLombokModel(true); // 是否为lombok模型strategy.setRestControllerStyle(true); // 生成 @RestController 控制器strategy.setInclude("user", "role", "permission"); // 需要包含的表名strategy.setControllerMappingHyphenStyle(true); // 驼峰转连字符strategy.setTablePrefix(pc.getModuleName() + "_"); // 表前缀mpg.setStrategy(strategy);// 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有!mpg.setTemplateEngine(new FreemarkerTemplateEngine());mpg.execute();}
}
高级配置示例
/*** 高级代码生成器配置*/
@Component
public class AdvancedCodeGenerator {/*** 高级配置代码生成*/public void generateAdvancedCode() {// 代码生成器AutoGenerator mpg = new AutoGenerator();// 全局配置GlobalConfig gc = buildGlobalConfig();mpg.setGlobalConfig(gc);// 数据源配置DataSourceConfig dsc = buildDataSourceConfig();mpg.setDataSource(dsc);// 包配置PackageConfig pc = buildPackageConfig();mpg.setPackageInfo(pc);// 自定义配置InjectionConfig cfg = buildInjectionConfig(pc);mpg.setCfg(cfg);// 配置模板TemplateConfig templateConfig = buildTemplateConfig();mpg.setTemplate(templateConfig);// 策略配置StrategyConfig strategy = buildStrategyConfig();mpg.setStrategy(strategy);// 选择模板引擎mpg.setTemplateEngine(new VelocityTemplateEngine());mpg.execute();}/*** 构建全局配置*/private GlobalConfig buildGlobalConfig() {GlobalConfig gc = new GlobalConfig();String projectPath = System.getProperty("user.dir");gc.setOutputDir(projectPath + "/src/main/java").setAuthor("Code Generator").setOpen(false).setFileOverride(true).setActiveRecord(false) // 不需要ActiveRecord特性的请改为false.setEnableCache(false) // XML 二级缓存.setBaseResultMap(true) // XML ResultMap.setBaseColumnList(true) // XML columList.setDateType(DateType.ONLY_DATE).setSwagger2(true); // 实体属性 Swagger2 注解// 自定义文件命名,注意 %s 会自动填充表实体属性!gc.setEntityName("%sEntity").setMapperName("%sMapper").setXmlName("%sMapper").setServiceName("%sService").setServiceImplName("%sServiceImpl").setControllerName("%sController");return gc;}/*** 构建数据源配置*/private DataSourceConfig buildDataSourceConfig() {DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8").setDriverName("com.mysql.cj.jdbc.Driver").setUsername("root").setPassword("password").setDbType(DbType.MYSQL);return dsc;}/*** 构建包配置*/private PackageConfig buildPackageConfig() {PackageConfig pc = new PackageConfig();pc.setModuleName("system").setParent("com.example.mybatisplus").setEntity("entity").setMapper("mapper").setService("service").setServiceImpl("service.impl").setController("controller").setXml("mapper.xml");return pc;}/*** 构建自定义配置*/private InjectionConfig buildInjectionConfig(PackageConfig pc) {InjectionConfig cfg = new InjectionConfig() {@Overridepublic void initMap() {Map<String, Object> map = new HashMap<>();map.put("date", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));map.put("author", "Code Generator");map.put("version", "1.0.0");map.put("projectName", "MyBatis Plus Demo");this.setMap(map);}};// 自定义输出配置List<FileOutConfig> focList = new ArrayList<>();String projectPath = System.getProperty("user.dir");// 自定义Mapper XML生成focList.add(new FileOutConfig("/templates/mapper.xml.vm") {@Overridepublic String outputFile(TableInfo tableInfo) {return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;}});// 自定义DTO生成focList.add(new FileOutConfig("/templates/dto.java.vm") {@Overridepublic String outputFile(TableInfo tableInfo) {return projectPath + "/src/main/java/com/example/mybatisplus/dto/"+ tableInfo.getEntityName() + "DTO.java";}});// 自定义VO生成focList.add(new FileOutConfig("/templates/vo.java.vm") {@Overridepublic String outputFile(TableInfo tableInfo) {return projectPath + "/src/main/java/com/example/mybatisplus/vo/"+ tableInfo.getEntityName() + "VO.java";}});cfg.setFileOutConfigList(focList);return cfg;}/*** 构建模板配置*/private TemplateConfig buildTemplateConfig() {TemplateConfig templateConfig = new TemplateConfig();// 配置自定义输出模板templateConfig.setEntity("/templates/entity.java").setMapper("/templates/mapper.java").setService("/templates/service.java").setServiceImpl("/templates/serviceImpl.java").setController("/templates/controller.java").setXml(null); // 指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别return templateConfig;}/*** 构建策略配置*/private StrategyConfig buildStrategyConfig() {StrategyConfig strategy = new StrategyConfig();// 数据库表配置strategy.setNaming(NamingStrategy.underline_to_camel) // 数据库表映射到实体的命名策略.setColumnNaming(NamingStrategy.underline_to_camel) // 数据库表字段映射到实体的命名策略.setInclude("sys_user", "sys_role", "sys_permission") // 需要包含的表名,允许正则表达式.setExclude("test_table") // 需要排除的表名,允许正则表达式.setTablePrefix("sys_") // 表前缀.setFieldPrefix("is_", "has_"); // 字段前缀// 实体类配置strategy.setEntityLombokModel(true) // 是否为lombok模型.setEntityTableFieldAnnotationEnable(true) // 是否生成实体时,生成字段注解.setEntityColumnConstant(true) // 是否生成字段常量.setEntityBuilderModel(false) // 是否为构建者模型.setEntitySerialVersionUID(true) // 是否生成serialVersionUID.setChainModel(false) // 是否为链式模型.setEntityBooleanColumnRemoveIsPrefix(true); // Boolean类型字段是否移除is前缀// Controller配置strategy.setRestControllerStyle(true) // 生成 @RestController 控制器.setControllerMappingHyphenStyle(true); // 驼峰转连字符// 填充字段配置List<TableFill> tableFillList = new ArrayList<>();tableFillList.add(new TableFill("create_time", FieldFill.INSERT));tableFillList.add(new TableFill("update_time", FieldFill.INSERT_UPDATE));tableFillList.add(new TableFill("create_by", FieldFill.INSERT));tableFillList.add(new TableFill("update_by", FieldFill.INSERT_UPDATE));strategy.setTableFillList(tableFillList);// 乐观锁字段配置strategy.setVersionFieldName("version");// 逻辑删除字段配置strategy.setLogicDeleteFieldName("deleted");return strategy;}
}
自定义模板配置
/*** 自定义模板配置*/
@Component
public class CustomTemplateGenerator {/*** 使用自定义模板生成代码*/public void generateWithCustomTemplate() {// 代码生成器AutoGenerator mpg = new AutoGenerator();// 全局配置GlobalConfig gc = new GlobalConfig();String projectPath = System.getProperty("user.dir");gc.setOutputDir(projectPath + "/src/main/java").setAuthor("Custom Generator").setOpen(false).setFileOverride(true).setSwagger2(true);mpg.setGlobalConfig(gc);// 数据源配置DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8").setDriverName("com.mysql.cj.jdbc.Driver").setUsername("root").setPassword("password");mpg.setDataSource(dsc);// 包配置PackageConfig pc = new PackageConfig();pc.setParent("com.example.custom").setEntity("domain").setMapper("repository").setService("application").setServiceImpl("application.impl").setController("interfaces.web");mpg.setPackageInfo(pc);// 自定义配置InjectionConfig cfg = new InjectionConfig() {@Overridepublic void initMap() {Map<String, Object> map = new HashMap<>();map.put("basePackage", "com.example.custom");map.put("apiVersion", "v1");map.put("moduleName", "user");this.setMap(map);}};// 自定义输出配置List<FileOutConfig> focList = new ArrayList<>();// 生成Repository接口focList.add(new FileOutConfig("/templates/repository.java.vm") {@Overridepublic String outputFile(TableInfo tableInfo) {return projectPath + "/src/main/java/com/example/custom/repository/"+ tableInfo.getEntityName() + "Repository.java";}});// 生成Repository实现类focList.add(new FileOutConfig("/templates/repositoryImpl.java.vm") {@Overridepublic String outputFile(TableInfo tableInfo) {return projectPath + "/src/main/java/com/example/custom/repository/impl/"+ tableInfo.getEntityName() + "RepositoryImpl.java";}});// 生成Application ServicefocList.add(new FileOutConfig("/templates/applicationService.java.vm") {@Overridepublic String outputFile(TableInfo tableInfo) {return projectPath + "/src/main/java/com/example/custom/application/"+ tableInfo.getEntityName() + "ApplicationService.java";}});// 生成FacadefocList.add(new FileOutConfig("/templates/facade.java.vm") {@Overridepublic String outputFile(TableInfo tableInfo) {return projectPath + "/src/main/java/com/example/custom/interfaces/facade/"+ tableInfo.getEntityName() + "Facade.java";}});cfg.setFileOutConfigList(focList);mpg.setCfg(cfg);// 配置模板TemplateConfig templateConfig = new TemplateConfig();templateConfig.setEntity("/templates/domain.java").setMapper("/templates/mapper.java").setService(null) // 不生成默认Service.setServiceImpl(null) // 不生成默认ServiceImpl.setController(null) // 不生成默认Controller.setXml("/templates/mapper.xml");mpg.setTemplate(templateConfig);// 策略配置StrategyConfig strategy = new StrategyConfig();strategy.setNaming(NamingStrategy.underline_to_camel).setColumnNaming(NamingStrategy.underline_to_camel).setEntityLombokModel(true).setEntityTableFieldAnnotationEnable(true).setInclude("user", "role").setTablePrefix("t_");mpg.setStrategy(strategy);// 选择模板引擎mpg.setTemplateEngine(new VelocityTemplateEngine());mpg.execute();}
}
批量生成配置
/*** 批量代码生成器*/
@Component
public class BatchCodeGenerator {/*** 批量生成多个模块的代码*/public void batchGenerate() {// 定义模块配置List<ModuleConfig> modules = Arrays.asList(new ModuleConfig("user", "用户模块", Arrays.asList("sys_user", "sys_user_role")),new ModuleConfig("role", "角色模块", Arrays.asList("sys_role", "sys_role_permission")),new ModuleConfig("permission", "权限模块", Arrays.asList("sys_permission", "sys_menu")));// 为每个模块生成代码for (ModuleConfig module : modules) {generateModuleCode(module);}}/*** 生成单个模块代码*/private void generateModuleCode(ModuleConfig moduleConfig) {AutoGenerator mpg = new AutoGenerator();// 全局配置GlobalConfig gc = new GlobalConfig();String projectPath = System.getProperty("user.dir");gc.setOutputDir(projectPath + "/src/main/java").setAuthor("Batch Generator").setOpen(false).setFileOverride(true).setSwagger2(true).setServiceName("%sService").setServiceImplName("%sServiceImpl").setMapperName("%sMapper").setControllerName("%sController");mpg.setGlobalConfig(gc);// 数据源配置DataSourceConfig dsc = new DataSourceConfig();dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8").setDriverName("com.mysql.cj.jdbc.Driver").setUsername("root").setPassword("password");mpg.setDataSource(dsc);// 包配置PackageConfig pc = new PackageConfig();pc.setModuleName(moduleConfig.getModuleName()).setParent("com.example.modules").setEntity("entity").setMapper("mapper").setService("service").setServiceImpl("service.impl").setController("controller");mpg.setPackageInfo(pc);// 自定义配置InjectionConfig cfg = new InjectionConfig() {@Overridepublic void initMap() {Map<String, Object> map = new HashMap<>();map.put("moduleName", moduleConfig.getModuleName());map.put("moduleComment", moduleConfig.getComment());map.put("date", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));this.setMap(map);}};// 自定义输出配置List<FileOutConfig> focList = new ArrayList<>();// 生成模块配置文件focList.add(new FileOutConfig("/templates/moduleConfig.java.vm") {@Overridepublic String outputFile(TableInfo tableInfo) {return projectPath + "/src/main/java/com/example/modules/" + moduleConfig.getModuleName()+ "/config/" + StringUtils.capitalize(moduleConfig.getModuleName()) + "Config.java";}});cfg.setFileOutConfigList(focList);mpg.setCfg(cfg);// 策略配置StrategyConfig strategy = new StrategyConfig();strategy.setNaming(NamingStrategy.underline_to_camel).setColumnNaming(NamingStrategy.underline_to_camel).setEntityLombokModel(true).setRestControllerStyle(true).setInclude(moduleConfig.getTables().toArray(new String[0])).setTablePrefix("sys_").setControllerMappingHyphenStyle(true);mpg.setStrategy(strategy);// 模板引擎mpg.setTemplateEngine(new VelocityTemplateEngine());mpg.execute();System.out.println("模块 [" + moduleConfig.getModuleName() + "] 代码生成完成");}/*** 模块配置*/@Data@AllArgsConstructorpublic static class ModuleConfig {private String moduleName;private String comment;private List<String> tables;}
}
模板定制
简要描述:MybatisPlus代码生成器支持自定义模板,可以根据项目需求定制生成的代码格式和内容。
核心概念:
- 模板引擎:支持Velocity、Freemarker、Beetl等模板引擎
- 模板变量:内置丰富的模板变量
- 自定义模板:可以完全自定义代码模板
- 模板继承:支持模板继承和复用
- 条件渲染:支持条件判断和循环渲染
Velocity模板示例
Entity模板定制
##(set $tableName = $table.name)
##(set $entityName = $table.entityName)
##(set $entityPath = $table.entityPath)
package ${package.Entity};#foreach($pkg in ${table.importPackages})
import ${pkg};
#end
#if(${swagger2})
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
#end
#if(${entityLombokModel})
import lombok.Data;
import lombok.EqualsAndHashCode;
#if(${chainModel})
import lombok.experimental.Accessors;
#end
#end/*** <p>* $!{table.comment}* </p>** @author ${author}* @since ${date}*/
#if(${entityLombokModel})
@Data#if(${superEntityClass})
@EqualsAndHashCode(callSuper = true)#else
@EqualsAndHashCode(callSuper = false)#end#if(${chainModel})
@Accessors(chain = true)#end
#end
#if(${table.convert})
@TableName("${table.name}")
#end
#if(${swagger2})
@ApiModel(value="${entity}对象", description="$!{table.comment}")
#end
#if(${superEntityClass})
public class ${entity} extends ${superEntityClass}#if(${activeRecord})<${entity}>#end {
#elseif(${activeRecord})
public class ${entity} extends Model<${entity}> {
#else
public class ${entity} implements Serializable {
#end#if(${entitySerialVersionUID})private static final long serialVersionUID = 1L;
#end
## ---------- BEGIN 字段循环遍历 ----------
#foreach($field in ${table.fields})#if(${field.keyFlag})
#set($keyPropertyName=${field.propertyName})
#end
#if("$!field.comment" != "")#if(${swagger2})@ApiModelProperty(value = "${field.comment}")#else/*** ${field.comment}*/#end
#end
#if(${field.keyFlag})
## 主键#if(${field.keyIdentityFlag})@TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)#elseif(!$null.isNull(${idType}) && "$!idType" != "")@TableId(value = "${field.annotationColumnName}", type = IdType.${idType})#elseif(${field.convert})@TableId("${field.annotationColumnName}")#end
## 普通字段
#elseif(${field.fill})
## ----- 存在字段填充设置 -----#if(${field.convert})@TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})#else@TableField(fill = FieldFill.${field.fill})#end
#elseif(${field.convert})@TableField("${field.annotationColumnName}")
#end
## 乐观锁注解
#if(${versionFieldName}==${field.name})@Version
#end
## 逻辑删除注解
#if(${logicDeleteFieldName}==${field.name})@TableLogic
#endprivate ${field.propertyType} ${field.propertyName};
#end
## ---------- END 字段循环遍历 ----------#if(!${entityLombokModel})
#foreach($field in ${table.fields})#if(${field.propertyType.equals("boolean")})#set($getprefix="is")#else#set($getprefix="get")#endpublic ${field.propertyType} ${getprefix}${field.capitalName}() {return ${field.propertyName};}#if(${chainModel})public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {#elsepublic void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {#endthis.${field.propertyName} = ${field.propertyName};#if(${chainModel})return this;#end}
#end
#end#if(${entityColumnConstant})#foreach($field in ${table.fields})public static final String ${field.name.toUpperCase()} = "${field.name}";#end
#end
#if(${activeRecord})@Overrideprotected Serializable pkVal() {#if(${keyPropertyName})return this.${keyPropertyName};#elsereturn null;#end}
#end
#if(!${entityLombokModel})@Overridepublic String toString() {return "${entity}{" +#foreach($field in ${table.fields})#if($!{foreach.index}==0)"${field.propertyName}=" + ${field.propertyName} +#else", ${field.propertyName}=" + ${field.propertyName} +#end#end"}";}
#end
}
Mapper模板定制
package ${package.Mapper};import ${package.Entity}.${entity};
import ${superMapperClassPackage};
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;/*** <p>* $!{table.comment} Mapper 接口* </p>** @author ${author}* @since ${date}*/
#if(${kotlin})
interface ${table.mapperName} : ${superMapperClass}<${entity}>
#else
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {/*** 自定义分页查询* @param page 分页参数* @param queryParam 查询参数* @return 分页结果*/IPage<${entity}> selectCustomPage(IPage<${entity}> page, @Param("query") ${entity}QueryDTO queryParam);/*** 根据条件统计数量* @param queryParam 查询参数* @return 统计结果*/Long countByCondition(@Param("query") ${entity}QueryDTO queryParam);/*** 批量插入* @param entityList 实体列表* @return 影响行数*/int insertBatch(@Param("list") List<${entity}> entityList);/*** 根据条件查询列表* @param queryParam 查询参数* @return 结果列表*/List<${entity}> selectByCondition(@Param("query") ${entity}QueryDTO queryParam);/*** 根据ID列表查询* @param ids ID列表* @return 结果列表*/List<${entity}> selectByIds(@Param("ids") List<Long> ids);/*** 软删除* @param id 主键ID* @param updateBy 更新人* @return 影响行数*/int softDeleteById(@Param("id") Long id, @Param("updateBy") String updateBy);/*** 批量软删除* @param ids ID列表* @param updateBy 更新人* @return 影响行数*/int softDeleteByIds(@Param("ids") List<Long> ids, @Param("updateBy") String updateBy);
}
#end
Service模板定制
package ${package.Service};import ${package.Entity}.${entity};
import ${superServiceClassPackage};
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;/*** <p>* $!{table.comment} 服务类* </p>** @author ${author}* @since ${date}*/
#if(${kotlin})
interface ${table.serviceName} : ${superServiceClass}<${entity}>
#else
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {/*** 分页查询* @param page 分页参数* @param queryDTO 查询条件* @return 分页结果*/IPage<${entity}VO> selectPage(Page<${entity}> page, ${entity}QueryDTO queryDTO);/*** 根据条件查询列表* @param queryDTO 查询条件* @return 结果列表*/List<${entity}VO> selectList(${entity}QueryDTO queryDTO);/*** 根据ID查询详情* @param id 主键ID* @return 详情信息*/${entity}VO selectById(Long id);/*** 新增* @param createDTO 创建DTO* @return 是否成功*/boolean create(${entity}CreateDTO createDTO);/*** 修改* @param updateDTO 更新DTO* @return 是否成功*/boolean update(${entity}UpdateDTO updateDTO);/*** 删除* @param id 主键ID* @return 是否成功*/boolean deleteById(Long id);/*** 批量删除* @param ids ID列表* @return 是否成功*/boolean deleteByIds(List<Long> ids);/*** 导出数据* @param queryDTO 查询条件* @return 导出数据*/List<${entity}ExportVO> exportData(${entity}QueryDTO queryDTO);/*** 导入数据* @param importList 导入数据列表* @return 导入结果*/ImportResult<${entity}ImportDTO> importData(List<${entity}ImportDTO> importList);
}
#end
Controller模板定制
package ${package.Controller};import org.springframework.web.bind.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import ${package.Service}.${table.serviceName};
import ${package.Entity}.${entity};
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.*;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;/*** <p>* $!{table.comment} 前端控制器* </p>** @author ${author}* @since ${date}*/
#if(${restControllerStyle})
@RestController
#else
@Controller
#end
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
@Api(tags = "$!{table.comment}管理")
@Validated
#if(${kotlin})
class ${table.controllerName}#if(${superControllerClass}) : ${superControllerClass}()#end
#else#if(${superControllerClass})
public class ${table.controllerName} extends ${superControllerClass} {#else
public class ${table.controllerName} {#end@Autowiredprivate ${table.serviceName} ${table.entityPath}Service;/*** 分页查询*/@GetMapping("/page")@ApiOperation(value = "分页查询$!{table.comment}", notes = "分页查询$!{table.comment}列表")@ApiImplicitParams({@ApiImplicitParam(name = "current", value = "当前页", defaultValue = "1", dataType = "int", paramType = "query"),@ApiImplicitParam(name = "size", value = "每页显示条数", defaultValue = "10", dataType = "int", paramType = "query")})public Result<IPage<${entity}VO>> page(@RequestParam(value = "current", defaultValue = "1") Integer current,@RequestParam(value = "size", defaultValue = "10") Integer size,${entity}QueryDTO queryDTO) {Page<${entity}> page = new Page<>(current, size);IPage<${entity}VO> result = ${table.entityPath}Service.selectPage(page, queryDTO);return Result.success(result);}/*** 查询列表*/@GetMapping("/list")@ApiOperation(value = "查询$!{table.comment}列表", notes = "根据条件查询$!{table.comment}列表")public Result<List<${entity}VO>> list(${entity}QueryDTO queryDTO) {List<${entity}VO> result = ${table.entityPath}Service.selectList(queryDTO);return Result.success(result);}/*** 根据ID查询详情*/@GetMapping("/{id}")@ApiOperation(value = "查询$!{table.comment}详情", notes = "根据ID查询$!{table.comment}详情")@ApiImplicitParam(name = "id", value = "主键ID", required = true, dataType = "long", paramType = "path")public Result<${entity}VO> getById(@PathVariable @NotNull(message = "ID不能为空") Long id) {${entity}VO result = ${table.entityPath}Service.selectById(id);return Result.success(result);}/*** 新增*/@PostMapping@ApiOperation(value = "新增$!{table.comment}", notes = "新增$!{table.comment}")public Result<Boolean> create(@RequestBody @Valid ${entity}CreateDTO createDTO) {boolean result = ${table.entityPath}Service.create(createDTO);return Result.success(result);}/*** 修改*/@PutMapping@ApiOperation(value = "修改$!{table.comment}", notes = "修改$!{table.comment}")public Result<Boolean> update(@RequestBody @Valid ${entity}UpdateDTO updateDTO) {boolean result = ${table.entityPath}Service.update(updateDTO);return Result.success(result);}/*** 删除*/@DeleteMapping("/{id}")@ApiOperation(value = "删除$!{table.comment}", notes = "根据ID删除$!{table.comment}")@ApiImplicitParam(name = "id", value = "主键ID", required = true, dataType = "long", paramType = "path")public Result<Boolean> deleteById(@PathVariable @NotNull(message = "ID不能为空") Long id) {boolean result = ${table.entityPath}Service.deleteById(id);return Result.success(result);}/*** 批量删除*/@DeleteMapping("/batch")@ApiOperation(value = "批量删除$!{table.comment}", notes = "根据ID列表批量删除$!{table.comment}")public Result<Boolean> deleteByIds(@RequestBody @Valid List<@NotNull(message = "ID不能为空") Long> ids) {boolean result = ${table.entityPath}Service.deleteByIds(ids);return Result.success(result);}/*** 导出*/@GetMapping("/export")@ApiOperation(value = "导出$!{table.comment}", notes = "导出$!{table.comment}数据")public void export(HttpServletResponse response, ${entity}QueryDTO queryDTO) {List<${entity}ExportVO> data = ${table.entityPath}Service.exportData(queryDTO);ExcelUtils.export(response, "$!{table.comment}数据", ${entity}ExportVO.class, data);}/*** 导入*/@PostMapping("/import")@ApiOperation(value = "导入$!{table.comment}", notes = "导入$!{table.comment}数据")public Result<ImportResult<${entity}ImportDTO>> importData(@RequestParam("file") MultipartFile file) {List<${entity}ImportDTO> importList = ExcelUtils.read(file, ${entity}ImportDTO.class);ImportResult<${entity}ImportDTO> result = ${table.entityPath}Service.importData(importList);return Result.success(result);}
}
#end
生成策略配置
简要描述:生成策略配置是代码生成器的核心配置,控制着代码生成的各种策略和规则。
核心概念:
- 命名策略:数据库表名到实体类名的转换规则
- 字段策略:数据库字段到实体属性的转换规则
- 包含排除策略:控制哪些表参与代码生成
- 填充策略:自动填充字段的配置
- 继承策略:实体类继承关系的配置
基础策略配置
/*** 基础策略配置*/
public class BasicStrategyConfig {/*** 构建基础策略配置*/public StrategyConfig buildBasicStrategy() {StrategyConfig strategy = new StrategyConfig();// 数据库表配置strategy.setNaming(NamingStrategy.underline_to_camel) // 数据库表映射到实体的命名策略.setColumnNaming(NamingStrategy.underline_to_camel) // 数据库表字段映射到实体的命名策略.setInclude("sys_user", "sys_role", "sys_permission") // 需要包含的表名.setExclude("temp_table", "log_table") // 需要排除的表名.setTablePrefix("sys_", "t_") // 表前缀.setFieldPrefix("is_", "has_"); // 字段前缀// 实体类配置strategy.setEntityLombokModel(true) // 是否为lombok模型.setEntityTableFieldAnnotationEnable(true) // 是否生成实体时,生成字段注解.setEntityColumnConstant(true) // 是否生成字段常量.setEntityBuilderModel(false) // 是否为构建者模型.setEntitySerialVersionUID(true) // 是否生成serialVersionUID.setChainModel(false) // 是否为链式模型.setEntityBooleanColumnRemoveIsPrefix(true); // Boolean类型字段是否移除is前缀// Controller配置strategy.setRestControllerStyle(true) // 生成 @RestController 控制器.setControllerMappingHyphenStyle(true); // 驼峰转连字符return strategy;}
}
高级策略配置
/*** 高级策略配置*/
public class AdvancedStrategyConfig {/*** 构建高级策略配置*/public StrategyConfig buildAdvancedStrategy() {StrategyConfig strategy = new StrategyConfig();// 数据库表配置strategy.setNaming(NamingStrategy.underline_to_camel).setColumnNaming(NamingStrategy.underline_to_camel).setInclude(getIncludeTables()) // 动态获取表名.setTablePrefix("sys_", "t_", "tb_").setFieldPrefix("is_", "has_", "can_");// 实体类配置strategy.setEntityLombokModel(true).setEntityTableFieldAnnotationEnable(true).setEntityColumnConstant(true).setEntityBuilderModel(false).setEntitySerialVersionUID(true).setChainModel(false).setEntityBooleanColumnRemoveIsPrefix(true);// 填充字段配置List<TableFill> tableFillList = buildTableFillList();strategy.setTableFillList(tableFillList);// 乐观锁字段配置strategy.setVersionFieldName("version");// 逻辑删除字段配置strategy.setLogicDeleteFieldName("deleted");// 父类实体配置strategy.setSuperEntityClass("com.example.common.entity.BaseEntity").setSuperEntityColumns("id", "create_time", "update_time", "create_by", "update_by", "deleted");// 父类Mapper配置strategy.setSuperMapperClass("com.example.common.mapper.BaseMapper");// 父类Service配置strategy.setSuperServiceClass("com.example.common.service.IBaseService").setSuperServiceImplClass("com.example.common.service.impl.BaseServiceImpl");// 父类Controller配置strategy.setSuperControllerClass("com.example.common.controller.BaseController");// Controller配置strategy.setRestControllerStyle(true).setControllerMappingHyphenStyle(true);return strategy;}/*** 动态获取需要生成的表名*/private String[] getIncludeTables() {// 可以从配置文件、数据库或其他地方动态获取List<String> tables = new ArrayList<>();tables.add("sys_user");tables.add("sys_role");tables.add("sys_permission");tables.add("sys_menu");tables.add("sys_dept");return tables.toArray(new String[0]);}/*** 构建填充字段列表*/private List<TableFill> buildTableFillList() {List<TableFill> tableFillList = new ArrayList<>();// 创建时间字段自动填充tableFillList.add(new TableFill("create_time", FieldFill.INSERT));tableFillList.add(new TableFill("created_time", FieldFill.INSERT));tableFillList.add(new TableFill("gmt_create", FieldFill.INSERT));// 更新时间字段自动填充tableFillList.add(new TableFill("update_time", FieldFill.INSERT_UPDATE));tableFillList.add(new TableFill("updated_time", FieldFill.INSERT_UPDATE));tableFillList.add(new TableFill("gmt_modified", FieldFill.INSERT_UPDATE));// 创建人字段自动填充tableFillList.add(new TableFill("create_by", FieldFill.INSERT));tableFillList.add(new TableFill("created_by", FieldFill.INSERT));tableFillList.add(new TableFill("creator", FieldFill.INSERT));// 更新人字段自动填充tableFillList.add(new TableFill("update_by", FieldFill.INSERT_UPDATE));tableFillList.add(new TableFill("updated_by", FieldFill.INSERT_UPDATE));tableFillList.add(new TableFill("modifier", FieldFill.INSERT_UPDATE));return tableFillList;}
}
条件策略配置
/*** 条件策略配置*/
public class ConditionalStrategyConfig {/*** 根据条件构建策略配置*/public StrategyConfig buildConditionalStrategy(GeneratorConfig config) {StrategyConfig strategy = new StrategyConfig();// 基础配置strategy.setNaming(config.getNamingStrategy()).setColumnNaming(config.getColumnNamingStrategy());// 根据环境配置表前缀if (config.getEnvironment().equals("dev")) {strategy.setTablePrefix("dev_", "test_");} else if (config.getEnvironment().equals("prod")) {strategy.setTablePrefix("prod_");}// 根据项目类型配置实体if (config.getProjectType().equals("microservice")) {// 微服务项目配置strategy.setEntityLombokModel(true).setEntityTableFieldAnnotationEnable(true).setEntityColumnConstant(false).setChainModel(true);} else {// 单体项目配置strategy.setEntityLombokModel(true).setEntityTableFieldAnnotationEnable(false).setEntityColumnConstant(true).setChainModel(false);}// 根据是否使用Swagger配置注解if (config.isUseSwagger()) {// 在GlobalConfig中设置swagger2为true}// 根据是否使用父类配置继承if (config.isUseSuperClass()) {strategy.setSuperEntityClass(config.getSuperEntityClass()).setSuperEntityColumns(config.getSuperEntityColumns()).setSuperMapperClass(config.getSuperMapperClass()).setSuperServiceClass(config.getSuperServiceClass()).setSuperServiceImplClass(config.getSuperServiceImplClass()).setSuperControllerClass(config.getSuperControllerClass());}// 根据表名模式配置包含表if (config.getTablePattern() != null) {strategy.setInclude(getTablesByPattern(config.getTablePattern()));} else {strategy.setInclude(config.getIncludeTables());}// 配置填充字段if (config.isUseAutoFill()) {strategy.setTableFillList(buildAutoFillList(config));}// 配置乐观锁if (config.isUseOptimisticLock()) {strategy.setVersionFieldName(config.getVersionFieldName());}// 配置逻辑删除if (config.isUseLogicDelete()) {strategy.setLogicDeleteFieldName(config.getLogicDeleteFieldName());}// Controller配置strategy.setRestControllerStyle(config.isRestController()).setControllerMappingHyphenStyle(config.isHyphenStyle());return strategy;}/*** 根据模式获取表名*/private String[] getTablesByPattern(String pattern) {// 这里可以实现根据正则表达式或其他模式匹配表名的逻辑// 例如:从数据库查询符合模式的表名List<String> tables = new ArrayList<>();if (pattern.equals("sys_*")) {tables.add("sys_user");tables.add("sys_role");tables.add("sys_permission");} else if (pattern.equals("biz_*")) {tables.add("biz_order");tables.add("biz_product");tables.add("biz_customer");}return tables.toArray(new String[0]);}/*** 构建自动填充列表*/private List<TableFill> buildAutoFillList(GeneratorConfig config) {List<TableFill> tableFillList = new ArrayList<>();// 根据配置添加自动填充字段if (config.getAutoFillFields() != null) {for (AutoFillField field : config.getAutoFillFields()) {tableFillList.add(new TableFill(field.getFieldName(), field.getFieldFill()));}}return tableFillList;}/*** 生成器配置类*/@Datapublic static class GeneratorConfig {private String environment; // 环境:dev, test, prodprivate String projectType; // 项目类型:microservice, monolithprivate NamingStrategy namingStrategy = NamingStrategy.underline_to_camel;private NamingStrategy columnNamingStrategy = NamingStrategy.underline_to_camel;private boolean useSwagger = true;private boolean useSuperClass = true;private boolean useAutoFill = true;private boolean useOptimisticLock = false;private boolean useLogicDelete = true;private boolean restController = true;private boolean hyphenStyle = true;// 父类配置private String superEntityClass;private String[] superEntityColumns;private String superMapperClass;private String superServiceClass;private String superServiceImplClass;private String superControllerClass;// 表配置private String tablePattern;private String[] includeTables;// 字段配置private String versionFieldName = "version";private String logicDeleteFieldName = "deleted";private List<AutoFillField> autoFillFields;}/*** 自动填充字段配置*/@Data@AllArgsConstructorpublic static class AutoFillField {private String fieldName;private FieldFill fieldFill;}
}
策略配置工厂
/*** 策略配置工厂*/
@Component
public class StrategyConfigFactory {/*** 创建默认策略配置*/public StrategyConfig createDefaultStrategy() {return new BasicStrategyConfig().buildBasicStrategy();}/*** 创建微服务策略配置*/public StrategyConfig createMicroserviceStrategy() {StrategyConfig strategy = new StrategyConfig();strategy.setNaming(NamingStrategy.underline_to_camel).setColumnNaming(NamingStrategy.underline_to_camel).setEntityLombokModel(true).setEntityTableFieldAnnotationEnable(true).setChainModel(true).setRestControllerStyle(true).setControllerMappingHyphenStyle(true);// 微服务通用父类strategy.setSuperEntityClass("com.example.common.entity.BaseEntity").setSuperEntityColumns("id", "create_time", "update_time", "deleted").setSuperMapperClass("com.example.common.mapper.BaseMapper").setSuperServiceClass("com.example.common.service.IBaseService").setSuperServiceImplClass("com.example.common.service.impl.BaseServiceImpl").setSuperControllerClass("com.example.common.controller.BaseController");// 微服务通用填充字段List<TableFill> tableFillList = Arrays.asList(new TableFill("create_time", FieldFill.INSERT),new TableFill("update_time", FieldFill.INSERT_UPDATE),new TableFill("create_by", FieldFill.INSERT),new TableFill("update_by", FieldFill.INSERT_UPDATE));strategy.setTableFillList(tableFillList);strategy.setLogicDeleteFieldName("deleted");return strategy;}/*** 创建DDD策略配置*/public StrategyConfig createDDDStrategy() {StrategyConfig strategy = new StrategyConfig();strategy.setNaming(NamingStrategy.underline_to_camel).setColumnNaming(NamingStrategy.underline_to_camel).setEntityLombokModel(true).setEntityTableFieldAnnotationEnable(true).setEntityBuilderModel(true) // DDD中使用Builder模式.setChainModel(false).setRestControllerStyle(true);// DDD分层架构父类strategy.setSuperEntityClass("com.example.domain.entity.AggregateRoot").setSuperEntityColumns("id", "version", "create_time", "update_time").setSuperMapperClass("com.example.infrastructure.mapper.BaseMapper").setSuperServiceClass("com.example.domain.service.DomainService").setSuperControllerClass("com.example.interfaces.controller.BaseController");// DDD通用字段List<TableFill> tableFillList = Arrays.asList(new TableFill("create_time", FieldFill.INSERT),new TableFill("update_time", FieldFill.INSERT_UPDATE));strategy.setTableFillList(tableFillList);strategy.setVersionFieldName("version"); // DDD中使用乐观锁return strategy;}/*** 创建CQRS策略配置*/public StrategyConfig createCQRSStrategy() {StrategyConfig strategy = new StrategyConfig();strategy.setNaming(NamingStrategy.underline_to_camel).setColumnNaming(NamingStrategy.underline_to_camel).setEntityLombokModel(true).setEntityTableFieldAnnotationEnable(true).setEntityColumnConstant(true).setChainModel(false).setRestControllerStyle(true);// CQRS读写分离父类strategy.setSuperEntityClass("com.example.common.entity.BaseEntity").setSuperEntityColumns("id", "version", "create_time", "update_time").setSuperMapperClass("com.example.common.mapper.BaseMapper");// 不生成默认Service,CQRS有专门的Command和Query处理器strategy.setSuperServiceClass(null).setSuperServiceImplClass(null);strategy.setSuperControllerClass("com.example.common.controller.BaseController");List<TableFill> tableFillList = Arrays.asList(new TableFill("create_time", FieldFill.INSERT),new TableFill("update_time", FieldFill.INSERT_UPDATE));strategy.setTableFillList(tableFillList);strategy.setVersionFieldName("version");return strategy;}/*** 根据架构类型创建策略配置*/public StrategyConfig createByArchitecture(String architecture) {switch (architecture.toLowerCase()) {case "microservice":return createMicroserviceStrategy();case "ddd":return createDDDStrategy();case "cqrs":return createCQRSStrategy();default:return createDefaultStrategy();}}
}
自定义生成模板
简要描述:自定义生成模板允许开发者根据项目需求定制代码生成的输出格式和内容,支持多种模板引擎。
核心概念:
- 模板引擎:支持Velocity、Freemarker、Beetl等模板引擎
- 模板变量:代码生成过程中可用的变量和对象
- 模板路径:自定义模板文件的存放位置
- 模板覆盖:用自定义模板覆盖默认模板
- 条件渲染:根据配置条件渲染不同的代码内容
Velocity模板引擎
/*** Velocity模板配置*/
public class VelocityTemplateConfig {/*** 配置Velocity模板引擎*/public TemplateConfig buildVelocityTemplate() {TemplateConfig templateConfig = new TemplateConfig();// 设置模板引擎为VelocitytemplateConfig.setEntity("/templates/entity.java.vm").setMapper("/templates/mapper.java.vm").setMapperXml("/templates/mapper.xml.vm").setService("/templates/service.java.vm").setServiceImpl("/templates/serviceImpl.java.vm").setController("/templates/controller.java.vm");return templateConfig;}
}
Entity模板示例 (entity.java.vm)
package ${package.Entity};#foreach($pkg in ${table.importPackages})
import ${pkg};
#end
#if(${swagger2})
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
#end
#if(${entityLombokModel})
import lombok.Data;
import lombok.EqualsAndHashCode;
#if(${chainModel})
import lombok.experimental.Accessors;
#end
#end/*** <p>* $!{table.comment}* </p>** @author ${author}* @since ${date}*/
#if(${entityLombokModel})
@Data#if(${superEntityClass})
@EqualsAndHashCode(callSuper = true)#else
@EqualsAndHashCode(callSuper = false)#end#if(${chainModel})
@Accessors(chain = true)#end
#end
#if(${table.convert})
@TableName("${table.name}")
#end
#if(${swagger2})
@ApiModel(value="${entity}对象", description="$!{table.comment}")
#end
#if(${superEntityClass})
public class ${entity} extends ${superEntityClass}#if(${activeRecord})<${entity}>#end {
#elseif(${activeRecord})
public class ${entity} extends Model<${entity}> {
#else
public class ${entity} implements Serializable {
#end#if(${entitySerialVersionUID})private static final long serialVersionUID = 1L;
#end
## ---------- BEGIN 字段循环遍历 ----------
#foreach($field in ${table.fields})#if(${field.keyFlag})
#set($keyPropertyName=${field.propertyName})
#end
#if("$!field.comment" != "")#if(${swagger2})@ApiModelProperty(value = "${field.comment}")#else/*** ${field.comment}*/#end
#end
#if(${field.keyFlag})
## 主键#if(${field.keyIdentityFlag})@TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)#elseif(!$null.isNull(${idType}) && "$!idType" != "")@TableId(value = "${field.annotationColumnName}", type = IdType.${idType})#elseif(${field.convert})@TableId("${field.annotationColumnName}")#end
## 普通字段
#elseif(${field.fill})
## ----- 存在字段填充设置 -----#if(${field.convert})@TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})#else@TableField(fill = FieldFill.${field.fill})#end
#elseif(${field.convert})@TableField("${field.annotationColumnName}")
#end
## 乐观锁注解
#if(${versionFieldName}==${field.name})@Version
#end
## 逻辑删除注解
#if(${logicDeleteFieldName}==${field.name})@TableLogic
#endprivate ${field.propertyType} ${field.propertyName};
#end
## ---------- END 字段循环遍历 ----------#if(!${entityLombokModel})
#foreach($field in ${table.fields})#if(${field.propertyType.equals("boolean")})#set($getprefix="is")#else#set($getprefix="get")#endpublic ${field.propertyType} ${getprefix}${field.capitalName}() {return ${field.propertyName};}#if(${chainModel})public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {#elsepublic void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {#endthis.${field.propertyName} = ${field.propertyName};#if(${chainModel})return this;#end}
#end
#end#if(${entityColumnConstant})#foreach($field in ${table.fields})public static final String ${field.name.toUpperCase()} = "${field.name}";#end
#end
#if(${activeRecord})@Overrideprotected Serializable pkVal() {#if(${keyPropertyName})return this.${keyPropertyName};#elsereturn null;#end}
#end
#if(!${entityLombokModel})@Overridepublic String toString() {return "${entity}{" +#foreach($field in ${table.fields})#if($!{foreach.index}==0)"${field.propertyName}=" + ${field.propertyName} +#else", ${field.propertyName}=" + ${field.propertyName} +#end#end"}";}
#end
}
Mapper模板示例 (mapper.java.vm)
package ${package.Mapper};import ${package.Entity}.${entity};
import ${superMapperClassPackage};
#if(${mapperAnnotation})
import org.apache.ibatis.annotations.Mapper;
#end/*** <p>* $!{table.comment} Mapper 接口* </p>** @author ${author}* @since ${date}*/
#if(${mapperAnnotation})
@Mapper
#end
#if(${kotlin})
interface ${table.mapperName} : ${superMapperClass}<${entity}>
#else
public interface ${table.mapperName} extends ${superMapperClass}<${entity}> {/*** 根据用户名查询用户* @param username 用户名* @return 用户信息*/${entity} selectByUsername(String username);/*** 根据状态查询用户列表* @param status 状态* @return 用户列表*/List<${entity}> selectByStatus(Integer status);/*** 批量更新状态* @param ids ID列表* @param status 状态* @return 更新数量*/int updateStatusByIds(@Param("ids") List<Long> ids, @Param("status") Integer status);/*** 统计各状态用户数量* @return 统计结果*/List<Map<String, Object>> countByStatus();
}
#end
Service模板示例 (service.java.vm)
package ${package.Service};import ${package.Entity}.${entity};
import ${superServiceClassPackage};
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.Map;/*** <p>* $!{table.comment} 服务类* </p>** @author ${author}* @since ${date}*/
#if(${kotlin})
interface ${table.serviceName} : ${superServiceClass}<${entity}>
#else
public interface ${table.serviceName} extends ${superServiceClass}<${entity}> {/*** 根据用户名查询用户* @param username 用户名* @return 用户信息*/${entity} getByUsername(String username);/*** 分页查询用户* @param page 分页参数* @param queryParam 查询参数* @return 分页结果*/Page<${entity}> pageQuery(Page<${entity}> page, ${entity}QueryParam queryParam);/*** 批量更新状态* @param ids ID列表* @param status 状态* @return 是否成功*/boolean updateStatusBatch(List<Long> ids, Integer status);/*** 导出数据* @param queryParam 查询参数* @return 导出数据*/List<${entity}ExportVO> exportData(${entity}QueryParam queryParam);/*** 统计数据* @return 统计结果*/Map<String, Object> getStatistics();
}
#end
Controller模板示例 (controller.java.vm)
package ${package.Controller};import org.springframework.web.bind.annotation.*;
#if(${restControllerStyle})
import org.springframework.web.bind.annotation.RestController;
#else
import org.springframework.stereotype.Controller;
#end
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end
import ${package.Service}.${table.serviceName};
import ${package.Entity}.${entity};
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List;
import java.util.Map;
#if(${swagger2})
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
#end/*** <p>* $!{table.comment} 前端控制器* </p>** @author ${author}* @since ${date}*/
#if(${restControllerStyle})
@RestController
#else
@Controller
#end
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
#if(${swagger2})
@Api(value = "$!{table.comment}管理", tags = "$!{table.comment}管理")
#end
@Validated
#if(${kotlin})
class ${table.controllerName}#if(${superControllerClass}) : ${superControllerClass}()#end
#else#if(${superControllerClass})
public class ${table.controllerName} extends ${superControllerClass} {#else
public class ${table.controllerName} {#end@Autowiredprivate ${table.serviceName} ${table.entityPath}Service;/*** 分页查询*/@GetMapping("/page")#if(${swagger2})@ApiOperation(value = "分页查询$!{table.comment}")#endpublic Result<Page<${entity}>> page(@RequestParam(defaultValue = "1") Integer current,@RequestParam(defaultValue = "10") Integer size,${entity}QueryParam queryParam) {Page<${entity}> page = new Page<>(current, size);Page<${entity}> result = ${table.entityPath}Service.pageQuery(page, queryParam);return Result.success(result);}/*** 根据ID查询*/@GetMapping("/{id}")#if(${swagger2})@ApiOperation(value = "根据ID查询$!{table.comment}")#endpublic Result<${entity}> getById(#if(${swagger2})@ApiParam(value = "主键ID", required = true)#end@PathVariable Long id) {${entity} entity = ${table.entityPath}Service.getById(id);return Result.success(entity);}/*** 新增*/@PostMapping#if(${swagger2})@ApiOperation(value = "新增$!{table.comment}")#endpublic Result<Boolean> save(#if(${swagger2})@ApiParam(value = "$!{table.comment}对象", required = true)#end@Valid @RequestBody ${entity}SaveDTO saveDTO) {${entity} entity = BeanUtil.copyProperties(saveDTO, ${entity}.class);boolean result = ${table.entityPath}Service.save(entity);return Result.success(result);}/*** 修改*/@PutMapping("/{id}")#if(${swagger2})@ApiOperation(value = "修改$!{table.comment}")#endpublic Result<Boolean> updateById(#if(${swagger2})@ApiParam(value = "主键ID", required = true)#end@PathVariable Long id,#if(${swagger2})@ApiParam(value = "$!{table.comment}对象", required = true)#end@Valid @RequestBody ${entity}UpdateDTO updateDTO) {${entity} entity = BeanUtil.copyProperties(updateDTO, ${entity}.class);entity.setId(id);boolean result = ${table.entityPath}Service.updateById(entity);return Result.success(result);}/*** 删除*/@DeleteMapping("/{id}")#if(${swagger2})@ApiOperation(value = "删除$!{table.comment}")#endpublic Result<Boolean> removeById(#if(${swagger2})@ApiParam(value = "主键ID", required = true)#end@PathVariable Long id) {boolean result = ${table.entityPath}Service.removeById(id);return Result.success(result);}/*** 批量删除*/@DeleteMapping("/batch")#if(${swagger2})@ApiOperation(value = "批量删除$!{table.comment}")#endpublic Result<Boolean> removeByIds(#if(${swagger2})@ApiParam(value = "主键ID列表", required = true)#end@NotEmpty @RequestBody List<Long> ids) {boolean result = ${table.entityPath}Service.removeByIds(ids);return Result.success(result);}/*** 批量更新状态*/@PutMapping("/status")#if(${swagger2})@ApiOperation(value = "批量更新状态")#endpublic Result<Boolean> updateStatus(#if(${swagger2})@ApiParam(value = "状态更新参数", required = true)#end@Valid @RequestBody ${entity}StatusUpdateDTO statusUpdateDTO) {boolean result = ${table.entityPath}Service.updateStatusBatch(statusUpdateDTO.getIds(), statusUpdateDTO.getStatus());return Result.success(result);}/*** 导出数据*/@GetMapping("/export")#if(${swagger2})@ApiOperation(value = "导出$!{table.comment}数据")#endpublic void export(HttpServletResponse response, ${entity}QueryParam queryParam) {List<${entity}ExportVO> data = ${table.entityPath}Service.exportData(queryParam);ExcelUtil.export(response, "$!{table.comment}数据", ${entity}ExportVO.class, data);}/*** 获取统计数据*/@GetMapping("/statistics")#if(${swagger2})@ApiOperation(value = "获取统计数据")#endpublic Result<Map<String, Object>> getStatistics() {Map<String, Object> statistics = ${table.entityPath}Service.getStatistics();return Result.success(statistics);}
}
#end
代码生成最佳实践
简要描述:代码生成最佳实践是在实际项目中使用MybatisPlus代码生成器的经验总结和规范指导。
核心概念:
- 规范化配置:统一的代码生成配置标准
- 模板标准化:可复用的模板设计
- 分层架构:符合项目架构的代码生成
- 扩展性设计:便于后续维护和扩展
- 自动化集成:与CI/CD流程的集成
项目结构最佳实践
/*** 项目结构配置*/
public class ProjectStructureBestPractice {/*** 标准项目结构配置*/public void configureStandardStructure() {// 推荐的项目结构/*src/main/java/├── com.example.project/│ ├── common/ // 公共模块│ │ ├── entity/ // 基础实体类│ │ ├── mapper/ // 基础Mapper│ │ ├── service/ // 基础Service│ │ ├── controller/ // 基础Controller│ │ ├── config/ // 配置类│ │ ├── util/ // 工具类│ │ └── exception/ // 异常类│ ├── module1/ // 业务模块1│ │ ├── entity/│ │ ├── mapper/│ │ ├── service/│ │ │ └── impl/│ │ ├── controller/│ │ ├── dto/ // 数据传输对象│ │ ├── vo/ // 视图对象│ │ └── convert/ // 对象转换器│ └── module2/ // 业务模块2│ └── ...src/main/resources/├── mapper/ // MyBatis XML文件│ ├── module1/│ └── module2/├── templates/ // 代码生成模板│ ├── entity.java.vm│ ├── mapper.java.vm│ ├── mapper.xml.vm│ ├── service.java.vm│ ├── serviceImpl.java.vm│ └── controller.java.vm└── application.yml*/}/*** 微服务项目结构配置*/public void configureMicroserviceStructure() {// 微服务推荐结构/*user-service/├── user-api/ // API接口模块│ └── src/main/java/│ └── com.example.user.api/│ ├── entity/ // 实体类│ ├── dto/ // 传输对象│ ├── vo/ // 视图对象│ └── feign/ // Feign接口├── user-service/ // 服务实现模块│ └── src/main/java/│ └── com.example.user.service/│ ├── mapper/│ ├── service/│ ├── controller/│ └── config/└── user-common/ // 公共模块└── src/main/java/└── com.example.user.common/├── base/├── util/└── exception/*/}
}
配置管理最佳实践
/*** 配置管理最佳实践*/
@Configuration
public class GeneratorConfigBestPractice {/*** 环境配置管理*/@Bean@ConfigurationProperties(prefix = "generator")public GeneratorProperties generatorProperties() {return new GeneratorProperties();}/*** 代码生成器配置*/public AutoGenerator buildGenerator(GeneratorProperties properties) {AutoGenerator generator = new AutoGenerator();// 全局配置GlobalConfig globalConfig = buildGlobalConfig(properties);generator.setGlobalConfig(globalConfig);// 数据源配置DataSourceConfig dataSourceConfig = buildDataSourceConfig(properties);generator.setDataSource(dataSourceConfig);// 包配置PackageConfig packageConfig = buildPackageConfig(properties);generator.setPackageInfo(packageConfig);// 策略配置StrategyConfig strategyConfig = buildStrategyConfig(properties);generator.setStrategy(strategyConfig);// 模板配置TemplateConfig templateConfig = buildTemplateConfig(properties);generator.setTemplate(templateConfig);return generator;}/*** 构建全局配置*/private GlobalConfig buildGlobalConfig(GeneratorProperties properties) {GlobalConfig globalConfig = new GlobalConfig();globalConfig.setOutputDir(properties.getOutputDir()).setAuthor(properties.getAuthor()).setOpen(false).setFileOverride(properties.isFileOverride()).setActiveRecord(properties.isActiveRecord()).setEnableCache(false).setBaseResultMap(true).setBaseColumnList(true).setSwagger2(properties.isSwagger2());// 自定义文件命名if (properties.getEntityName() != null) {globalConfig.setEntityName(properties.getEntityName());}if (properties.getMapperName() != null) {globalConfig.setMapperName(properties.getMapperName());}if (properties.getServiceName() != null) {globalConfig.setServiceName(properties.getServiceName());}if (properties.getServiceImplName() != null) {globalConfig.setServiceImplName(properties.getServiceImplName());}if (properties.getControllerName() != null) {globalConfig.setControllerName(properties.getControllerName());}return globalConfig;}/*** 构建数据源配置*/private DataSourceConfig buildDataSourceConfig(GeneratorProperties properties) {DataSourceConfig dataSourceConfig = new DataSourceConfig();dataSourceConfig.setUrl(properties.getUrl()).setDriverName(properties.getDriverName()).setUsername(properties.getUsername()).setPassword(properties.getPassword()).setDbType(DbType.MYSQL);return dataSourceConfig;}/*** 构建包配置*/private PackageConfig buildPackageConfig(GeneratorProperties properties) {PackageConfig packageConfig = new PackageConfig();packageConfig.setParent(properties.getParentPackage()).setModuleName(properties.getModuleName()).setEntity("entity").setMapper("mapper").setService("service").setServiceImpl("service.impl").setController("controller");return packageConfig;}/*** 构建策略配置*/private StrategyConfig buildStrategyConfig(GeneratorProperties properties) {StrategyConfig strategyConfig = new StrategyConfig();strategyConfig.setNaming(NamingStrategy.underline_to_camel).setColumnNaming(NamingStrategy.underline_to_camel).setEntityLombokModel(true).setRestControllerStyle(true).setControllerMappingHyphenStyle(true);// 表配置if (properties.getIncludeTables() != null && properties.getIncludeTables().length > 0) {strategyConfig.setInclude(properties.getIncludeTables());}if (properties.getExcludeTables() != null && properties.getExcludeTables().length > 0) {strategyConfig.setExclude(properties.getExcludeTables());}if (properties.getTablePrefix() != null && properties.getTablePrefix().length > 0) {strategyConfig.setTablePrefix(properties.getTablePrefix());}// 父类配置if (properties.getSuperEntityClass() != null) {strategyConfig.setSuperEntityClass(properties.getSuperEntityClass()).setSuperEntityColumns(properties.getSuperEntityColumns());}if (properties.getSuperMapperClass() != null) {strategyConfig.setSuperMapperClass(properties.getSuperMapperClass());}if (properties.getSuperServiceClass() != null) {strategyConfig.setSuperServiceClass(properties.getSuperServiceClass());}if (properties.getSuperServiceImplClass() != null) {strategyConfig.setSuperServiceImplClass(properties.getSuperServiceImplClass());}if (properties.getSuperControllerClass() != null) {strategyConfig.setSuperControllerClass(properties.getSuperControllerClass());}// 填充字段配置if (properties.getTableFillList() != null && !properties.getTableFillList().isEmpty()) {List<TableFill> tableFillList = properties.getTableFillList().stream().map(fill -> new TableFill(fill.getFieldName(), fill.getFieldFill())).collect(Collectors.toList());strategyConfig.setTableFillList(tableFillList);}// 乐观锁和逻辑删除if (properties.getVersionFieldName() != null) {strategyConfig.setVersionFieldName(properties.getVersionFieldName());}if (properties.getLogicDeleteFieldName() != null) {strategyConfig.setLogicDeleteFieldName(properties.getLogicDeleteFieldName());}return strategyConfig;}/*** 构建模板配置*/private TemplateConfig buildTemplateConfig(GeneratorProperties properties) {TemplateConfig templateConfig = new TemplateConfig();// 自定义模板路径if (properties.getTemplatePath() != null) {String templatePath = properties.getTemplatePath();templateConfig.setEntity(templatePath + "/entity.java.vm").setMapper(templatePath + "/mapper.java.vm").setMapperXml(templatePath + "/mapper.xml.vm").setService(templatePath + "/service.java.vm").setServiceImpl(templatePath + "/serviceImpl.java.vm").setController(templatePath + "/controller.java.vm");}// 禁用某些模板if (!properties.isGenerateEntity()) {templateConfig.setEntity(null);}if (!properties.isGenerateMapper()) {templateConfig.setMapper(null);}if (!properties.isGenerateMapperXml()) {templateConfig.setMapperXml(null);}if (!properties.isGenerateService()) {templateConfig.setService(null);}if (!properties.isGenerateServiceImpl()) {templateConfig.setServiceImpl(null);}if (!properties.isGenerateController()) {templateConfig.setController(null);}return templateConfig;}
}/*** 生成器配置属性*/
@Data
@ConfigurationProperties(prefix = "generator")
public class GeneratorProperties {// 全局配置private String outputDir = System.getProperty("user.dir") + "/src/main/java";private String author = "generator";private boolean fileOverride = false;private boolean activeRecord = false;private boolean swagger2 = true;// 数据源配置private String url;private String driverName;private String username;private String password;// 包配置private String parentPackage;private String moduleName;// 表配置private String[] includeTables;private String[] excludeTables;private String[] tablePrefix;// 父类配置private String superEntityClass;private String[] superEntityColumns;private String superMapperClass;private String superServiceClass;private String superServiceImplClass;private String superControllerClass;// 填充字段配置private List<TableFillConfig> tableFillList;// 乐观锁和逻辑删除private String versionFieldName;private String logicDeleteFieldName;// 文件命名配置private String entityName;private String mapperName;private String serviceName;private String serviceImplName;private String controllerName;// 模板配置private String templatePath;private boolean generateEntity = true;private boolean generateMapper = true;private boolean generateMapperXml = true;private boolean generateService = true;private boolean generateServiceImpl = true;private boolean generateController = true;@Datapublic static class TableFillConfig {private String fieldName;private FieldFill fieldFill;}
}
模板设计最佳实践
/*** 模板设计最佳实践*/
public class TemplateBestPractice {/*** 模板设计原则*/public void templateDesignPrinciples() {/*1. 单一职责原则- 每个模板只负责生成一种类型的文件- 避免在一个模板中混合多种逻辑2. 可配置性原则- 通过变量控制代码生成的行为- 支持条件渲染,适应不同场景3. 可扩展性原则- 预留扩展点,便于后续功能增加- 支持自定义变量和函数4. 一致性原则- 统一的代码风格和命名规范- 一致的注释和文档格式5. 可维护性原则- 清晰的模板结构和逻辑- 充分的注释说明*/}/*** 模板变量最佳实践*/public void templateVariablesBestPractice() {/*常用模板变量:全局变量:- ${author} // 作者- ${date} // 日期- ${package.Entity} // 实体包名- ${package.Mapper} // Mapper包名- ${package.Service} // Service包名- ${package.Controller} // Controller包名表相关变量:- ${table.name} // 表名- ${table.comment} // 表注释- ${table.entityName} // 实体类名- ${table.entityPath} // 实体路径- ${table.mapperName} // Mapper名- ${table.serviceName} // Service名- ${table.controllerName} // Controller名- ${table.fields} // 字段列表- ${table.importPackages} // 导入包列表字段相关变量:- ${field.name} // 字段名- ${field.type} // 字段类型- ${field.propertyName} // 属性名- ${field.propertyType} // 属性类型- ${field.comment} // 字段注释- ${field.keyFlag} // 是否主键- ${field.keyIdentityFlag} // 是否自增主键- ${field.fill} // 填充策略配置相关变量:- ${swagger2} // 是否启用Swagger- ${entityLombokModel} // 是否使用Lombok- ${chainModel} // 是否链式模型- ${activeRecord} // 是否ActiveRecord- ${restControllerStyle} // 是否REST风格- ${superEntityClass} // 父类实体- ${superMapperClass} // 父类Mapper- ${superServiceClass} // 父类Service- ${superControllerClass} // 父类Controller*/}/*** 条件渲染最佳实践*/public void conditionalRenderingBestPractice() {/*条件渲染示例:1. 基础条件判断#if(${swagger2})import io.swagger.annotations.ApiModel;import io.swagger.annotations.ApiModelProperty;#end2. 字段循环与条件#foreach($field in ${table.fields})#if(${field.keyFlag})@TableId(value = "${field.name}", type = IdType.AUTO)#elseif(${field.fill})@TableField(value = "${field.name}", fill = FieldFill.${field.fill})#else@TableField("${field.name}")#endprivate ${field.propertyType} ${field.propertyName};#end3. 复杂条件组合#if(${entityLombokModel})@Data#if(${superEntityClass})@EqualsAndHashCode(callSuper = true)#else@EqualsAndHashCode(callSuper = false)#end#if(${chainModel})@Accessors(chain = true)#end#end4. 包导入条件#foreach($pkg in ${table.importPackages})import ${pkg};#end#if(${swagger2})import io.swagger.annotations.ApiModel;#end#if(${entityLombokModel})import lombok.Data;#end*/}
}
代码质量最佳实践
/*** 代码质量最佳实践*/
public class CodeQualityBestPractice {/*** 代码规范检查*/public void codeStandardCheck() {/*1. 命名规范- 类名:大驼峰命名法(PascalCase)- 方法名:小驼峰命名法(camelCase)- 常量名:全大写下划线分隔(UPPER_SNAKE_CASE)- 包名:全小写点分隔2. 注释规范- 类注释:包含类的作用、作者、创建时间- 方法注释:包含方法作用、参数说明、返回值说明- 字段注释:说明字段的含义和用途3. 代码结构- 合理的包结构划分- 清晰的层次关系- 适当的设计模式应用4. 异常处理- 统一的异常处理机制- 合适的异常类型选择- 完整的异常信息记录*/}/*** 性能优化建议*/public void performanceOptimization() {/*1. 数据库层面- 合理的索引设计- 避免N+1查询问题- 使用批量操作- 合适的分页策略2. 代码层面- 避免在循环中进行数据库操作- 使用缓存减少重复查询- 合理使用连接池- 异步处理耗时操作3. 架构层面- 读写分离- 数据库分片- 缓存策略- 消息队列*/}/*** 安全性考虑*/public void securityConsiderations() {/*1. SQL注入防护- 使用参数化查询- 输入验证和过滤- 避免动态SQL拼接2. 数据验证- 前端和后端双重验证- 数据类型和格式检查- 业务规则验证3. 权限控制- 接口权限验证- 数据权限控制- 操作日志记录4. 敏感信息保护- 密码加密存储- 敏感数据脱敏- 日志信息过滤*/}
}
团队协作最佳实践
/*** 团队协作最佳实践*/
public class TeamCollaborationBestPractice {/*** 配置标准化*/public void configurationStandardization() {/*1. 统一配置文件- 使用配置文件管理生成器配置- 不同环境使用不同配置- 版本控制配置文件2. 模板标准化- 团队统一的代码模板- 定期更新和维护模板- 模板使用文档3. 命名规范- 统一的命名约定- 清晰的包结构规范- 一致的文件组织方式*/}/*** 版本控制策略*/public void versionControlStrategy() {/*1. 代码生成器配置版本控制- 配置文件纳入版本控制- 模板文件版本管理- 生成脚本版本控制2. 生成代码的版本控制策略- 基础代码纳入版本控制- 自定义修改的代码保护- 重新生成时的冲突处理3. 分支管理- 功能分支开发- 代码审查流程- 合并策略制定*/}/*** 文档管理*/public void documentationManagement() {/*1. 代码生成文档- 生成器使用说明- 配置参数文档- 模板自定义指南2. API文档- 自动生成API文档- 接口变更记录- 版本兼容性说明3. 开发规范文档- 编码规范- 数据库设计规范- 接口设计规范*/}/*** 持续集成实践*/public void continuousIntegrationPractice() {/*1. 自动化代码生成- CI/CD流程中集成代码生成- 数据库变更自动生成代码- 生成代码的自动测试2. 代码质量检查- 静态代码分析- 代码覆盖率检查- 性能测试集成3. 部署自动化- 自动化构建- 环境配置管理- 回滚策略制定*/}
}