# auto-mapper
**Repository Path**: tang_world/auto-mapper
## Basic Information
- **Project Name**: auto-mapper
- **Description**: AutoMapper是一款适用于Mybatis的SQL生成框架,提供了JPA风格的SQL语句生成能力,用户仅需依赖一个编译期jar包就能在编译期根据Mapper接口中的方法定义生成相应的XML文件和SQL语句。就像流行的Lombok一样,一切发生在编译期间,因此不会对软件性能造成任何影响,并且用户可以直接在编译完成的target目录或jar中看到生成的SQL语句。
- **Primary Language**: Unknown
- **License**: Apache-2.0
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 1
- **Created**: 2023-01-10
- **Last Updated**: 2023-08-28
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# AutoMapper
AutoMapper是一款适用于Mybatis的SQL生成插件,提供了JPA风格的SQL语句生成能力,用户仅需依赖一个编译期jar包,就能在编译期根据Mapper接口中的方法定义生成相应的XML文件和SQL语句。就像流行的Lombok一样,一切发生在编译期间,因此不会对软件性能造成任何影响,并且用户可以直接在编译完成的target目录或jar中看到生成的SQL语句。
# 效果展示
下方动图演示了在idea上插件在编译时动态生成SQL的效果,您可以通过Build -> ReBuild Project达到一样的效果。

