ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

DOMA实战排错:注解处理器、SQL文件与Spring Boot集成

DOMA实战排错:注解处理器、SQL文件与Spring Boot集成

做 Java 持久层的人应该都遇到过这种时刻:本地跑得好好的,推到 CI 上编译直接红一片,日志里滚着几十行cannot find symbol: class UserDaoImpl。第一次遇到会怀疑人生,第二次遇到就知道——大概率不是代码写错,而是 DOMA 的注解处理器没干活。DOMA 这个框架的特点很鲜明:它在编译期就把 DAO 的实现类生成好、把 SQL 文件解析校验完,好处是运行期几乎没有反射开销,坏处是问题全部前移到构建阶段,报错信息往往指向生成物而不是根因。这篇内容我把这些年处理 DOMA 相关问题的经验整理出来,覆盖构建链路、SQL 文件定位、2-way SQL 写法、实体映射、事务管理、主键与批量、Spring Boot 集成、日志与代码生成八个方面。适合正在用或者准备用 DOMA 做数据访问的同学,从刚上手的新人到已经在生产环境跑了一年的人,应该都能从里面找到一两条自己没注意过的细节。

1. 编译期就崩的 DAO:先在构建链路里找原因

DOMA 的 DAO 是接口,你写@Dao注解的接口,编译时由注解处理器生成<接口名>Impl的实现类。运行期注入的、被调用的都是这个生成类。所以只要生成环节出问题,你看到的报错永远是"找不到 Impl",而不是"处理器没跑"。这个信息差是排查的第一道坎。

1.1 实现类不是运行时生成的

很多人第一反应是去检查依赖有没有引进来,这没错,但方向容易跑偏。doma-core是运行期需要的东西,负责 SQL 执行、实体映射、方言支持;真正生成实现类的是doma-processor,它只在编译期存在,运行期完全不需要。这两者的区别决定了它们的依赖作用域必须分开写。

如果你的构建脚本里把处理器写成了implementation或者compileOnly,会出现两种截然不同的症状。写成implementation时,处理器会被打进最终产物,能跑通,但你会带着几百 KB 完全用不上的东西上线,而且某些构建工具在增量编译时会因为类路径变化反复触发全量编译。写成compileOnly更麻烦,它在编译期看似可见,但很多构建工具从某个版本开始不再把compileOnly的内容传递到注解处理器路径上,结果就是处理器压根没被调用,最后报"找不到 Impl"。

正确的写法是按构建工具区分。用 Gradle 的话,处理器要放在annotationProcessor配置里:

dependencies { implementation 'doma-core:x.y.z' annotationProcessor 'doma-processor:x.y.z' }

用 Maven 的话,处理器要写进编译插件的annotationProcessorPaths,而不是当成普通依赖:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <artifactId>doma-processor</artifactId> <version>x.y.z</version> </path> </annotationProcessorPaths> </configuration> </plugin>

注意:如果项目里同时用了其他注解处理器(比如生成样板代码的那类工具),annotationProcessorPaths里要把它们都列上,只列 DOMA 会把其他的挤掉,然后你会看到一堆本来正常的 setter 方法集体消失。

1.2 依赖作用域写错,处理器根本不参与编译

上面说的是显式写法,还有一种更隐蔽的情况:项目用了构建工具的 BOM 或者父 POM 统一管理版本,你在子模块里只写annotationProcessor 'doma-processor'不写版本号,结果父级没有对这个坐标做版本仲裁,构建工具要么报"无法解析版本",要么静默跳过。后者的表现和没加处理器一模一样。

排查这类问题有个笨但快的办法:编译时加上-X打开构建工具的详细日志,搜索doma-processor这个关键字,看它有没有出现在 processor path 里。如果只在 classpath 里、不在 processor path 里,那就是作用域问题,跑再多次clean也没用。另一个办法是去build/classes或target/classes下找生成物,DOMA 默认把 Impl 生成在 DAO 接口所在包的impl子包里,你去看那个目录是不是空的,一眼就知道处理器有没有干活。

顺带说一下 Kotlin 项目。Kotlin 用的是 kapt(或者新版构建工具里的 KSP,但 DOMA 目前对 kapt 的支持更成熟),处理器要配在kapt配置里而不是annotationProcessor,并且kapt的配置项里要显式声明处理器路径。Kotlin 还有一个额外坑:@Entity标注的 data class 属性必须是var且可赋值,用val会导致生成的映射代码无法赋值,编译期报错信息会指向生成类,很难一眼看出是实体定义的问题。

1.3 IDE 里那几处容易漏掉的开关

命令行能编译过、IDE 里却满屏红,这是最典型的注解处理器未启用症状。主流 IDE 对注解处理器的处理方式不完全一致,有的默认开启、有的需要手动勾选,有的还要单独配置处理器路径。如果你看到"找不到 Impl"的红线但构建工具构建正常,先去设置里搜"annotation processing",确认是启用状态,并且确认处理器没有被限定在某些模块上。

还有一种情况是 IDE 的增量编译缓存脏了。注解处理器生成的文件在增量编译时可能没有被正确刷新,表现是改完 SQL 或者实体之后,Impl 里还是旧的方法签名。处理方式不是重启 IDE,而是先做一次完整的重新构建,让生成目录重建。我一般会把这个动作写进排错清单:只要涉及实体字段增删、DAO 方法签名变更,就先完整重建一次,再判断是不是真的有问题。

1.4 版本错位引发的连锁报错

doma-core和doma-processor的版本必须严格一致。这条看起来是常识,但实际踩到的人不少,因为版本不一致时的报错非常有迷惑性。处理器生成的 Impl 会调用 core 里的类和方法,如果 core 的某个方法签名在版本间发生了变化,你看到的报错会是"找不到符号:方法 xxx",报错位置在生成类里,而生成类你根本没写过。

更麻烦的是依赖传递带来的隐式升级。比如 A 模块依赖了 B 模块,B 模块内部引了较新版本的 core,而你的处理器还是老版本,冲突就出现了。这种情况要靠构建工具的依赖树命令去查,看doma-core最终解析到的是哪个版本,和你显式声明的处理器版本对不对得上。对不上就在根项目统一版本,别指望各个模块各管各的。

2. SQL 文件的定位规则:路径差一个字符就找不到

DOMA 把 SQL 从 Java 代码里挪到了独立的.sql文件里,这个设计让 SQL 可以单独格式化、单独审阅、单独交给 DBA 评审,是它最实用的特性之一。代价是路径规则必须记准,一旦错位,编译期直接报"SQL 文件未找到",不会给你模糊匹配的机会。

2.1 从 Dao 全限定名反推路径

默认规则很机械:META-INF+ DAO 接口所在的包路径(点换成斜杠)+ 接口简单名 + 方法名 +.sql。假设你有com.example.app.dao.UserDao接口,里面有个selectById方法,那么在资源目录下的完整路径就是:

src/main/resources/META-INF/com/example/app/dao/UserDao/selectById.sql

这条规则里最容易出错的不是包名,而是方法名的对应关系。如果 DAO 方法有重载(同名不同参数),DOMA 是按方法名找 SQL 文件的,重载方法会指向同一个文件,这时候必须用@Sql显式指定路径来区分。另外,接口名的大小写必须和文件名完全一致,Windows 上不敏感、Linux 上敏感,本地开发一切正常、一上容器就报找不到,这类问题排查起来很耗时间。

2.2 注解里显式指定路径更省心

包路径一长,默认规则就容易写错。我的做法是能不依赖默认规则就不依赖,直接在方法上加@Sql指定相对路径:

@Sql("META-INF/sql/user/selectById.sql") @Select User selectById(Integer id);

这样做的好处是把 SQL 目录结构从"必须镜像 Java 包结构"里解放出来,可以按业务模块组织,比如META-INF/sql/user/、META-INF/sql/order/。多个 DAO 共用同一段 SQL 的时候也方便,直接指向同一个文件就行,不用复制粘贴三份再各自维护。

代价是路径写错时编译器只能告诉你"找不到这个文件",不会帮你猜。所以我一般会在项目初始化阶段就把这套目录约定固化下来,写进团队的项目结构说明里,新人接手不用再猜。

2.3 资源目录与打包方式

SQL 文件必须最终落在 classpath 的META-INF下。用 Maven 或者 Gradle 的默认结构,src/main/resources会自动打进产物,没问题。但如果项目自定义了资源目录,或者用了多模块结构把资源集中放到了某个公共模块,就要确认这些资源确实被包含在最终 classpath 里。

多模块项目里有个高频坑:SQL 文件放在 A 模块的 resources 下,DAO 接口在 B 模块,B 依赖了 A。这种情况能不能找到取决于 A 的资源有没有被打进 B 的运行期 classpath。implementation依赖会传递,但如果你在 A 里把资源排除掉了(有些项目为了减小产物体积会配exclude),运行时就会报文件不存在。这类问题的特征是:编译期不报错(因为编译期看的是源码路径),运行期才炸。所以 SQL 文件的检查不能只看编译通过,还要跑一次集成测试。

2.4 编码与 BOM 造成的解析失败

SQL 文件里如果有中文注释、中文别名,编码必须统一成 UTF-8。DOMA 在编译期解析 SQL 文件,如果读进来的字节流按错误编码解释,会出现乱码,严重时直接解析失败。更隐蔽的是 BOM:某些编辑器保存 UTF-8 时会带 BOM 头,这个隐藏字符会跟在 SQL 语句最前面,导致解析器认不出第一条语句。

另外说一个和换行符有关的坑。SQL 文件里的行注释--在某些情况下会把后续内容一起吞掉,如果你在拼接动态 SQL 时行注释写在了指令中间,解析结果可能和你肉眼看到的完全不一样。我的习惯是 SQL 文件里一律用/* */块注释,不用--,虽然多敲两个字符,但省下来的排查时间远不止这些。

3. 2-way SQL 的实用写法与常见误用

2-way SQL 是 DOMA 最有辨识度的能力:带有/*%if */这类指令的 SQL 文件,既能当作纯 SQL 直接在数据库客户端里跑(指令部分被当成注释忽略),又能被框架解析成动态 SQL。这个"双向可用"的特性对调试帮助极大,但也意味着指令的语法必须严格,错一个符号就从"能跑"变成"编译失败"。

3.1 绑定变量占位符的样例值不是废代码

SQL 文件里你会看到这种写法:

select id, name, age from users where id = /* id */1

/* id */后面那个1看起来像随手写的,其实是占位符的"样例值"。它的作用是让这条 SQL 在数据库客户端里能直接执行——把/* id */换成实际值的位置就是这里。DOMA 解析时会把它替换成真正的绑定参数,值本身不参与执行。

理解这一点很重要,因为它解释了为什么占位符的位置必须精确:where id = /* id */1是对的,where id = 1 /* id */就不对,解析器认不出绑定变量,会把它当成字面量。我之前接手过一个项目,SQL 里全是后一种写法,测试环境数据量小一直没暴露,上生产后因为无法使用索引直接超时,排查了半天才发现是绑定变量根本没生效。

3.2 条件分支里最容易写错的空值判断

动态条件的标准写法:

select * from users where 1 = 1 /*%if name != null and name != '' */ and name like /* name */'%a%' /*%end*/

几个容易翻车的点。第一,表达式里用and还是&&,两种写法框架都支持,但同一个文件里混用容易出错,选一种固定下来。第二,字符串比较用''还是双引号,SQL 文件里的表达式遵循的是框架的表达式语法,字符串字面量用单引号,这一点和 Java 不一样。第三,也是最容易忽略的:/*%if */和/*%end*/必须成对出现,嵌套时尤其容易漏。漏了的后果不是运行时报错,而是编译期解析失败,报错信息会告诉你哪个文件的哪个位置括号不匹配,但行号有时会偏移,得对着文件慢慢数。

我自己的应对办法是给每个条件块加注释标号,比如/*%if ... */ -- c1,/*%end*/ -- c1,嵌套三层以上时全靠这个对齐。

3.3 IN 展开与空列表保护

IN 查询是最容易写出问题的场景。传一个列表进去,指望它自动展开成in (1, 2, 3),需要两个配合:参数上加@In注解,SQL 里用展开指令:

select * from users where id in /*%expand*/(/* ids */1, 2)

这里有个必须处理的边界:列表为空。空列表展开后得到的是in (),语法错误。所以外面要套一层条件判断:

/*%if ids != null and ids.size() > 0 */ and id in /*%expand*/(/* ids */1, 2) /*%end*/

有人会说空列表直接返回空结果不就行了,逻辑上确实如此,但在 DAO 层做判断会让每个调用方都得记得处理,不如把保护写进 SQL 模板里,一次写好终身受益。另外,IN 列表的元素数量也要有上限意识,某些数据库对 IN 里的元素个数有限制,超过会报错或者性能骤降。遇到大批量 ID 的场景,老老实实分批查,别硬塞。

