众所周知,在Java项目中经常会有用到各种对象属性复制的情况,以及从一个对象转换为另一个对象。
之前我们可能会使用以下工具:
BeanUtils.copyProperties(A, B);
MapStruct
对象映射今天我们来一起看看另一种功能同样强大,性能也很高的工具!就是Orika MapperFacade
。
在SpringBoot项目中,我们使用Orika MapperFacade时也是非常方便的。以下会用代码举例。
项目环境是java1.8,使用了Springboot的2.x版本。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>org.fenggroupId>
<artifactId>mapper-facade-demoartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>mapper-facade-demoname>
<description>mapper-facade-demodescription>
<properties>
<java.version>1.8java.version>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<spring-boot.version>2.7.6spring-boot.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>com.gitlab.haynesgroupId>
<artifactId>orika-spring-boot-starterartifactId>
<version>1.26.0version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>3.8.1version>
<configuration>
<source>1.8source>
<target>1.8target>
<encoding>UTF-8encoding>
configuration>
plugin>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>${spring-boot.version}version>
<configuration>
<mainClass>org.feng.MapperFacadeDemoApplicationmainClass>
<skip>trueskip>
configuration>
<executions>
<execution>
<id>repackageid>
<goals>
<goal>repackagegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
project>
因为在转换java8的时间类(诸如LocalDateTime等)时,会有格式问题,所以增加了类转换器。
package org.feng.config;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.MappingContext;
import ma.glasnost.orika.converter.BidirectionalConverter;
import ma.glasnost.orika.converter.ConverterFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.Type;
import org.feng.util.TimeUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.LocalDateTime;
/**
* mapperFacade配置
*
* @author feng
*/
@Configuration
public class MapperFacadeConfig {
@Bean
public MapperFactory mapperFactory() {
DefaultMapperFactory mapperFactory = new DefaultMapperFactory.Builder()
// 忽略映射空字段
.mapNulls(false)
.build();
ConverterFactory converterFactory = mapperFactory.getConverterFactory();
// 注册时间转换器
converterFactory.registerConverter(new LocalDateTimeStrConverter());
return mapperFactory;
}
@Bean
public MapperFacade mapperFacade(@Autowired MapperFactory mapperFactory) {
return mapperFactory.getMapperFacade();
}
private static class LocalDateTimeStrConverter extends BidirectionalConverter<LocalDateTime, String> {
@Override
public String convertTo(LocalDateTime localDateTime, Type<String> type, MappingContext mappingContext) {
return TimeUtil.parse(localDateTime);
}
@Override
public LocalDateTime convertFrom(String str, Type<LocalDateTime> type, MappingContext mappingContext) {
return TimeUtil.format(str);
}
}
}
package org.feng.util;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 时间工具类
*
* @version v1.0
* @author: fengjinsong
* @date: 2024年01月18日 22时40分
*/
public class TimeUtil {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static String parse(LocalDateTime localDateTime) {
if (localDateTime == null) {
return "";
}
return DATE_TIME_FORMATTER.format(localDateTime);
}
public static LocalDateTime format(String dateTime) {
if (dateTime == null) {
return null;
}
return LocalDateTime.parse(dateTime, DATE_TIME_FORMATTER);
}
}
为了简化文章篇幅,这里将用到的实体类放在了一起。
@Data
public class Order {
private Long id;
private String code;
private String type;
private String desc;
private LocalDateTime createDateTime;
}
@Data
public class User {
private Long id;
private String userName;
private String email;
private LocalDateTime birthday;
private LocalDate registerDay;
private List<Order> orders;
}
@Data
public class OrderVO {
private Long id;
private String code;
private String type;
private String desc;
private String createDateTime;
}
@Data
public class UserVO {
private Long id;
private String name;
private String email;
private String birthday;
private LocalDate registerDay;
private List<OrderVO> orders;
}
为了方便,直接在启动类中进行操作。在springboot项目启动后,会执行对应的转换代码。
package org.feng;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.BoundMapperFacade;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import org.feng.entity.Order;
import org.feng.entity.User;
import org.feng.vo.UserVO;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@SpringBootApplication
public class MapperFacadeDemoApplication implements CommandLineRunner {
@Resource
private MapperFactory mapperFactory;
public static void main(String[] args) {
SpringApplication.run(MapperFacadeDemoApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
// 存在不同字段名映射,需要增加映射规则;字段名完全相同时,可以直接获取mapperFacade实例进行转换
mapperFactory.classMap(User.class, UserVO.class)
.field("userName", "name")
.byDefault()
.register();
BoundMapperFacade<User, UserVO> mapperFacade = mapperFactory.getMapperFacade(User.class, UserVO.class);
User user = new User();
user.setUserName("玑而");
user.setId(2L);
user.setBirthday(LocalDateTime.now());
user.setRegisterDay(LocalDate.of(2023,12,21));
List<Order> orders = new ArrayList<>();
Order order = new Order();
order.setCreateDateTime(LocalDateTime.now());
order.setId(21L);
order.setCode("323d2");
orders.add(order);
user.setOrders(orders);
UserVO userVO = mapperFacade.map(user);
log.info(userVO.toString());
}
}
控制台输出:
UserVO(id=2, name=玑而, email=null, birthday=2024-01-31 11:47:50, registerDay=2023-12-21, orders=[OrderVO(id=21, code=323d2, type=null, desc=null, createDateTime=2024-01-31 11:47:50)])
另外需要注意一下,MapperFacade本身是使用递归的方式去做的。
也就是说如果你要转换的对象内嵌套了其他对象,也是可以复制的。