无论您使用的是什么开发环境,只要编译器是按照标准实现的,都能生成成功。另外在打包发布时,这个过程是无感的,因为在打包编译的过程中AutoMapper会自动生效。
# 快速开始
首先需要在项目的编译阶段依赖AutoMapper,如果您正在使用maven管理项目,那么添加如下依赖即可:
```xml
fun.fengwk.auto-mapper
auto-mapper-processor
provided
0.0.24
```
常见的Mybatis使用方式是编写一个`*Mapper.java`文件在其中定义接口方法,然后编写一个`*Mapper.xml`文件实现接口方法的SQL语句。例如example模块中(tips:example模块中展示了如何在spring-boot项目里使用AutoMapper)就有`ExampleMapper.java`文件和`ExampleMapper.xml`文件,但只要在`ExampleMapper`类上标记`@AutoMapper`注解,AutoMapper就可以在编译期间帮助您生成相应符合规范的Mybatis SQL片段语句到相同包路径下的xml文件中(如果已经手动编写了相应方法定义的SQL,那么框架会跳过该方法的自动生成工作)。
```java
@AutoMapper
public interface ExampleMapper {
ExampleDO findById(long id);
}
```
例如定义了如上的`ExampleMapper`类,那么框架会自动生成如下的Mybatis SQL片段并插入与`ExampleMapper`类相同包路径下的名称为`ExampleMapper.xml`文件中,如果不存在`ExampleMapper.xml`文件那么AutoMapper将会自动生成一个。
```xml
```
# 语法约定
为了实现SQL生成,必须遵循一定规则来定义Mapper接口的方法,如果您曾使用过JPA,那么这些规则会非常容易理解,因为这些规则几乎完全遵循了JPA的约定。
下标站时刻展示了当前支持的所有模式(如果您了解状态机可以直接查看`doc/MapperMethodStateMachine.drawio`):
| 描述 | 模式 | 约束 |
| ---- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 新增 | insert[Selective] | 单个JavaBean入参 |
| | insertAll[Selective] | 可迭代的JavaBean入参,如果在该场景下使用了Selective,则需要在jdbc参数中追加`allowMultiQueries=true`,并且UseGeneratedKeys会失效(确切的来说只会返回第一个SQL的自增键,这是由jdbc的特性决定的,详见JdbcGeneratedKeysTest)|
| 删除 | deleteAll | 无入参 |
| | deleteBy... | 多入参数时必须使用@Param注解绑定By后参数与入参关系 |
| 修改 | updateBy...[Selectivce] | 多入参数时必须使用@Param注解绑定By后参数与入参关系 |
| 查询 | findAll[OrderBy...] | 无入参 |
| | findBy...[OrderBy...] | 多入参数时必须使用@Param注解绑定By后参数与入参关系 |
| 计数 | countAll | 无入参 |
| | countBy... | 多入参数时必须使用@Param注解绑定By后参数与入参关系 |
| 分页 | pageAll[OrderBy...] | 入参必须拥有limit,可选offset,必须使用@Param注解绑定By后参数与入参关系 |
| | pageBy...[OrderBy...] | 入参必须拥有limit,可选offset,必须使用@Param注解绑定By后参数与入参关系 |
下表格展示了当前支持的所有关键字:
| 关键字 | 方法名 | SQL |
| ------------------ | -------------------------------------- | -------------------------------------------- |
| And | findByLastnameAndFirstname | ... where x.lastname = ? and x.firstname = ? |
| Or | findByLastnameOrFirstname | ... where x.lastname = ? or x.firstname = ? |
| Is,Equals | findById,findByIdIs,findByIdEquals | ... where x.id = ? |
| LessThan | findByAgeLessThan | ... where x.age < ? |
| LessThanEquals | findByAgeLessThanEquals | ... where x.age <= ? |
| GreaterThan | findByAgeGreaterThan | ... where x.age > ? |
| GreaterThanEquals | findByAgeGreaterThanEquals | ... where x.age >= ? |
| After | findByIdAfter | ... where x.id > ? |
| Before | findByIdBefore | ... where x.id < ? |
| IsNull | findByNameIsNull | ... where x.name is null |
| IsNotNull,NotNull | findByNameIsNotNull,findByNameNotNull | ... where x.name is not null |
| Like | findByNameLike | ... where x.name like ? |
| NotLike | findByNameNotLike | ... where x.name not like ? |
| StartingWith | findByNameStartingWith | ... where x.name not like '?%' |
| EndingWith | findByNameEndingWith | ... where x.name not like '%?' |
| Containing | findByNameContaining | ... where x.name not like '%?%' |
| OrderBy | findByIdOrderByIdDesc | ... where x.id = 1 order by x.id desc |
| Not | findByNameNot | ... where x.name != ? |
| In | findByIdIn(Collection) | ... where x.id in (...) |
| NotIn | findByIdNotIn(Collection) | ... where x.id not in (...) |
# 注解支持
支持自定义字段名:可以在入参或字段上添加`@FieldName`注解支持自定义数据库字段名称。
支持useGeneratedKeys:可以在字段上添加`@UseGeneratedKeys`注解支持Mybatis的useGeneratedKeys功能。
使用`@ExcludeField`可以忽略insert或update方法中的指定字段。
使用`@IncludeField`可以只指定insert或update方法中的指定字段。
使用`@Selective`可以指明where查询字段是否为可选的。
# 全局配置
尽管我们可以在`@AutoMapper`注解中修改当前类的配置,但如果需要进行全局配置,可以在resource根目录下定义`auto-mapper.config`文件作为全局配置,优先级为:用户明确指定的注解配置 > 全局配置 > 默认配置。
```properties
fun.fengwk.automapper.annotation.AutoMapper.dbType=MYSQL
fun.fengwk.automapper.annotation.AutoMapper.mapperSuffix=Mapper
fun.fengwk.automapper.annotation.AutoMapper.tableNamingStyle=LOWER_UNDER_SCORE_CASE
fun.fengwk.automapper.annotation.AutoMapper.fieldNamingStyle=LOWER_UNDER_SCORE_CASE
fun.fengwk.automapper.annotation.AutoMapper.tableNamePrefix=test_
```
# 编译信息
AutoMapper在编译期会打印这以下几种常见信息:
- 映射完成写入目标xml文件。
- 用户已经编写过要映射的方法的sql片段,AutoMapper将跳过这一方法的处理。
- 用户没有编写过要映射的方法并且自动映射失败,此时会报告错误并中断编译,此时用户需要确认是否需要使用AutoMapper映射此方法,如果需要则需修正方法语法,否则可以自行在xml文件中定义相应方法对应的sql片段。
# MySQL特性
当在`@AutoMapper`注解或全局配置中指定了dbType为MYSQL时可以使用一些特有的语法:
- 使用`insertIgnore`代替`insert`可使用`insert ignore into`语法。
- 使用`replace`代替`insert`可使用`replace into`语法。
- 使用`findLockInShareMode`代替`find`可使用`select ... lock in share mode`语法。
- 使用`findForUpdate`代替`find`可使用`select ... for update`语法。
- 对于like语句,将使用concat拼接防止SQL注入。
# 应用示例
下面将会展示一些具体的示例(这些示例均来自于example模块,可以结合具体代码理解)帮助您了解如何使用规则定义接口方法。
## 示例一
方法
```java
int insert(ExampleDO exampleDO);
```
SQL片段
```xml
insert into example (name, sort) values
(#{name}, #{sort})
```
说明
其中name和sort是通过读取ExampleDO中字段获取的,并且Java字段名称会通过@AutoMapper中定义中fieldNamingStyle转换为相应的数据库字段格式,如果没有办法通过常规的规则转换,也可以通过@FieldName注解直接指定数据库字段名称,如果使用了自增主键,使用@UseGeneratedKeys注解可以对相应字段开启Mybatis的useGeneratedKeys属性并且在insert语句中忽略该字段的插入。
## 示例二
方法
```java
int deleteById(long id);
```
SQL片段
```xml
delete from example
where id=#{id}
```
说明
AutoMapper根据By后条件生成where语句,因为只有一个参数不必添加`@Param`注解来说明参数名称。
## 示例三
方法
```java
int updateById(ExampleDO exampleDO);
```
SQL片段
```xml
update example set id=#{id}, name=#{name}, sort=#{sort}
where id=#{id}
```
说明
AutoMapper通过读取入参ExampleDO类的字段生成set语句,并且根据By后条件生成where语句。
## 示例四
方法
```java
ExampleDO findById(long id);
```
SQL片段
```xml
```
说明
AutoMapper通过读取返回值ExampleDO类中的字段生成select语句,并且根据By后条件生成where语句,因为只有一个参数不必添加`@Param`注解来说明参数名称。
## 示例五
方法
```java
List findByNameStartingWith(String name);
```
SQL片段
```xml
```
说明
AutoMapper通过读取返回值泛型ExampleDO类中的字段生成select语句,根据By后条件生成where语句,因为只有一个参数不必添加`@Param`注解来说明参数名称。
## 示例六
方法
```java
List findByNameStartingWithAndSortGreaterThanEqualsOrderBySortDesc(@Param("name") String name, @Param("sort") int sort);
```
SQL片段
```xml
```
说明
AutoMapper通过读取返回值泛型ExampleDO类中的字段生成select语句,根据By后条件生成where语句,因为只有多个参数必须添加`@Param`注解来说明参数名称。
## 示例七
方法
```java
List pageAll(@Param("offset") int offset, @Param("limit") int limit);
```
SQL片段
```xml
```
说明
AutoMapper解析父类泛型E得到ExampleDO,继而读取ExampleDO字段生成select语句,由于是page方法,必须添加offset和limit参数。
## 示例八
方法
```java
@ExcludeField("name")
int insert(ExampleDO exampleDO);
```
SQL片段
```xml
insert into example (sort) values
(#{sort})
```
说明
使用`@ExcludeField`注解可以去除insert和update语句中不需要的字段。
## 示例九
方法
```java
@IncludeField("name")
int insert(ExampleDO exampleDO);
```
SQL片段
```xml
insert into example (name) values
(#{name})
```
说明
使用`@IncludeField`注解可以在insert和update语句中仅引入需要的字段。
## 示例十
方法
```java
List findByIdInAndIsDeleted(@Param("id") Collection ids, @Param("isDeleted") int isDeleted);
```
SQL片段
```xml
```
说明
注意在当前版本中@Param的value值要与对象字段和接口定义表达式中的值(这里是id)保持一致。
# 原理
AutoMapper基于JSR 269 Annotation Processing API实现,Annotation Processing API是Javac程序的一个SPI扩展点,通过编译期读取原文件信息自动生成相应代码片段,类似原理实现的框架有Lombok、Google auto......