前几天在代码中看到有人用unitchangeconvert.instance.dtotobo(processvo),感觉非常帅。这只是一个接口,并没有给出实现类,就给实体转换了,省去了我们自己写转换类的时间,今天摸鱼中了解了下:mapstruct 是一个专注于「java bean 之间属性映射」的代码生成器,与 spring 原生转换、mybatis-plus 类型转换器是完全不同的工具,解决的是「对象与对象之间的属性拷贝」问题(如 dto ↔ pojo、vo ↔ entity),而非参数 / 数据库字段的类型转换。
下面从 核心定位、与其他转换工具的区别、使用场景、快速上手、高级特性 五个维度,帮你理清 mapstruct 的核心价值和用法:
一、核心定位:java bean 映射的 “代码生成神器”
日常开发中,我们经常需要在不同层级的 java bean 之间拷贝属性(如:
- controller 接收前端
userdto→ 服务层转换为userentity存入数据库; - 数据库查询出
userentity→ 转换为uservo返回给前端)。
传统做法是手动写 setter 拷贝(繁琐易出错),或用 beanutils.copyproperties(反射效率低、字段名不一致需手动处理、类型转换不灵活)。
mapstruct 的核心解决思路:
- 基于「注解 + 接口」定义映射规则;
- 编译期自动生成 类型安全、无反射、高效 的映射实现类(纯 java 代码,不是运行时动态处理);
- 支持字段名映射、类型自动转换、自定义转换逻辑、依赖注入等。
二、与其他 “转换工具” 的核心区别
你之前接触的工具和 mapstruct 适用场景完全不同,用表格清晰对比:
| 工具 / 框架 | 核心用途 | 适用场景 | 底层实现 |
|---|---|---|---|
| mapstruct | java bean ↔ java bean(属性拷贝) | dto↔entity、vo↔pojo(业务层) | 编译期生成 java 代码(无反射) |
| spring 原生 converter | 基础类型 / 简单对象转换(如 string→integer) | web 参数转换、配置注入(web / 配置层) | 接口实现 + spring 容器管理 |
| mybatis-plus 转换器 | 数据库字段 ↔ java 实体(orm 层) | 数据库存储类型→java 类型 | mybatis 类型处理器接口 |
| beanutils(spring/commons) | 快速属性拷贝 | 简单场景(字段名完全一致) | 运行时反射(效率低) |
一句话区分:
- 要拷贝两个 java bean 的属性 → 用 mapstruct;
- 要把 string 转 integer / 枚举(非 bean 拷贝) → 用 spring
converter; - 要把数据库字段转 java 实体 → 用 mybatis-plus 转换器。
三、mapstruct 快速上手(spring boot 环境)
步骤 1:引入依赖(maven)
<!-- mapstruct 核心依赖 -->
<dependency>
<groupid>org.mapstruct</groupid>
<artifactid>mapstruct</artifactid>
<version>1.5.5.final</version> <!-- 推荐稳定版 -->
</dependency>
<!-- 编译期代码生成依赖(必须,否则不会生成实现类) -->
<dependency>
<groupid>org.mapstruct</groupid>
<artifactid>mapstruct-processor</artifactid>
<version>1.5.5.final</version>
<scope>provided</scope>
</dependency>
<!-- 若用 lombok,需确保 mapstruct 能识别 lombok 生成的 getter/setter -->
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok</artifactid>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<!-- lombok 与 mapstruct 兼容依赖(java 11+ 可能需要) -->
<dependency>
<groupid>org.projectlombok</groupid>
<artifactid>lombok-mapstruct-binding</artifactid>
<version>0.2.0</version>
<scope>provided</scope>
</dependency>
步骤 2:定义待映射的 java bean
假设存在「用户实体」和「用户 dto」(字段名部分不一致,类型不同):
// 数据库实体(pojo)
@data
@noargsconstructor
@allargsconstructor
public class userentity {
private long id; // 主键
private string username; // 用户名(字段名:username)
private integer age; // 年龄(int 类型)
private localdate birth; // 生日(localdate 类型)
private string phone; // 手机号
}
// 前端传输对象(dto)
@data
@noargsconstructor
@allargsconstructor
public class userdto {
private long userid; // 主键(字段名:userid,与实体 id 对应)
private string name; // 用户名(字段名:name,与实体 username 对应)
private string age; // 年龄(string 类型,与实体 int 对应)
private string birth; // 生日(string 类型,格式:yyyy-mm-dd)
private string phonenum; // 手机号(字段名:phonenum,与实体 phone 对应)
}
步骤 3:定义 mapstruct 映射接口
用 @mapper 注解(mapstruct 的注解,非 mybatis 的 @mapper!)定义映射规则,componentmodel = "spring" 表示让 spring 管理映射器实例(支持依赖注入):
import org.mapstruct.mapper;
import org.mapstruct.mapping;
import org.mapstruct.mappings;
import org.mapstruct.factory.mappers;
// 关键注解:componentmodel="spring" → spring 管理该映射器
@mapper(componentmodel = "spring", imports = {localdate.class, datetimeformatter.class})
public interface userconvert {
// 方式 1:spring 依赖注入用(无需手动创建实例)
// 方式 2:非 spring 环境用(手动获取实例)
// userconvert instance = mappers.getmapper(userconvert.class);
/**
* dto → 实体(核心映射方法)
* @mapping:指定字段映射规则(字段名不一致、类型转换、格式转换)
*/
@mappings({
// dto 的 userid → 实体的 id(字段名不一致)
@mapping(source = "userid", target = "id"),
// dto 的 name → 实体的 username(字段名不一致)
@mapping(source = "name", target = "username"),
// dto 的 age(string)→ 实体的 age(int):自动类型转换(mapstruct 内置支持)
@mapping(source = "age", target = "age"),
// dto 的 birth(string)→ 实体的 birth(localdate):自定义格式转换
@mapping(
source = "birth",
target = "birth",
dateformat = "yyyy-mm-dd" // 指定日期格式
),
// dto 的 phonenum → 实体的 phone(字段名不一致)
@mapping(source = "phonenum", target = "phone")
})
userentity dtotoentity(userdto userdto);
/**
* 实体 → dto(反向映射,字段名对应规则与正向一致)
* 可复用正向映射规则,用 @inheritinverseconfiguration 简化
*/
@inheritinverseconfiguration(name = "dtotoentity")
@mapping(
source = "birth",
target = "birth",
dateformat = "yyyy-mm-dd" // 反向转换也需指定日期格式
)
userdto entitytodto(userentity userentity);
/**
* 自定义转换逻辑(如特殊格式处理、复杂计算)
* 若 mapstruct 内置转换不满足,可定义默认方法(default)
*/
default integer stringtoage(string agestr) {
if (agestr == null || agestr.trim().isempty()) {
return 0; // 空值默认 0
}
try {
return integer.parseint(agestr.trim());
} catch (numberformatexception e) {
return 0; // 格式错误默认 0
}
}
// 反向转换:integer → string
default string agetostring(integer age) {
return age == null ? "" : string.valueof(age);
}
}
步骤 4:编译项目,查看生成的实现类
mapstruct 会在 编译期 生成 userconvertimpl 实现类(无需手动写),路径在 target/generated-sources/annotations/ 下,核心逻辑如下(自动生成的纯 java 代码):
// 自动生成的实现类(spring 管理)
@component
public class userconvertimpl implements userconvert {
@override
public userentity dtotoentity(userdto userdto) {
if (userdto == null) {
return null;
}
userentity userentity = new userentity();
userentity.setid(userdto.getuserid());
userentity.setusername(userdto.getname());
// 调用自定义的 stringtoage 方法转换类型
userentity.setage(stringtoage(userdto.getage()));
// 自动按指定格式转换 string → localdate
if (userdto.getbirth() != null) {
userentity.setbirth(localdate.parse(userdto.getbirth(), datetimeformatter.ofpattern("yyyy-mm-dd")));
}
userentity.setphone(userdto.getphonenum());
return userentity;
}
@override
public userdto entitytodto(userentity userentity) {
// 类似正向转换逻辑,自动处理字段映射和类型转换
}
// 自动生成自定义方法的调用逻辑...
}
步骤 5:在 spring 中使用映射器
通过 @autowired 注入 userconvert,直接调用映射方法:
@service
public class userservice {
@autowired
private userconvert userconvert; // 注入 mapstruct 映射器
public void adduser(userdto userdto) {
// 1. dto → 实体(自动完成字段映射和类型转换)
userentity userentity = userconvert.dtotoentity(userdto);
// 2. 存入数据库(假设用 mybatis 操作)
// usermapper.insert(userentity);
system.out.println("转换后的实体:" + userentity);
}
public userdto getuserbyid(long id) {
// 1. 从数据库查询实体
userentity userentity = new userentity(id, "张三", 25, localdate.of(2000, 1, 1), "13800138000");
// 2. 实体 → dto(反向转换)
userdto userdto = userconvert.entitytodto(userentity);
return userdto;
}
}
测试效果:
// 测试 dto → 实体 userdto dto = new userdto(1l, "张三", "25", "2000-01-01", "13800138000"); userentity entity = userconvert.dtotoentity(dto); // 输出:userentity(id=1, username=张三, age=25, birth=2000-01-01, phone=13800138000) // 测试实体 → dto userentity entity = new userentity(1l, "张三", 25, localdate.of(2000, 1, 1), "13800138000"); userdto dto = userconvert.entitytodto(entity); // 输出:userdto(userid=1, name=张三, age=25, birth=2000-01-01, phonenum=13800138000)
四、mapstruct 核心注解与高级特性
1. 核心注解(常用)
| 注解 | 作用 | 示例 |
|---|---|---|
| @mapper | 标识映射接口,指定组件模型(如 spring) | @mapper(componentmodel = "spring") |
| @mapping | 单个字段映射规则 | @mapping(source = "name", target = "username") |
| @mappings | 多个 @mapping 的集合 | 包裹多个 @mapping 注解 |
| @inheritinverseconfiguration | 继承反向映射规则(避免重复写注解) | @inheritinverseconfiguration(name = "dtotoentity") |
| @mappingtarget | 映射到已存在的对象(更新属性) | void updateentity(userdto dto, @mappingtarget userentity entity) |
| @named | 命名自定义转换方法(用于复杂映射) | @named("stringtoage") default integer stringtoage(string s) {} |
2. 高级特性
(1)集合映射(自动支持 list/set/array)
mapstruct 会自动为集合生成映射方法,无需手动定义:
@mapper(componentmodel = "spring")
public interface userconvert {
// 单个对象映射(已定义)
userentity dtotoentity(userdto dto);
userdto entitytodto(userentity entity);
// 集合映射(自动生成,无需手动写实现)
list<userentity> dtolisttoentitylist(list<userdto> dtolist);
set<userdto> entitysettodtoset(set<userentity> entityset);
}
(2)自定义转换逻辑(复杂场景)
若内置转换不满足(如枚举转换、对象嵌套),可定义 default 方法或单独的转换类:
// 示例:枚举转换
public enum genderenum {
male(1, "男"), female(2, "女");
// getter/setter/静态方法...
}
// dto 中 gender 是 string 类型("男"/"女"),实体中是 genderenum
@mapping(
source = "gender",
target = "gender",
qualifiedbyname = "genderstrtoenum" // 指定自定义转换方法
)
userentity dtotoentity(userdto dto);
// 自定义枚举转换方法(用 @named 标识)
@named("genderstrtoenum")
default genderenum genderstrtoenum(string genderstr) {
if ("男".equals(genderstr)) return genderenum.male;
if ("女".equals(genderstr)) return genderenum.female;
return null;
}
(3)依赖注入其他服务
因 componentmodel = "spring",映射器可注入 spring bean(如业务服务、工具类):
@mapper(componentmodel = "spring")
public interface userconvert {
@autowired
userservice userservice; // 注入 spring 服务
@mapping(
source = "userid",
target = "deptid",
qualifiedbyname = "getdeptidbyuserid" // 调用注入的服务
)
userentity dtotoentity(userdto dto);
@named("getdeptidbyuserid")
default long getdeptidbyuserid(long userid) {
// 调用业务服务获取部门id(复杂逻辑)
return userservice.getdeptidbyuserid(userid);
}
}
(4)日期 / 数字格式转换
通过 dateformat/numberformat 指定格式:
// 日期格式 @mapping(source = "birth", target = "birth", dateformat = "yyyy-mm-dd hh:mm:ss") // 数字格式(如保留2位小数) @mapping(source = "amount", target = "amount", numberformat = "#.00") userentity dtotoentity(userdto dto);
五、注意事项
编译期生成代码:
- 必须引入
mapstruct-processor依赖,否则不会生成实现类,运行时会报「找不到实现类」错误; - 若用 idea,需开启「annotation processing」(settings → build → compiler → annotation processors),否则 idea 可能不识别生成的类。
- 必须引入
与 lombok 兼容:
- 确保 lombok 版本 ≥ 1.18.16,mapstruct 版本 ≥ 1.4.2;
- 若字段无 getter/setter(如用
@data生成),mapstruct 无法识别字段,需确保 lombok 正确生成访问器。
字段名匹配规则:
- 默认按「字段名相同」映射(忽略大小写?不,严格匹配);
- 若字段名是
username(实体)和user_name(dto),可开启unmappedtargetpolicy = reportingpolicy.ignore忽略未映射字段,或手动指定@mapping。
总结
mapstruct 是 java bean 映射的最优解,核心优势:
- 类型安全:编译期检查字段名、类型是否匹配,避免运行时错误;
- 效率高:无反射,编译期生成原生 java 代码,性能远超
beanutils; - 灵活:支持字段映射、类型转换、自定义逻辑、依赖注入;
- 简化代码:无需手动写
setter拷贝,注解驱动开发。
到此这篇关于java中mapstruct转换实体的步骤实现的文章就介绍到这了,更多相关java mapstruct转换实体内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论