1. MyBatis逆向工程深度解析
在Java持久层框架中,MyBatis逆向工程一直是个高频使用的功能。不同于基础版的代码生成,今天要探讨的是如何打造一个"奢华尊享版"的逆向工程方案。这个版本不仅包含标准功能,还整合了企业级开发中真正需要的增强特性。
我经历过十几个使用MyBatis的中大型项目,发现很多团队在使用逆向工程时都停留在基础层面。实际上,通过合理的定制和扩展,可以让生成的代码直接达到生产可用级别,减少80%以上的重复劳动。下面就来拆解这个方案的实现要点。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心功能设计
2.1 基础代码生成增强
标准的MyBatis Generator生成的Mapper接口和XML往往需要二次修改。我们可以通过自定义CommentGenerator和Plugin来实现:
java复制// 示例:增强版CommentGenerator
public class RichCommentGenerator extends DefaultCommentGenerator {
@Override
public void addFieldComment(Field field,
IntrospectedTable table,
IntrospectedColumn column) {
// 添加Swagger注解
field.addJavaDocLine("@ApiModelProperty(value = \""
+ column.getRemarks() + "\")");
// 添加校验注解
if (!column.isNullable()) {
field.addJavaDocLine("@NotNull");
}
if (column.getLength() > 0) {
field.addJavaDocLine("@Size(max = "
+ column.getLength() + ")");
}
}
}
2.2 动态查询支持
为生成的Mapper添加通用查询能力:
xml复制<!-- 在自定义的Mapper.xml模板中加入 -->
<sql id="Base_Query_Condition">
<where>
<foreach collection="condition.oredCriteria" item="criteria">
<if test="criteria.valid">
<trim prefix="(" prefixOverrides="and" suffix=")">
<foreach collection="criteria.criteria" item="criterion">
<choose>
<when test="criterion.noValue">
and ${criterion.condition}
</when>
<when test="criterion.singleValue">
and ${criterion.condition} #{criterion.value}
</when>
</choose>
</foreach>
</trim>
</if>
</foreach>
</where>
</sql>
3. 高级特性实现
3.1 审计字段自动处理
企业级应用通常需要创建人、创建时间等审计字段。可以通过自定义插件实现:
java复制public class AuditFieldPlugin extends PluginAdapter {
@Override
public boolean modelBaseRecordClassGenerated(TopLevelClass topLevelClass,
IntrospectedTable table) {
// 添加审计字段注解
topLevelClass.addAnnotation("@EntityListeners(AuditingEntityListener.class)");
return true;
}
@Override
public boolean modelFieldGenerated(Field field,
TopLevelClass topLevelClass,
IntrospectedColumn column,
IntrospectedTable table,
ModelClassType modelClassType) {
if ("create_time".equals(column.getActualColumnName())) {
field.addAnnotation("@CreatedDate");
}
// 其他审计字段处理...
return true;
}
}
3.2 多租户支持
对于SaaS系统,需要自动注入租户条件:
java复制public class TenantIdPlugin extends PluginAdapter {
@Override
public boolean sqlMapSelectByExampleWithoutBLOBsElementGenerated(
XmlElement element, IntrospectedTable table) {
// 在查询条件中自动加入tenant_id条件
XmlElement tenantCondition = new XmlElement("if");
tenantCondition.addAttribute(new Attribute(
"test", "tenantId != null"));
tenantCondition.addElement(new TextElement(
"and tenant_id = #{tenantId}"));
element.getElements().add(0, tenantCondition);
return true;
}
}
4. 工程化配置
4.1 分层架构生成
不同于传统单层生成,我们可以配置生成以下结构:
code复制src/
├── main/
│ ├── java/
│ │ ├── com.example.
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ │ ├── impl/
│ │ │ ├── dao/
│ │ │ ├── model/
│ │ │ ├── dto/
│ │ │ ├── vo/
│ ├── resources/
│ │ ├── mapper/
通过配置context的targetRuntime和targetProject实现多目录生成:
xml复制<context id="Mysql" targetRuntime="MyBatis3" defaultModelType="flat">
<property name="javaFileEncoding" value="UTF-8"/>
<property name="controllerTargetPackage" value="com.example.controller"/>
<property name="serviceTargetPackage" value="com.example.service"/>
<property name="serviceImplTargetPackage" value="com.example.service.impl"/>
<!-- 其他配置 -->
</context>
5. 定制化开发技巧
5.1 枚举类型处理
数据库中的状态字段通常需要映射为Java枚举:
java复制// 在自定义TypeHandler中处理
@MappedTypes(StatusEnum.class)
public class StatusEnumHandler implements TypeHandler<StatusEnum> {
@Override
public void setParameter(PreparedStatement ps, int i,
StatusEnum parameter, JdbcType jdbcType) {
ps.setInt(i, parameter.getCode());
}
// 其他方法实现...
}
// 在生成模型类时自动添加注解
field.addAnnotation("@TypeHandler(value = StatusEnumHandler.class)");
5.2 逻辑删除集成
实现逻辑删除而非物理删除:
xml复制<!-- 在全局配置中 -->
<table tableName="%">
<property name="softDeleteColumn" value="is_deleted"/>
<property name="softDeleteValue" value="1"/>
<property name="notSoftDeleteValue" value="0"/>
</table>
对应的插件实现:
java复制public class LogicalDeletePlugin extends PluginAdapter {
@Override
public boolean sqlMapDeleteByPrimaryKeyElementGenerated(
XmlElement element, IntrospectedTable table) {
// 将DELETE改为UPDATE
element.getElements().clear();
String tableName = table.getFullyQualifiedTableNameAtRuntime();
element.addElement(new TextElement(
"update " + tableName +
" set is_deleted = 1 where id = #{id}"));
return true;
}
}
6. 性能优化策略
6.1 批量操作支持
增强批量插入和更新能力:
java复制// 在自定义Mapper接口模板中加入
public interface RichMapper<T> {
@InsertProvider(type = RichSqlProvider.class, method = "batchInsert")
int batchInsert(@Param("list") List<T> records);
@UpdateProvider(type = RichSqlProvider.class, method = "batchUpdate")
int batchUpdate(@Param("list") List<T> records);
}
// 对应的SQL提供类
public class RichSqlProvider {
public String batchInsert(Map<String, Object> params) {
List<?> list = (List<?>) params.get("list");
// 构建批量INSERT语句...
}
}
6.2 结果集缓存
为常用查询添加缓存注解:
java复制public class CachePlugin extends PluginAdapter {
@Override
public boolean clientGenerated(Interface interfaze,
TopLevelClass topLevelClass,
IntrospectedTable table) {
// 为Mapper接口添加二级缓存配置
interfaze.addAnnotation("@CacheNamespace(implementation = MybatisRedisCache.class)");
return true;
}
}
7. 监控与调试
7.1 SQL日志增强
通过自定义拦截器实现完整SQL日志:
java复制@Intercepts({
@Signature(type= Executor.class, method="update",
args={MappedStatement.class,Object.class}),
@Signature(type= Executor.class, method="query",
args={MappedStatement.class,Object.class,
RowBounds.class,ResultHandler.class})
})
public class SqlLoggerInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) {
// 获取BoundSql并记录完整SQL与参数
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
Object parameter = invocation.getArgs()[1];
BoundSql boundSql = ms.getBoundSql(parameter);
log.info("SQL: {}", boundSql.getSql());
log.info("Params: {}", parameter);
return invocation.proceed();
}
}
7.2 执行耗时监控
java复制public class PerformanceInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) {
long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long end = System.currentTimeMillis();
log.info("Execution took {} ms", end - start);
}
}
}
8. 企业级集成方案
8.1 Spring Boot Starter
将整套方案打包为Starter:
java复制@Configuration
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisRichAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// 配置包含所有插件的SqlSessionFactory
}
@Bean
public SqlLoggerInterceptor sqlLoggerInterceptor() {
return new SqlLoggerInterceptor();
}
// 其他Bean配置...
}
8.2 多数据源支持
java复制@Configuration
public class DynamicDataSourceConfig {
@Bean
@Primary
public DataSource dynamicDataSource(
@Qualifier("master") DataSource master,
@Qualifier("slave") DataSource slave) {
DynamicDataSource ds = new DynamicDataSource();
ds.setDefaultTargetDataSource(master);
Map<Object, Object> dataSources = new HashMap<>();
dataSources.put("master", master);
dataSources.put("slave", slave);
ds.setTargetDataSources(dataSources);
return ds;
}
}
9. 代码生成配置实践
9.1 完整配置示例
xml复制<generatorConfiguration>
<context id="DB2Tables" defaultModelType="flat">
<!-- 插件配置 -->
<plugin type="com.example.AuditFieldPlugin"/>
<plugin type="com.example.TenantIdPlugin"/>
<plugin type="com.example.LogicalDeletePlugin"/>
<!-- 注释生成器 -->
<commentGenerator type="com.example.RichCommentGenerator">
<property name="author" value="Generator"/>
<property name="dateFormat" value="yyyy-MM-dd"/>
</commentGenerator>
<!-- 数据源配置 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/test"
userId="root" password="123456">
<property name="nullCatalogMeansCurrent" value="true"/>
</jdbcConnection>
<!-- 模型生成配置 -->
<javaModelGenerator targetPackage="com.example.model"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<!-- Mapper XML生成配置 -->
<sqlMapGenerator targetPackage="mapper"
targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<!-- Mapper接口生成配置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.example.dao"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<!-- 表配置 -->
<table tableName="t_user" domainObjectName="User"
enableCountByExample="false" enableUpdateByExample="false"
enableDeleteByExample="false" enableSelectByExample="false"
selectByExampleQueryId="false">
<generatedKey column="id" sqlStatement="JDBC" identity="true"/>
<columnOverride column="status"
javaType="com.example.enums.StatusEnum"
typeHandler="com.example.handler.StatusEnumHandler"/>
</table>
</context>
</generatorConfiguration>
9.2 多环境配置策略
建议采用profile区分环境配置:
xml复制<profiles>
<profile id="dev">
<properties>
<jdbc.url>jdbc:mysql://dev-db:3306/test</jdbc.url>
</properties>
</profile>
<profile id="prod">
<properties>
<jdbc.url>jdbc:mysql://prod-db:3306/test</jdbc.url>
</properties>
</profile>
</profiles>
通过mvn命令指定profile:
bash复制mvn mybatis-generator:generate -Pprod
10. 常见问题解决方案
10.1 字段名与关键字冲突
处理方案:
xml复制<table tableName="user">
<columnOverride column="order" property="userOrder"
delimitedColumnName="true"/>
</table>
10.2 复杂类型处理
对于JSON类型的字段:
java复制// 在模型类生成时添加注解
field.addAnnotation("@ColumnType(typeHandler = JsonTypeHandler.class)");
10.3 生成代码风格统一
配置模板文件:
xml复制<property name="javaFormatter" value="com.example.MyJavaFormatter"/>
<property name="xmlFormatter" value="com.example.MyXmlFormatter"/>
11. 进阶扩展方向
11.1 生成GraphQL Schema
java复制public class GraphQLSchemaPlugin extends PluginAdapter {
@Override
public boolean contextGenerateAdditionalXmlFiles(
Context context, List<GeneratedXmlFile> warnings) {
// 根据表结构生成GraphQL类型定义
String schema = generateSchema(context);
GeneratedXmlFile gxf = new GeneratedXmlFile(
new Document(schema),
"schema.graphqls",
"graphql",
context.getSqlMapGeneratorConfiguration().getTargetPackage(),
"UTF-8");
warnings.add(gxf);
return true;
}
}
11.2 生成前端API代码
java复制public class ApiClientPlugin extends PluginAdapter {
@Override
public boolean contextGenerateAdditionalJavaFiles(
Context context, List<GeneratedJavaFile> warnings) {
// 生成TypeScript的API客户端代码
CompilationUnit unit = new CompilationUnit("com.example.api");
// 构建API定义...
GeneratedJavaFile gjf = new GeneratedJavaFile(
unit,
context.getJavaClientGeneratorConfiguration().getTargetProject(),
"UTF-8");
warnings.add(gjf);
return true;
}
}
12. 最佳实践建议
-
版本控制:将生成器配置和模板纳入版本控制,与项目代码同步维护
-
分层生成:区分基础代码和定制代码,基础代码由生成器维护,定制代码手动开发
-
持续集成:在CI流程中加入代码生成步骤,确保生成的代码始终与数据库同步
-
文档生成:集成Swagger等文档工具,实现从数据库到API文档的自动化
-
代码审查:虽然代码是生成的,但仍需进行审查,确保符合项目规范
这套方案在实际项目中经过多次迭代,已经能够满足企业级应用开发的绝大多数需求。通过合理的配置和扩展,可以显著提升开发效率,同时保证代码质量的一致性。
