文章目錄
一、代碼分層介紹
1.應用分層與領域模型
阿里巴巴出的的《阿里巴巴Java開發手冊》裏面制定了一個工程規約,第一條就是關於應用分層的,如下:
按這個手冊上說的,一般分爲如下幾層:
-
開放接口層
-
終端顯示層
-
Web層
-
Service層
-
Manager層
-
Dao層
-
外部接口或第三方平臺
在實際開發工作中比較常用的分層是Web層(Controller)、Service層、Dao層(Mapper)等3層。
每一層的數據對象都放在各自的領域模型對象中,按照手冊裏面的分層領域模型規約,分爲這幾種 -
DO (Data Object):與數據庫表結構一一對應,通過DAO層向上傳輸數據源對象。
-
DTO(Data Transfer Object):數據傳輸對象,Service和Manager向外傳輸的對象。
-
BO(Business Object):業務對象。可以由Service層輸出的封裝業務邏輯的對象。
-
QUERY:數據查詢對象,各層接收上層的查詢請求。注:超過2個參數的查詢封裝,禁止使用Map類來傳輸。
-
VO(View Object):顯示層對象,通常是Web向模板引擎層傳輸的對象。
除了手冊裏面的 還有PO持久化對象和POJO普通Java對象。
在實際項目開發中, 沒必要弄這麼多種區分,一般常用是VO/DTO和PO/Entity這2種。通常保證業務邏輯層Service和數據庫DAO層的操作對象嚴格劃分出來,確保互相不滲透,不混用就行。也就是說:Controller層的數據對象不要直接滲透到DAO層,同理數據表實體對象Entity/PO也不要直接傳到Controller層進行輸出或展示。
在開發中Web(Controller)層調用Service層,然後Service層再調用Dao層,上層調用下層涉及到了層與層之間領域對象的轉換。
2.爲什麼要應用分層開發和區分領域模型
- 解耦,各層的職責分明,面向接口開發、易於擴展和維護。
- 一些不需要的字段沒必要傳輸到前端
- 一些字段需要轉換後才能在前端展示或者存儲
- 一些前端展示的字段不需要保存到數據庫
3.不同的實體類間進行轉換
一般有如下幾種方式:
- 手動設值拷貝,代碼中充斥大量Set 和Get方法
- 使用BeanUtil工具的copyProperties等方法拷貝對象屬性
- 使用Dozer:一個對象轉換工具
- 使用MapStruct:是一個代碼生成器,它基於約定優於配置的方法,極大地簡化了Java bean類型之間映射的實現。
本文要介紹的是MapStruct的使用,MapStruct生成的映射代碼使用的是普通的方法調用,因此速度快、類型安全且易於理解。
二、使用MapStruct
1.官方文檔Introduction翻譯
MapStruct是一個Java註解處理器,用於生成類型安全的bean映射類。
您所要做的就是定義一個mapper接口,該接口聲明任何必需的映射方法。
在編譯過程中,MapStruct將生成此接口的實現類。這個實現類使用普通的Java方法調用來映射源對象和目標對象,即沒有使用反射或類似的的技術。
與手工編寫映射代碼相比,MapStruct通過生成冗長且容易出錯的代碼來節省時間。遵循約定優於配置的方法,MapStruct使用合理的默認值,但在配置或實現特殊行爲時,它會跳出你的方式。
與動態映射框架相比,MapStruct提供了以下優勢:
- 通過使用普通方法調用而不是反射來快速執行
- 編譯時類型安全,只有對象和屬性之間的映射可以被映射,沒有意外的映射一個訂單實體到一個客戶的DTO等。
- 在構建時清除錯誤報告,比如:映射不完整(不是所有的目標屬性都映射了) 或者 映射不正確(找不到正確的映射方法或類型轉換)
2.添加MapStruct依賴
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>1.3.1.Final</version>
</dependency>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.1.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
注意事項:
1.確保您的項目使用的是Java 1.8或更高版本
2.Maven插件版本要3.6以上。
3.如果要結合Lombok一起使用,則Lombok版本要1.16.16版本以上,並且配置改成如下:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.1.Final</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
3.定義一個映射器接口
(1).基本映射
@Mapper
public interface EmpConvert {
/**
* @param emp
* @return
*/
@Mapping(source = "empname",target = "ename")
EmpVo empToEmpVo(Emp emp);
}
- 當屬性與其目標實體對應項具有相同的名稱時,將隱式映射該屬性。
- 當屬性在目標實體中具有不同的名稱時,可以通過@Mapping註解指定其名稱。
MapperStruct根據定義的接口生成的實現類如下:
public class EmpConvertImpl implements EmpConvert {
public EmpConvertImpl() {
}
public EmpVo empToEmpVo(Emp emp) {
if (emp == null) {
return null;
} else {
EmpVo empVo = new EmpVo();
empVo.setEname(emp.getEmpname());
empVo.setEmpno(emp.getEmpno());
empVo.setJob(emp.getJob());
empVo.setMgr(emp.getMgr());
empVo.setHiredate(emp.getHiredate());
empVo.setSal(emp.getSal());
empVo.setComm(emp.getComm());
empVo.setDeptno(emp.getDeptno());
empVo.setDname(emp.getDname());
empVo.setCreateTime(emp.getCreateTime());
empVo.setUpdateTime(emp.getUpdateTime());
return empVo;
}
}
}
MapStruct的一般原理是生成儘可能多的代碼,就好像是你自己寫的一樣。特別是,這意味着通過普通的getter/setter調用而不是反射或類似的方法將值從源複製到目標。
(2).在映射器中添加默認方法
假設從A映射到B需要一些特殊的邏輯,需要特定的條件才能進行映射。一種方法是在B上實現一個自定義方法,然後由MapStruct生成的映射器調用這個自定義方法。
還有一種方法就是使用Java8的默認方法,在默認方法中手寫映射邏輯。
@Mapper
public interface EmpConvert {
/**
* @param emp
* @return
*/
@Mapping(source = "empname",target = "ename")
EmpVo empToEmpVo(Emp emp);
/**
* 使用默認方法實現特殊的映射邏輯
* @param sysuser
* @return
*/
default SysuserVo sysUserToSysuserVo(Sysuser sysuser){
}
映射器也可以用抽象類的形式定義,而不是接口的形式,並直接在映射器類中實現自定義方法。在這種情況下,MapStruct將生成抽象類的擴展,實現所有的抽象方法。與聲明默認方法相比,此方法的一個優點是可以在mapper類中聲明其他字段。
(3).具有多個源參數的映射方法
MapStruct還支持帶有多個源參數的映射方法。例如爲了將多個實體合併成一個視圖對象。
/**
* 在使用@Mapping註釋時,必須指定屬性所在的參數。
* @param emp
* @param dept
* @return
*/
@Mapping(source = "emp.empname", target = "ename")
@Mapping(source = "emp.remark",target = "remark")
@Mapping(source = "emp.deptno",target = "deptno")
@Mapping(source = "dept.dname", target = "empDeptName")
EmpVo empAndDeptToEmpVo(Emp emp, Dept dept);
上面這個方法接受兩個源參數並返回一個組合的目標對象。與單參數映射方法一樣,屬性是按名稱映射的。
如果多個源對象定義了具有相同名稱的屬性,則必須使用@Mapping註解指定用於檢索屬性的源參數,如示例中的deptno屬性所示。當這樣的歧義沒有解決時,將會引發錯誤。對於在給定源對象中只存在一次的屬性,可以選擇指定源參數的名稱,因爲它可以自動確定。
如果所有源參數都爲空,則帶有多個源參數的映射方法將返回null。否則,將實例化目標對象,並傳播所提供參數的所有屬性。
MapStruct還提供了直接引用源參數的可能性,例如,源參數不是bean類型
@Mapping(source = "emp.empname", target = "ename")
@Mapping(source = "dname", target = "empDeptName")
EmpVo fromEmp(Emp emp, String dname);
在本例中,源參數被直接映射到目標中,如上例所示。參數dname是一個非bean類型(在本例中是java.lang.String),它被映射到dname屬性字段上。
(4).更新已存在的bean實例
有時候不需要創建目標類型的新實例,而是希望更新該類型的現有實例。這種映射可以通過爲目標對象添加一個參數並使用@MappingTarget標記這個參數來實現
/**
* 更新已有的emp對象
* @param empVo
* @param emp
*/
void updateEmpFromVo(EmpVo empVo,@MappingTarget Emp emp);
(5).公共字段映射
MapStruct還支持沒有getter /setter的公共字段的映射。如果MapStruct不能找到適合屬性的getter/setter方法,它將使用字段作爲讀/寫訪問器。
如果字段是public或public final,則將其視爲讀訪問器。如果字段是靜態的,則不將其視爲讀訪問器。
只有在字段是公共的情況下,纔將其視爲寫訪問器。如果字段是final和/或靜態的,則不認爲它是寫訪問器。
4.調用映射器
1.通過映射器工廠調用
Emp emp = empService.getByEmpno("592");
EmpConvert empConvert = Mappers.getMapper(EmpConvert.class);
EmpVo empVo = empConvert.empToEmpVo(emp);
按照慣例,mapper接口應該定義一個名爲INSTANCE的成員,該成員持有mapper類型的單個實例
@Mapper
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
EmpVo empToEmpVo(Emp emp);
這種模式使客戶端很容易使用mapper對象,而無需重複實例化新實例:
EmpVo empVo = EmpConvert.INSTANCE.empToEmpVo(emp);
注意,MapStruct生成的映射器是無狀態的,並且是線程安全的,因此可以同時從多個線程安全地訪問。
2.使用依賴注入調用
如果您使用的是依賴項注入框架,比如CDI 或Spring框架,那麼建議您通過依賴項注入來獲取mapper對象,而不是通過上面描述的Mappers類
@Mapper(componentModel = "spring")
public interface EmpConvert {
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
EmpVo empToEmpVo(Emp emp);
/**
* 注入emp映射器
*/
@Resource
private EmpConvert empConvert;
public EmpVo getEmp() {
Emp emp = empService.getByEmpno("592");
return empConvert.empToEmpVo(emp);
}
5.數據類型轉換
5.1隱式類型轉換
在許多情況下,MapStruct會自動處理類型轉換。例如,如果一個屬性字段在源bean中是int類型的,而在目標bean中是String類型的,那麼生成的映射器代碼將通過分別調用字符串#valueOf(int)和整數#parseInt(String)來透明地執行轉換。
會自動進行類型轉換的有以下幾種情況:
- 所有Java原始數據類型及其對應的包裝器類型之間的關係,例如int與Integer、boolean與Boolean等。生成的代碼是空感知的,即當將包裝器類型轉換爲相應的原語類型時,將執行空檢查。
- 在所有Java基礎數字類型和包裝器類型之間,例如在int和long或byte和Integer之間。
- 所有Java基元類型(包括它們的包裝器)和字符串之間的過渡,例如在int和String之間,或者在Boolean和String之間。由java.text理解的格式字符串。可以指定DecimalFormat。
- 在枚舉類型和字符串之間。
- 在大數字類型之間(java.math。BigInteger、Java .math. bigdecimal)和Java基本類型(包括它們的包裝器)以及字符串。由java.text理解的格式字符串。可以指定DecimalFormat。
- java.time之間。LocalDateTime來自Java 8日期-時間包和Java .util.Date
- 在java.sql.Timestamp 和java.util.Date之間
- …其他的情況看官網
5.2映射對象引用
通常,一個對象不僅具有基本屬性,而且還引用其他對象。例如,Emp類可以包含一個對Dept對象的引用,這個引用應該被EmpVo類映射到一個DeptVo對象。
public class Emp {
/**
* 員工編號
*/
private Long empno;
/**
* 所在部門對象
*/
private Dept dept;
......
public class EmpVo {
/**
* 員工編號
*/
private Long empno;
/**
*/
private DeptVo deptVo;
......
emp映射器如下:
@Mapper(componentModel = "spring")
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept",target = "deptVo")
EmpVo empToEmpVo(Emp emp);
DeptVo deptToDeptVo(Dept dept);
emp映射器生成的代碼如下:
@Component
public class EmpConvertImpl implements EmpConvert {
public EmpConvertImpl() {
}
public EmpVo empToEmpVo(Emp emp) {
if (emp == null) {
return null;
} else {
EmpVo empVo = new EmpVo();
empVo.setEname(emp.getEmpname());
empVo.setDeptVo(this.deptToDeptVo(emp.getDept()));
empVo.setEmpno(emp.getEmpno());
empVo.setJob(emp.getJob());
empVo.setMgr(emp.getMgr());
empVo.setHiredate(emp.getHiredate());
empVo.setSal(emp.getSal());
empVo.setComm(emp.getComm());
empVo.setDeptno(emp.getDeptno());
empVo.setCreateTime(emp.getCreateTime());
empVo.setUpdateTime(emp.getUpdateTime());
empVo.setRemark(emp.getRemark());
return empVo;
}
}
public DeptVo deptToDeptVo(Dept dept) {
if (dept == null) {
return null;
} else {
DeptVo deptVo = new DeptVo();
deptVo.setDeptno(dept.getDeptno());
deptVo.setDname(dept.getDname());
deptVo.setLoc(dept.getLoc());
deptVo.setRemark(dept.getRemark());
return deptVo;
}
}
這樣就可以映射任意深度對象層次。
在生成映射方法的實現時,MapStruct將對源和目標對象中的每個屬性將遵從以下規則:
- 如果源和目標屬性具有相同的類型,則值將簡單地從源複製到目標。如果屬性是一個集合(例如一個列表),集合的副本將被設置到目標屬性中
- 如果源屬性類型與目標屬性類型不同,請檢查是否存在另一種映射方法,該方法將源屬性的類型作爲參數類型,將目標屬性的類型作爲返回類型。如果存在這樣一個方法,它將在生成的映射實現中被調用。
- 如果不存在這樣的方法,MapStruct將查看是否存在針對屬性的源和目標類型的內置轉換。如果是這種情況,生成的映射代碼將應用這種轉換。
- 如果沒有找到這樣的方法,MapStruct將嘗試生成一個自動的子映射方法,它將在源和目標屬性之間進行映射。爲了阻止MapStruct生成自動子映射方法,可以使用@Mapper(disableSubMappingMethodsGeneration = true)。
- 如果MapStruct不能創建基於名稱的映射方法,則在構建時將引發一個錯誤,指示不可映射的屬性及其路徑。
5.3控制嵌套bean映射
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(target = "deptVo",ignore = true)
EmpVo empToEmpVo(Emp emp);
gnore = true 設置忽略deptVo屬性
當從實體映射到視圖對象時,在某一點上減少對其他實體的引用通常是有用的。爲此,實現一個自定義映射方法,例如將引用的dept實體映射到目標對象中的empDeptName和deptno上,
public class Emp {
/**
* 員工編號
*/
private Long empno;
/**
* 所在部門對象
*/
private Dept dept;
......
public class EmpVo {
/**
* 員工編號
*/
private Long empno;
/**
* 所在部門編號
*/
private int deptno;
/**
* 部門名稱
*/
private String empDeptName;
...
映射器代碼如下:
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname",target = "empDeptName")
@Mapping(source="dept.deptno",target = "deptno")
EmpVo empToEmpVo(Emp emp);
生成的映射器實現類如下:
@Component
public class EmpConvertImpl implements EmpConvert {
public EmpConvertImpl() {
}
public EmpVo empToEmpVo(Emp emp) {
if (emp == null) {
return null;
} else {
EmpVo empVo = new EmpVo();
empVo.setEname(emp.getEmpname());
empVo.setEmpDeptName(this.empDeptDname(emp));
Integer deptno = this.empDeptDeptno(emp);
if (deptno != null) {
empVo.setDeptno(deptno);
}
empVo.setEmpno(emp.getEmpno());
empVo.setJob(emp.getJob());
empVo.setMgr(emp.getMgr());
empVo.setHiredate(emp.getHiredate());
empVo.setSal(emp.getSal());
empVo.setComm(emp.getComm());
empVo.setCreateTime(emp.getCreateTime());
empVo.setUpdateTime(emp.getUpdateTime());
empVo.setRemark(emp.getRemark());
return empVo;
}
}
private String empDeptDname(Emp emp) {
if (emp == null) {
return null;
} else {
Dept dept = emp.getDept();
if (dept == null) {
return null;
} else {
String dname = dept.getDname();
return dname == null ? null : dname;
}
}
}
private Integer empDeptDeptno(Emp emp) {
if (emp == null) {
return null;
} else {
Dept dept = emp.getDept();
if (dept == null) {
return null;
} else {
Integer deptno = dept.getDeptno();
return deptno == null ? null : deptno;
}
}
}
5.4調用其他映射器
除了在同一mapper類型上定義的方法之外,MapStruct還可以調用在其他類中定義的映射方法,無論是由MapStruct生成的映射器,還是手工編寫的映射方法。這對於在多個類中構造映射代碼(例如,每個應用程序模塊使用一個映射器類型)或者提供不能由MapStruct生成的自定義映射邏輯是很有用的。
例如,Emp類可能包含一個屬性updateTime,類型是Timestamp,而相應的Vo屬性的類型是String。爲了映射這個屬性,你可以像這樣實現一個自定義轉換類:
public class TimestampConvert {
public String timeStampToString(Timestamp updateTime){
return updateTime.toString();
}
}
在EmpConvert接口的@Mapper註釋中,引用了這樣的TimestampConvert 類:
@Mapper(uses = TimestampConvert.class)
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname",target = "empDeptName")
@Mapping(source="dept.deptno",target = "deptno")
EmpVo empToEmpVo(Emp emp);
/**
*
* @param dept
* @return
*/
DeptVo deptToDeptVo(Dept dept);
在EmpConvert 接口的實現類中,MapStruct將查找一個將Timestamp對象映射到字符串的方法,在TimestampConvert類中找到它,並生成一個timeStampToString()調用來映射updateTime屬性。
EmpConvert 接口實現類如下:
public class EmpConvertImpl implements EmpConvert {
private final TimestampConvert timestampConvert = new TimestampConvert();
@Override
public EmpVo empToEmpVo(Emp emp) {
if ( emp == null ) {
return null;
}
EmpVo empVo = new EmpVo();
empVo.setEname( emp.getEmpname() );
empVo.setEmpDeptName( empDeptDname( emp ) );
Integer deptno = empDeptDeptno( emp );
if ( deptno != null ) {
empVo.setDeptno( deptno );
}
empVo.setEmpno( emp.getEmpno() );
empVo.setJob( emp.getJob() );
empVo.setMgr( emp.getMgr() );
empVo.setHiredate( emp.getHiredate() );
empVo.setSal( emp.getSal() );
empVo.setComm( emp.getComm() );
empVo.setCreateTime( emp.getCreateTime() );
empVo.setUpdateTime( timestampConvert.timeStampToString( emp.getUpdateTime() ) );
empVo.setRemark( emp.getRemark() );
return empVo;
}
注意點:如果是採用依賴注入的方式來調用映射器,則當前映射器調用的其他映射器也要是可注入的
@Mapper(componentModel = "spring",uses = TimestampConvert.class)
public interface EmpConvert {
......
}
/**
* 自定義時間轉換器
*
* @author David Lin
* @version: 1.0
* @date 2020-04-12 17:24
*/
@Component
public class TimestampConvert {
public String timeStampToString(Timestamp updateTime){
return updateTime.toString();
}
}
5.5將映射目標類型傳遞給自定義映射器
…
5.6將上下文或狀態對象傳遞給自定義方法
…
5.7映射方法解析
在將屬性從一種類型映射到另一種類型時,MapStruct查找將源類型映射到目標類型的最特定方法。該方法可以在相同的mapper接口上聲明,也可以在另一個通過@Mapper#uses()註冊的mapper上聲明。這同樣適用於工廠方法。
5.8基於限定符的映射方法選擇
…
6.映射集合
集合類型(List、Set等)的映射與映射bean類型的映射方式相同,即在mapper接口中使用所需的源和目標類型定義映射方法。MapStruct支持來自Java集合框架的廣泛的可迭代類型。
生成的代碼將包含一個循環,該循環遍歷源集合,轉換每個元素並將其放入目標集合。如果在給定的映射器或其使用的映射器中找到集合元素類型的映射方法,則調用此方法來執行元素轉換。或者,如果存在源和目標元素類型的隱式轉換,則將調用此轉換例程
@Mapper(componentModel = "spring")
public interface EmpConvert {
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
EmpVo empToEmpVo(Emp emp);
/**
*
* @param empList
* @return
*/
List<EmpVo> empsToEmpVos(List<Emp> empList);
7.自定義映射
有時需要在某些映射方法之前或之後應用自定義邏輯。MapStruct提供了兩種方法:裝飾器,它允許對特定映射方法進行類型安全的定製;映射前和映射後的生命週期方法,它允許對具有給定源或目標類型的映射方法進行通用定製。
(1).使用裝飾器定製映射
在某些情況下,可能需要自定義一個生成的映射方法,例如,在目標對象中設置一個不能由生成的方法實現設置的附加屬性。MapStruct使用裝飾器支持這一需求。
要將裝飾器應用到映射器類,請使用@DecoratedWith註釋指定它。
裝飾器必須是裝飾的映射器類型的子類型。您可以使它成爲一個抽象類,它只允許實現您想要自定義的mapper接口的那些方法。對於所有未實現的方法,將使用默認的生成例程生成對原始映射器的簡單委託。
比如Emp轉EmpVo類型時,想自定義job字段值,如下:
@Mapper(componentModel = "spring",uses = TimestampConvert.class)
@DecoratedWith(EmpDecorator.class)
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname",target = "empDeptName")
@Mapping(source="dept.deptno",target = "deptno")
EmpVo empToEmpVo(Emp emp);
/**
*
* @param dept
* @return
*/
DeptVo deptToDeptVo(Dept dept);
/**
* emp類型轉換裝飾器
*
* @author David Lin
* @version: 1.0
* @date 2020-05-02 9:35
*/
public abstract class EmpDecorator implements EmpConvert {
@Resource
private EmpConvert delegate;
@Override
public EmpVo empToEmpVo(Emp emp) {
EmpVo empVo = delegate.empToEmpVo(emp);
empVo.setJob("當前員工的工作崗位是:"+empVo.getEname());
return empVo;
}
}
@RequestMapping("/testEmp")
public EmpVo testTime(){
Emp emp = new Emp();
emp.setEmpname("smith");
emp.setEmpno(123L);
emp.setJob("開發");
Timestamp updateTime = new Timestamp(System.currentTimeMillis());
emp.setUpdateTime(updateTime);
EmpVo empVo =empConvert.empToEmpVo(emp);
logger.info("the empVo updateTiem is {}",empVo.getUpdateTime());
return empVo;
}
(2).使用前映射和後映射方法進行定製映射
在定製映射器時,裝飾器可能並不總是適合需要。您可以使用在映射開始之前或映射完成之後調用的回調方法。
回調方法可以在抽象映射器本身中實現,也可以在mapper #uses中的類型引用中實現,或者在用作@Context參數的類型中實現。
這裏採用mapper#uses實現
**
* emp映射方法增強
*
* @author David Lin
* @version: 1.0
* @date 2020-05-02 10:54
*/
@Component
public class EmpAop {
@BeforeMapping
public void setEmpJob(Emp emp){
emp.setJob("員工工作崗位:"+emp.getJob());
}
@AfterMapping
public void setRemark(Emp emp, @MappingTarget EmpVo empVo){
empVo.setRemark(emp.getEmpname()+":"+emp.getRemark());
}
}
@Mapper(componentModel = "spring",uses = {TimestampConvert.class,EmpAop.class})
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname",target = "empDeptName")
@Mapping(source="dept.deptno",target = "deptno")
EmpVo empToEmpVo(Emp emp);
/**
*
* @param dept
* @return
*/
DeptVo deptToDeptVo(Dept dept);
如果@BeforeMapping / @AfterMapping方法有參數,那麼只有當方法的返回類型(如果非void)可以賦值給映射方法的返回類型,並且所有參數都可以由映射方法的源或目標參數賦值時,纔會生成方法調用 。
- 使用@MappingTarget註釋的參數由映射的目標實例填充。
- 使用@TargetType註釋的參數將使用映射的目標類型填充。
- 使用@Context註釋的參數由映射方法的上下文參數填充。
- 任何其他參數都由映射的源參數填充。
對於非空方法,如果映射方法不爲空,則方法調用的返回值作爲映射方法的結果返回。
與映射方法一樣,可以爲前/後映射方法指定類型參數。
8.複用映射配置
…
9.高級映射選項
這裏描述了幾個高級選項,這些選項允許根據需要對生成的映射代碼的行爲進行微調。
1.默認值和常量
如果相應的源屬性爲空,可以指定默認值將預定義值設置爲目標屬性。在任何情況下都可以指定常量來設置這樣的預定義值。默認值和常量被指定爲字符串值。當目標類型是原語類型或已裝箱類型時,將獲取字符串值的文字值。位/八進制/十進制/十六進制模式在這種情況下是允許的,只要它們是有效的文字。在所有其他情況下,常量或默認值都要通過內置轉換或調用其他映射方法進行類型轉換
具有常量的映射不能包含對源屬性的引用
@Mapper(componentModel = "spring",uses = {TimestampConvert.class,EmpAop.class})
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname",target = "empDeptName",defaultValue = "開發部門")
@Mapping(source="dept.deptno",target = "deptno")
@Mapping(target = "updateTime",dateFormat = "yyyy-MM-dd",constant ="2020-05-02")
EmpVo empToEmpVo(Emp emp);
生成的代碼如下:
if (this.empDeptDname(emp) != null) {
empVo.setEmpDeptName(this.empDeptDname(emp));
} else {
empVo.setEmpDeptName("開發部門");
}
empVo.setUpdateTime("2020-05-02");
2.表達式
…
3.默認表達式
默認表達式是默認值和表達式的組合。它們只在源屬性爲空時使用。
同樣的警告和限制也適用於默認表達式。只支持Java,而MapStruct不會在生成時驗證表達式。
@Mapper(componentModel = "spring", imports = PlatStringUtil.class)
public interface EmpConvert {
EmpConvert INSTANCE = Mappers.getMapper(EmpConvert.class);
/**
* @param emp
* @return
*/
@Mapping(source = "empname", target = "ename")
@Mapping(source = "dept.dname", target = "empDeptName", defaultValue = "開發部門")
@Mapping(source = "dept.deptno", target = "deptno")
@Mapping(target = "updateTime", dateFormat = "yyyy-MM-dd", constant = "2020-05-02")
@Mapping(source = "remark", target = "remark", defaultExpression = "java(PlatStringUtil.randomUUID())")
EmpVo empToEmpVo(Emp emp);
上面這個例子演示瞭如何使用defaultExpression來設置一個remark字段(如果源字段爲null),如果源對象設置了現有的remark,則可以使用它來獲取它,如果沒有設置,則創建一個新ID。請注意,指定了完全限定的包名,因爲MapStruct不負責PlatStringUtil類的導入(除非在EmpConvert 中顯式地使用它)。可以通過在@Mapper註釋上定義導入來解決這個問題
4.確定結果類型
當結果類型具有繼承關係時,選擇映射方法(@Mapping)或工廠方法(@BeanMapping)可能會變得模糊。
5.控制“空”參數的映射結果
當映射方法的源參數等於null時,MapStruct提供對要創建的對象的控制。默認情況下將返回null。
但是,通過指定nullValueMappingStrategy = nullValueMappingStrategy。在@BeanMapping、@IterableMapping、@MapMapping或@Mapper或@MappingConfig上使用RETURN_DEFAULT,映射結果可以更改爲返回空的默認值。
幾種類型null值返回如下:
- Bean mappings::一個“空”的目標bean將被返回,除了常量和表達式之外,它們將在出現時被填充。
- Iterables / Arrays:將返回一個空的iterable。
- Maps:將返回一個空map。
優先級:
在映射方法級別上設置nullValueMappingStrategy屬性優先於@Mappe上設置, ,而@Mapper#nullValueMappingStrategy優先於@MappingConfig#nullValueMappingStrategy。
6.控制bean映射中“空”屬性的映射結果(僅更新映射方法)
當源屬性等於null或狀態檢查方法導致“缺席”時,MapStruct提供了對@MappingTarget帶註釋的目標bean中設置的屬性的控制。
默認情況下,目標屬性將設置爲null。
- 通過在@Mapping、@BeanMapping、@Mapper或@MappingConfig註解上,指定nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT,映射結果可以更改爲返回默認值。對於List 集合對象,MapStruct生成一個ArrayList,對於Map集合對象,生成一個HashMap,對於數組,生成一個空數組,對於字符串生成一個“”,對於基本類型/基本類型的包裝類型,生成一個false或0的表示。對於所有其他對象,將創建一個新實例。請注意,需要一個默認構造函數。如果不可用,則使用@Mapping#defaultValue。
- 通過在 @Mapping, @BeanMapping, @Mapper 或者@MappingConfig註解上指定nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,映射結果將等於帶註釋的@MappingTarget的原始值。
7.控制bean映射中“空”屬性的檢查結果
MapStruct提供了何時生成空檢查的控制,默認情況下(nullValueCheckStrategy = NullValueCheckStrategy.ON_IMPLICIT_CONVERSION))會生成一個空檢查:
…
當設置nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS時,如果源不是基本類型,則始終包含一個空檢查,除非在源bean上定義了源狀態檢查器。
8.源存在檢查
一些框架生成具有源狀態檢查器的bean屬性。通常這是以hasXYZ方法的形式出現的,XYZ是bean映射方法中源bean上的一個屬性。當MapStruct發現hasXYZ方法時,它將調用這個hasXYZ,而不是執行空檢查。
9.異常
在調用映射方法時,調用應用程序可能需要處理異常。這些異常可以通過手工編寫的邏輯和生成的內置映射方法或MapStruct的類型轉換來拋出。當調用應用程序需要處理異常時,可以在映射方法中定義一個拋出子句: