!9 release 1.4.0

Merge pull request !9 from easii/1.4.0
This commit is contained in:
easii 2024-03-16 14:27:21 +00:00 committed by Gitee
commit 932a67cbc3
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
12 changed files with 446 additions and 3 deletions

View File

@ -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' },

View File

@ -0,0 +1,3 @@
html {
height: 100%;
}

View File

@ -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)
> 微信交流群
![微信交流群](http://cos.easii.cn/wechat_20240315192612.jpg)
> vx : Clue8a796d01
![Clue8a796d01](http://cos.easii.cn/20230609091707.webp)
> 公众号:**代码笔耕**
![代码笔耕](http://cos.easii.cn/qrcode_for_gh_c207b35e04b8_344.webp)

View File

@ -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

View 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;
}
```

View File

@ -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` 注解。
这里要实现目标类到源类的自定义转换配置,可以有两种方式:

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 {};
}

View File

@ -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 {};
}