3.4 LIKE 转义与 @Escape

模糊查询里如果用户输入了%或者_,直接拼进 LIKE 会变成通配符,搜"50%"可能把全表都捞出来。DOMA 提供了专门的处理方式:参数上加@Escape注解,指定转义字符,框架会自动对%、_和转义字符本身做处理。

@Select List<User> searchByName(@Escape(escapeChar = '\\') String name);

对应的 SQL 里要写escape '\'声明转义字符,两边必须一致。这一点经常被漏掉:Java 侧加了注解,SQL 里没写escape子句,转义字符就被当成普通字符匹配了,结果就是搜不到东西,但又不报错,非常难查。

3.5 动态排序不要硬拼字符串

order by后面跟列名,这个位置不能放绑定变量(大部分数据库不支持order by ?),所以不能简单写成order by /* sortColumn */id。常见的处理方式是分支穷举:

order by /*%if sortKey == 'name' */ name /*%elseif sortKey == 'age' */ age desc /*%else*/ id /*%end*/

看起来啰嗦,但这是最安全的做法——列名来自白名单,不可能被注入。如果排序维度很多,穷举会很冗长,可以用展开指令配合服务端的白名单校验,但白名单校验一定要做在服务端,别把列名直接从请求参数透传到 SQL 层。

4. 实体映射的四个高频坑

实体映射是 DOMA 里配置量最大的部分,也是"看着能跑、实际有隐患"最集中的地方。很多问题不会立刻报错,等到数据出问题才暴露。

4.1 驼峰属性与下划线列名

Java 属性习惯驼峰,数据库列名习惯下划线,两者对不上就会导致映射失败或者映射到空值。DOMA 的处理方式有两种:一是每个属性显式加@Column(name = "user_name"),二是配置全局的命名策略。

显式注解最稳,缺点是字段多的时候啰嗦。全局命名策略省事,但要确认你用的版本支持哪种枚举值,不同版本的枚举名可能不一样。我的建议是:核心业务表用显式注解,日志表、配置表这类字段稳定的用全局策略。理由很实际——核心表的字段变更频率高,映射关系写在代码里一眼可见,改的时候不容易漏;边缘表字段几年不动,省点代码量更划算。

还有一个容易忽略的点:如果查询结果的列名和实体属性对不上,DOMA 不会抛异常,对应属性就是 null 或默认值。这种"静默错位"比报错危险得多,所以上线前一定要跑一次真实数据的查询验证。

4.2 基本类型和包装类型的取舍

实体里用int还是Integer,这个选择有实质影响。数据库列允许 NULL 时,如果用基本类型,查出来的 NULL 没法表达,行为取决于框架的具体实现,可能是默认值 0,可能是报错。更麻烦的是更新操作:一个int字段你无法区分"用户主动设为 0"和"这个字段没查出来"。

我的原则很简单:实体字段一律用包装类型,除非这个字段确定非空且业务上有明确的默认语义。代价是多几个 null 判断,收益是避免了一整类"数据莫名其妙变成 0"的线上问题。这个规则在金额、计数、状态位这几类字段上尤其重要,金额变成 0 的后果不用多说。

4.3 只更新改动字段:@OriginalStates 的正确姿势

默认的更新语句是全字段更新,也就是把实体里所有映射字段都写进set子句。这在高并发场景下有问题:两个请求同时读同一条记录,A 改了字段 x,B 改了字段 y,全字段更新会导致后提交的把先提交的覆盖掉。

DOMA 的解法是在实体里加一个特殊字段并用注解标记:

@Entity public class User { @Id Integer id; String name; String email; @OriginalStates User originalStates; }

加了之后,更新时框架会对比当前值和快照值,只把变化的字段写进set子句。听起来很美好,但有个前提:实体必须是从数据库查出来的。如果你手动new一个实体、只填了 id 和要改的字段,originalStates是 null,行为就退化成全字段更新,没填的字段会被写成 null。这个坑我见过不止一次,表现是"更新一个字段,结果其他字段全空了"。

正确姿势是:先selectById查出完整实体,改字段,再update。多一次查询,换来的是更新语义的安全性。

4.4 乐观锁 @Version 的更新条件

