mirror of
https://gitee.com/easii/mapstruct-plus.git
synced 2025-12-07 17:48:35 +08:00
commit
932a67cbc3
@ -15,6 +15,7 @@ export const series = {
|
||||
{ text: 'Map to object', link: '/en/guide/map-to-class' },
|
||||
{ text: 'Conversion for enum', link: '/en/guide/enum-convert' },
|
||||
{ text: 'Class converted with multiple class', link: '/en/guide/multiple-class-convert' },
|
||||
{ text: 'Cycle Avoiding', link: '/en/guide/cycle-avoiding' },
|
||||
{ text: 'Api', link: '/en/guide/converter-api' },
|
||||
{ text: 'Configuration', link: '/en/guide/configuration' },
|
||||
{ text: 'Faq', link: '/en/guide/faq' },
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
@ -120,3 +120,18 @@ implementation group: 'io.github.linpeilie', name: 'mapstruct-plus-spring-boot-s
|
||||
- [RuoYi-Vue-Plus](https://gitee.com/JavaLionLi/RuoYi-Vue-Plus/wikis/pages)
|
||||
- [RuoYi-Cloud-Plus](https://gitee.com/JavaLionLi/RuoYi-Cloud-Plus/wikis/pages)
|
||||
|
||||
## 联系我
|
||||
|
||||
> 个人网站:[代码笔耕](https://easii.gitee.io)
|
||||
|
||||
> 微信交流群
|
||||
|
||||

|
||||
|
||||
> vx : Clue8a796d01
|
||||
|
||||

|
||||
|
||||
> 公众号:**代码笔耕**
|
||||
|
||||

|
||||
@ -146,6 +146,10 @@ public class QuickStartTest {
|
||||
}
|
||||
```
|
||||
|
||||
When there are multiple methods in a custom type converter, you can also specify the concrete conversion method with `@AutoMapping` 's `qualifiedByName`.
|
||||
You can refer to [specify conversion methods](#specify-conversion-methods), sections.
|
||||
|
||||
|
||||
## Custom Property conversions
|
||||
|
||||
When there are inconsistent scenarios for attributes in the two classes, such as name, type, and so on,
|
||||
@ -344,6 +348,113 @@ public class User {
|
||||
}
|
||||
```
|
||||
|
||||
### specify conversion methods
|
||||
|
||||
::: info
|
||||
since 1.4.0
|
||||
|
||||
Note that this feature needs to be used in conjunction with `@AutoMapper` 's `uses`.
|
||||
:::
|
||||
|
||||
When an attribute needs to define the transformation logic separately and is complex, you can implement the method first,
|
||||
specifying it by qualifiedByName.
|
||||
|
||||
For example:
|
||||
|
||||
Need film distribution, need according to the different language, change to the corresponding language title.
|
||||
There are two conversion methods, one is to convert English to French, and the other is to convert French to English:
|
||||
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Named("TitleTranslator")
|
||||
public class Titles {
|
||||
|
||||
@Named("EnglishToFrench")
|
||||
public String translateTitleEF(String title) {
|
||||
if ("One Hundred Years of Solitude".equals(title)) {
|
||||
return "Cent ans de solitude";
|
||||
}
|
||||
return "Inconnu et inconnu";
|
||||
}
|
||||
|
||||
@Named("FrenchToEnglish")
|
||||
public String translateTitleFE(String title) {
|
||||
if ("Cent ans de solitude".equals(title)) {
|
||||
return "One Hundred Years of Solitude";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The conversion logic is then applied:
|
||||
|
||||
:::: code-group
|
||||
::: code-group-item EnglishRelease
|
||||
```java
|
||||
@Data
|
||||
@AutoMapper(target = FrenchRelease.class, uses = Titles.class)
|
||||
public class EnglishRelease {
|
||||
|
||||
@AutoMapping(qualifiedByName = "EnglishToFrench")
|
||||
private String title;
|
||||
|
||||
}
|
||||
```
|
||||
:::
|
||||
::: code-group-item FrenchRelease
|
||||
```java
|
||||
@Data
|
||||
@AutoMapper(target = EnglishRelease.class, uses = Titles.class)
|
||||
public class FrenchRelease {
|
||||
|
||||
@AutoMapping(qualifiedByName = "FrenchToEnglish")
|
||||
private String title;
|
||||
|
||||
}
|
||||
```
|
||||
:::
|
||||
::::
|
||||
|
||||
### Specifies a dependency between fields
|
||||
|
||||
When the transformation logic for attribute A, which depends on attribute B, can specify that attribute a depends on B,
|
||||
the transformation will first transform B and then transform A.
|
||||
|
||||
Example:
|
||||
|
||||
:::: code-group
|
||||
::: code-group-item DependsSource
|
||||
```java
|
||||
@Data
|
||||
@AutoMapper(target = DependsTarget.class)
|
||||
public class DependsSource {
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
@AutoMapping(dependsOn = {"firstName", "lastName"})
|
||||
private String fullName;
|
||||
|
||||
}
|
||||
```
|
||||
:::
|
||||
::: code-group-item DependsTarget
|
||||
```java
|
||||
@Data
|
||||
public class DependsTarget {
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String fullName;
|
||||
|
||||
}
|
||||
```
|
||||
:::
|
||||
::::
|
||||
|
||||
|
||||
## Automatically access the custom converter interface
|
||||
|
||||
::: info
|
||||
|
||||
87
docs/en/guide/cycle-avoiding.md
Normal file
87
docs/en/guide/cycle-avoiding.md
Normal file
@ -0,0 +1,87 @@
|
||||
---
|
||||
title: cycle avoiding
|
||||
order: 7
|
||||
category:
|
||||
- Guide
|
||||
description: MapStructPlus MapStructPlus类循环嵌套 CycleAvoiding
|
||||
---
|
||||
|
||||
## Background
|
||||
|
||||
Class loop nesting is when two classes reference each other.
|
||||
For example, both the source and target object structures contain bidirectional associations between parent and child objects.
|
||||
When this occurs, direct conversion results in a stack overflow error.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
```java
|
||||
@Data
|
||||
public class TreeNode {
|
||||
private TreeNode parent;
|
||||
private List<TreeNode> children;
|
||||
}
|
||||
|
||||
@Data
|
||||
public class TreeNodeDto {
|
||||
private TreeNodeDto parent;
|
||||
private List<TreeNodeDto> children;
|
||||
}
|
||||
```
|
||||
|
||||
The `parent` attribute can be of other types, possibly spanning a nested loop formed by a longer chain of attributes.
|
||||
|
||||
To accommodate this situation, the **`AutoMapper`** annotation for MapStructPlus adds the **`cycleAvoiding`** attribute,
|
||||
which is used for identification and whether loop nesting needs to be avoided.
|
||||
The default is `false`, which needs to be set to `true` if loop nesting is to be avoided.
|
||||
|
||||
|
||||
When configured to `true`, a `CycleAvoidingMappingContext` object is passed through the conversion link of the entire object,
|
||||
temporarily saving the conversion generated object, this type is returned directly to avoid stack overflow problems.
|
||||
So, when you configure this property to `true`, there is a bit of performance cost, and if there is no loop nesting,
|
||||
use the default configuration to avoid unnecessary performance cost.
|
||||
|
||||
## Example
|
||||
|
||||
Using the example above, in the `AutoMapper` annotation, configure the `cycleAvoiding` property to be `true`, as follows:
|
||||
|
||||
```java
|
||||
@Data
|
||||
@AutoMapper(target = TreeNodeDto.class, cycleAvoiding = true)
|
||||
public class TreeNode {
|
||||
private TreeNode parent;
|
||||
private List<TreeNode> children;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AutoMapper(target = TreeNode.class, cycleAvoiding = true)
|
||||
public class TreeNodeDto {
|
||||
private TreeNodeDto parent;
|
||||
private List<TreeNodeDto> children;
|
||||
}
|
||||
```
|
||||
|
||||
The compile-generated transformation logic is as follows:
|
||||
|
||||
```java
|
||||
public TreeNodeDto convert(TreeNode arg0, CycleAvoidingMappingContext arg1) {
|
||||
TreeNodeDto target = arg1.getMappedInstance(arg0, TreeNodeDto.class);
|
||||
if (target != null) {
|
||||
return target;
|
||||
}
|
||||
|
||||
if (arg0 == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TreeNodeDto treeNodeDto = new TreeNodeDto();
|
||||
|
||||
arg1.storeMappedInstance(arg0, treeNodeDto);
|
||||
|
||||
treeNodeDto.setParent(demoConvertMapperAdapterForCycleAvoiding.iglm_TreeNodeToTreeNodeDto(arg0.getParent(), arg1));
|
||||
treeNodeDto.setChildren(
|
||||
demoConvertMapperAdapterForCycleAvoiding.iglm_TreeNodeToTreeNodeDto(arg0.getChildren(), arg1));
|
||||
|
||||
return treeNodeDto;
|
||||
}
|
||||
```
|
||||
@ -142,6 +142,11 @@ public class QuickStartTest {
|
||||
}
|
||||
```
|
||||
|
||||
当自定的类型转换器中有多个方法时,还可以通过 `@AutoMapping` 的 `qualifiedByName` 来指定具体的转换方法。
|
||||
具体可以参考 [指定转换方法](#指定转换方法) 章节。
|
||||
|
||||
###
|
||||
|
||||
## 自定义属性转换
|
||||
|
||||
当两个类中属性存在不一致的场景时,例如名称、类型等不一致,可以进行自定义转换,通过在属性上面添加 `@AutoMapping`,来配置映射规则。
|
||||
@ -334,6 +339,107 @@ public class User {
|
||||
}
|
||||
```
|
||||
|
||||
### 指定转换方法
|
||||
|
||||
::: info
|
||||
since 1.4.0
|
||||
需要注意的是,该功能需要结合 `@AutoMapper` 的 `uses` 一起使用。
|
||||
:::
|
||||
|
||||
当某个属性需要单独定义转换逻辑,并且比较复杂时,可以先实现该方法,通过 `qualifiedByName` 来指定该方法。
|
||||
|
||||
例如:
|
||||
|
||||
需要电影发行,需要根据不同的语言,修改为相应的语言标题。这里先实现两个转换方法,一个是将英语转为法语,另一个是将法语转换为英语:
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Named("TitleTranslator")
|
||||
public class Titles {
|
||||
|
||||
@Named("EnglishToFrench")
|
||||
public String translateTitleEF(String title) {
|
||||
if ("One Hundred Years of Solitude".equals(title)) {
|
||||
return "Cent ans de solitude";
|
||||
}
|
||||
return "Inconnu et inconnu";
|
||||
}
|
||||
|
||||
@Named("FrenchToEnglish")
|
||||
public String translateTitleFE(String title) {
|
||||
if ("Cent ans de solitude".equals(title)) {
|
||||
return "One Hundred Years of Solitude";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
接下来应用该转换逻辑:
|
||||
|
||||
:::: code-group
|
||||
::: code-group-item EnglishRelease
|
||||
```java
|
||||
@Data
|
||||
@AutoMapper(target = FrenchRelease.class, uses = Titles.class)
|
||||
public class EnglishRelease {
|
||||
|
||||
@AutoMapping(qualifiedByName = "EnglishToFrench")
|
||||
private String title;
|
||||
|
||||
}
|
||||
```
|
||||
:::
|
||||
::: code-group-item FrenchRelease
|
||||
```java
|
||||
@Data
|
||||
@AutoMapper(target = EnglishRelease.class, uses = Titles.class)
|
||||
public class FrenchRelease {
|
||||
|
||||
@AutoMapping(qualifiedByName = "FrenchToEnglish")
|
||||
private String title;
|
||||
|
||||
}
|
||||
```
|
||||
:::
|
||||
::::
|
||||
|
||||
### 指定属性之间的依赖关系
|
||||
|
||||
当属性 A 的转换逻辑,依赖于属性 B,可以指定 A 依赖 B,则在进行转换时,会先转换 B,然后再转换 A。
|
||||
|
||||
示例:
|
||||
|
||||
:::: code-group
|
||||
::: code-group-item DependsSource
|
||||
```java
|
||||
@Data
|
||||
@AutoMapper(target = DependsTarget.class)
|
||||
public class DependsSource {
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
@AutoMapping(dependsOn = {"firstName", "lastName"})
|
||||
private String fullName;
|
||||
|
||||
}
|
||||
```
|
||||
:::
|
||||
::: code-group-item DependsTarget
|
||||
```java
|
||||
@Data
|
||||
public class DependsTarget {
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String fullName;
|
||||
|
||||
}
|
||||
```
|
||||
:::
|
||||
::::
|
||||
|
||||
## 自动接入自定义转换接口
|
||||
|
||||
::: info
|
||||
@ -468,7 +574,7 @@ public class CarDtoToCarMapperImpl implements CarDtoToCarMapper {
|
||||
**在该文中,所有提到的源类指通过 `@AutoMapper` 注解的类;目标类指的是 `@AutoMapper` 中 `target` 属性指定的类型。**
|
||||
:::
|
||||
|
||||
前面提到,当在一个类上面添加 `@AutoMapper` 注解时,默认情况下,除了会生成源类到目标类的转换接口,还会生成目标类到源类的转换接口和实现类,这里需要注意的是,默认情况下生成的该转换接口,并没有任何自定义配置,即使在源类中配置了 `@AutoMapping` 注解。
|
||||
前面提到,当在一个类上面添加 `@AutoMapper` 注解时,默认情况下,除了会生成**源类到目标类的转换接口**,还会生成**目标类到源类的转换接口和实现类**,这里需要注意的是,**默认情况下生成的该转换接口,并没有任何自定义配置**,即使在源类中配置了 `@AutoMapping` 注解。
|
||||
|
||||
这里要实现目标类到源类的自定义转换配置,可以有两种方式:
|
||||
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
package io.github.linpeilie.model;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import io.github.linpeilie.annotations.AutoMapping;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AutoMapper(target = DependsTarget.class)
|
||||
public class DependsSource {
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
@AutoMapping(dependsOn = {"firstName", "lastName"})
|
||||
private String fullName;
|
||||
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package io.github.linpeilie.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class DependsTarget {
|
||||
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
private String fullName;
|
||||
|
||||
}
|
||||
@ -2,6 +2,7 @@ package io.github.linpeilie.model;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import io.github.linpeilie.annotations.AutoMapping;
|
||||
import io.github.linpeilie.annotations.ReverseAutoMapping;
|
||||
import io.github.linpeilie.mapper.Titles;
|
||||
import lombok.Data;
|
||||
|
||||
@ -10,6 +11,7 @@ import lombok.Data;
|
||||
public class EnglishRelease {
|
||||
|
||||
@AutoMapping(qualifiedByName = "EnglishToFrench")
|
||||
@ReverseAutoMapping(qualifiedByName = "FrenchToEnglish")
|
||||
private String title;
|
||||
|
||||
}
|
||||
|
||||
@ -6,10 +6,8 @@ import io.github.linpeilie.mapper.Titles;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@AutoMapper(target = EnglishRelease.class, uses = Titles.class)
|
||||
public class FrenchRelease {
|
||||
|
||||
@AutoMapping(qualifiedByName = "FrenchToEnglish")
|
||||
private String title;
|
||||
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import org.mapstruct.Condition;
|
||||
import org.mapstruct.Named;
|
||||
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@ -40,10 +42,54 @@ public @interface AutoMapping {
|
||||
*/
|
||||
String defaultValue() default "";
|
||||
|
||||
/**
|
||||
* String-based form of qualifiers; When looking for a suitable mapping method for a given property, MapStruct will
|
||||
* only consider those methods carrying directly or indirectly (i.e. on the class-level) a {@link Named} annotation
|
||||
* for each of the specified qualifier names.
|
||||
* <p>
|
||||
* Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
|
||||
* are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
|
||||
* number of qualifiers as no custom annotation types are needed.
|
||||
* <p>
|
||||
* Note that {@link #defaultValue()} usage will also be converted using this qualifier.
|
||||
*
|
||||
* @return One or more qualifier name(s)
|
||||
* @see Named
|
||||
* @since 1.4.0
|
||||
*/
|
||||
String[] qualifiedByName() default {};
|
||||
|
||||
/**
|
||||
* String-based form of qualifiers for condition / presence check methods;
|
||||
* When looking for a suitable presence check method for a given property, MapStruct will
|
||||
* only consider those methods carrying directly or indirectly (i.e. on the class-level) a {@link Named} annotation
|
||||
* for each of the specified qualifier names.
|
||||
*
|
||||
* This is similar like {@link #qualifiedByName()} but it is only applied for {@link Condition} methods.
|
||||
* <p>
|
||||
* Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
|
||||
* are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
|
||||
* number of qualifiers as no custom annotation types are needed.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return One or more qualifier name(s)
|
||||
* @see #qualifiedByName()
|
||||
* @see Named
|
||||
* @since 1.4.0
|
||||
*/
|
||||
String[] conditionQualifiedByName() default {};
|
||||
|
||||
/**
|
||||
* One or more properties of the result type on which the mapped property depends. The generated method
|
||||
* implementation will invoke the setters of the result type ordered so that the given dependency relationship(s)
|
||||
* are satisfied. Useful in case one property setter depends on the state of another property of the result type.
|
||||
* <p>
|
||||
* An error will be raised in case a cycle in the dependency relationships is detected.
|
||||
*
|
||||
* @return the dependencies of the mapped property
|
||||
* @since 1.4.0
|
||||
*/
|
||||
String[] dependsOn() default {};
|
||||
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@ import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import org.mapstruct.Condition;
|
||||
import org.mapstruct.Named;
|
||||
|
||||
/**
|
||||
* 由目标类生成当前类的配置
|
||||
@ -67,10 +69,54 @@ public @interface ReverseAutoMapping {
|
||||
*/
|
||||
String defaultValue() default "";
|
||||
|
||||
/**
|
||||
* String-based form of qualifiers; When looking for a suitable mapping method for a given property, MapStruct will
|
||||
* only consider those methods carrying directly or indirectly (i.e. on the class-level) a {@link Named} annotation
|
||||
* for each of the specified qualifier names.
|
||||
* <p>
|
||||
* Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
|
||||
* are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
|
||||
* number of qualifiers as no custom annotation types are needed.
|
||||
* <p>
|
||||
* Note that {@link #defaultValue()} usage will also be converted using this qualifier.
|
||||
*
|
||||
* @return One or more qualifier name(s)
|
||||
* @see Named
|
||||
* @since 1.4.0
|
||||
*/
|
||||
String[] qualifiedByName() default {};
|
||||
|
||||
/**
|
||||
* String-based form of qualifiers for condition / presence check methods;
|
||||
* When looking for a suitable presence check method for a given property, MapStruct will
|
||||
* only consider those methods carrying directly or indirectly (i.e. on the class-level) a {@link Named} annotation
|
||||
* for each of the specified qualifier names.
|
||||
*
|
||||
* This is similar like {@link #qualifiedByName()} but it is only applied for {@link Condition} methods.
|
||||
* <p>
|
||||
* Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
|
||||
* are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
|
||||
* number of qualifiers as no custom annotation types are needed.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return One or more qualifier name(s)
|
||||
* @see #qualifiedByName()
|
||||
* @see Named
|
||||
* @since 1.4.0
|
||||
*/
|
||||
String[] conditionQualifiedByName() default {};
|
||||
|
||||
/**
|
||||
* One or more properties of the result type on which the mapped property depends. The generated method
|
||||
* implementation will invoke the setters of the result type ordered so that the given dependency relationship(s)
|
||||
* are satisfied. Useful in case one property setter depends on the state of another property of the result type.
|
||||
* <p>
|
||||
* An error will be raised in case a cycle in the dependency relationships is detected.
|
||||
*
|
||||
* @return the dependencies of the mapped property
|
||||
* @since 1.4.0
|
||||
*/
|
||||
String[] dependsOn() default {};
|
||||
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user