@Version标注的字段会自动参与更新条件,SQL 大致是update ... set version = version + 1 where id = ? and version = ?。更新影响的记录数为 0 就说明版本冲突了,业务层要处理这个返回值。

这里有个细节:返回 0 不等于出错。很多代码把update返回值直接判断为"成功/失败",遇到版本冲突时抛出"数据不存在"的异常,误导排查方向。正确做法是区分"记录不存在"和"版本冲突",前者查一次就能确认,后者要考虑重试或者提示用户。

另外,乐观锁字段的初始值要和数据库列默认值一致。见过数据库默认 0、实体初始化成 1 的配置,结果第一次更新就冲突,因为条件里带的是version = 1但库里存的是 0。

5. 事务与连接:独立运行和容器托管的差别

事务这块的问题通常不是"事务不生效",而是"事务生效了但用了不该用的连接"。表现是连接池被打满、或者同一段逻辑里两次查询看到的数据不一致。

5.1 独立场景下的事务边界

不用任何容器、纯 Java 应用里跑 DOMA,事务要靠框架自带的事务管理器,通过required、requiresNew这类传播行为来划定边界:

TransactionManager tm = new TransactionManager(config); tm.required(() -> { dao.insert(a); dao.insert(b); });

这里的关键是同一个事务管理器实例必须复用。如果每次调用都new一个,事务边界就是断的,两次插入各自提交,出异常也回滚不了。这类写法在工具类里特别容易出问题,因为工具类往往是每次调用都新建对象。

5.2 和 Spring 混管时的连接泄漏

把 DOMA 放进 Spring 环境后,事务通常交给 Spring 管理,DOMA 的事务管理器就不该再参与了。两者混用会出现"看起来有事务、实际是两个连接"的情况:Spring 开了事务拿到连接 A,DOMA 自带的事务管理器又去数据源要了连接 B,两条连接各自持有事务,锁等待、死锁、连接池耗尽全来了。

判断方法很直接:在事务方法里打印当前连接对象,看两次数据库操作的连接是不是同一个。不是同一个就是混管了。修复方式是把 DOMA 的配置接到 Spring 管理的连接上,让框架感知 Spring 的事务上下文,而不是自己开事务。

还有一个相关的坑是只读查询也开事务。严格来说读操作不需要事务,但在 Spring 里如果方法上挂了事务注解,就会占用一个连接直到方法结束。批量查询、报表导出这类耗时操作一旦开了事务,连接被长时间占用,并发一高就打满池子。读操作单独配置只读事务或者干脆不开事务,能省下不少连接。

6. 主键、批量与分页三件事

这三块都属于"能用但容易用错"的典型,用错了不报错,只是性能差或者数据不对。

6.1 自增主键的三种生成策略

DOMA 支持三种主键生成方式:数据库自增(IDENTITY)、序列(SEQUENCE)、表模拟序列(TABLE)。选择依据主要是数据库本身的能力和你对批量插入的需求。

策略适用场景主要限制
IDENTITY单条插入为主,需要立刻拿到主键批量插入时主键回填不可靠,性能受限
SEQUENCE批量插入、需要预分配主键依赖数据库序列对象,部分数据库不支持
TABLE数据库不支持序列时的替代方案每次取值都要访问表,并发下有锁竞争

选 IDENTITY 最省心,但如果你有批量导入的需求,就要提前想清楚:批量插入时自增主键无法逐条回填,实体对象里的 id 是空的。后面如果要用这个 id 做关联,就得换个策略。这个决策要在表设计阶段做,中途换代价很大。

6.2 批量插入的注解选择

DOMA 提供两种批量插入:一种是 JDBC 层面的批量执行(框架的批量插入注解),一条 SQL 反复绑定参数;另一种是拼成一条多值insert ... values (...), (...), (...)的语句。前者兼容性好、内存占用低,后者在数据量大时往返次数少、通常更快。

选择依据不只是性能。多值插入在不同数据库上对 SQL 长度有限制,一次塞几千行可能触发报文大小限制;批量执行则受限于驱动是否支持批量重写。我的经验是:几百行以内用多值插入,上千行分批走批量执行,批次大小控制在 500 到 1000 之间,太大反而因为日志和锁的原因变慢。

还有一点容易忽略:批量操作放在一个事务里和每条单独提交,性能差一个数量级。批量写之前确认事务边界包住了整个批次,否则等于一条一条提交。

6.3 分页不要自己拼 limit

DOMA 的分页是通过额外的参数对象把 limit 和 offset 传给框架,由方言决定具体的 SQL 语法。Oracle 用rownum、MySQL 用limit、SQL Server 用offset fetch,这些差异框架帮你处理了。自己拼limit的后果是换数据库就崩。

List<User> selectAll(SelectOptions options); // 调用 dao.selectAll(SelectOptions.get().limit(20).offset(40));

分页还有个隐藏问题:深分页。offset很大时数据库要扫描并丢弃前面的行,越翻越慢。这种情况改用游标分页(带上一次的最后一条记录的排序键)能解决,但那就不在框架的分页能力范围内了,需要自己写 SQL。

7. Spring Boot 集成时的配置边界

Spring Boot 环境用 DOMA,一般是通过官方提供的 starter 自动配置,省掉大量手工装配。但自动配置的边界如果不清楚,出问题时反而无从下手。

7.1 starter 帮你做了哪些自动配置

starter 主要做三件事:读取配置项构造框架的运行配置对象、注册数据源和事务管理的桥接、扫描 DAO 接口注册成 Bean。这三件事的顺序是有依赖的,所以配置项写错时,报错往往出现在第二、三步,而不是第一步。

常见的配置项包括数据源引用、方言、SQL 日志类型、异常日志类型。方言要显式配置,因为框架没法可靠地从连接元数据推断出你要用哪种分页和主键获取方式——同一个数据库在不同模式下行为可能不同。这点很多人忽略,觉得连上了就行了,结果分页语法不对。

7.2 多数据源下 Config 与 Dao 的隔离

单数据源很舒服,多数据源就开始出问题。核心是两件事:每个数据源要有一套独立的运行配置,每个 DAO 要明确归属到哪套配置上。

如果两个数据源的 DAO 混在同一个包下扫描,很容易出现"A 库的 DAO 用了 B 库的连接"。症状是查询报"表不存在",因为确实在错误的库里查了。解决办法是按包隔离,配置扫描规则,把每个数据源的 DAO 放在独立包下,配套独立的配置类和事务管理器。

7.3 方言配错引发的隐蔽错误

方言配错的典型表现不是报错,而是行为异常:分页返回的行数不对、IN 列表展开的语法不对、批量插入报语法错误。因为框架生成 SQL 时依赖方言决定语法,方言和实际数据库不匹配时,生成的 SQL 可能语法正确但语义不对,比如用 MySQL 的方言连 PostgreSQL,limit ?也能跑,但序列取值、字符串拼接这些细节会出问题。

判断方法:打开 SQL 日志,看实际执行的语句是不是你要的语法。这条建议适用所有怀疑方言的问题。

8. 把日志和代码生成用起来,问题少一半

最后这块不讲问题,讲两个能显著降低问题出现率的实践。

8.1 打开 SQL 日志的正确粒度

框架的 SQL 日志分几个级别:不打、打原始 SQL(带?占位)、打可读 SQL(参数已代入)。开发环境用可读形式,一眼能看出参数对不对;生产环境用原始形式,避免日志里大量敏感数据。

配置上就是把 DOMA 根包对应的 logger 级别调到 DEBUG。根包名在依赖里翻一下注解源码就能看到,配的时候注意别写到子包上,否则部分日志会漏掉。另外异常发生时框架也会输出 SQL,这个开关单独控制,建议生产环境打开——异常时的 SQL 往往是排查问题的关键线索。

注意:日志里打印参数意味着任何敏感信息都会落盘。涉及凭证、身份信息的表,生产环境的 SQL 日志要评估后再开,或者只开异常场景。

8.2 用代码生成对齐表结构

框架配套的代码生成工具能从数据库表结构反向生成实体和 DAO,这在接手老项目或者表结构频繁变更时非常好用。配置好驱动、连接信息、表名过滤规则和目标目录,执行生成任务就行。

生成完之后一定要重新编译一次,因为注解处理器需要读到新的实体和 DAO 接口才会生成 Impl。这算是我踩过的一个小坑:生成代码后直接跑测试,报"找不到 Impl",以为是生成工具出问题,其实只是没重新编译,处理器还没跑。现在我把生成和编译串成一个命令,省得每次都忘。

返回列表