1. Introduction
1.简介
Creating large Java applications composed of multiple layers require using multiple models such as persistence model, domain model or so-called DTOs. Using multiple models for different application layers will require us to provide a way of mapping between beans.
创建由多层组成的大型Java应用程序需要使用多个模型,如持久性模型、领域模型或所谓的DTO。为不同的应用层使用多个模型将要求我们提供一种Bean之间的映射方式。
Doing this manually can quickly create much boilerplate code and consume a lot of time. Luckily for us, there are multiple object mapping frameworks for Java.
手动操作会很快产生很多模板代码,并消耗大量的时间。对我们来说,幸运的是,有多种Java的对象映射框架。
In this tutorial, we’re going to compare the performance of the most popular Java mapping frameworks.
在本教程中,我们将比较最流行的Java映射框架的性能。
2. Mapping Frameworks
2.制图框架
2.1. Dozer
2.1 推土机
Dozer is a mapping framework that uses recursion to copy data from one object to another. The framework is able not only to copy properties between the beans, but it can also automatically convert between different types.
Dozer是一个映射框架,它使用递归将数据从一个对象复制到另一个对象。 该框架不仅能够在Bean之间复制属性,而且还能在不同类型之间自动转换。
To use the Dozer framework we need to add such dependency to our project:
为了使用Dozer框架,我们需要在我们的项目中添加这样的依赖关系。
<dependency>
<groupId>com.github.dozermapper</groupId>
<artifactId>dozer-core</artifactId>
<version>6.5.2</version>
</dependency>
More information about the usage of the Dozer framework can be found in this article.
关于Dozer框架使用的更多信息可以在这篇文章中找到。
The documentation of the framework can be found here, and the latest version can be found here.
2.2. Orika
2.2.奥里卡
Orika is a bean to bean mapping framework that recursively copies data from one object to another.
Orika是一个Bean到Bean的映射框架,它将数据从一个对象递归到另一个对象。
The general principle of work of the Orika is similar to Dozer. The main difference between the two is the fact that Orika uses bytecode generation. This allows for generating faster mappers with minimal overhead.
Orika的一般工作原理与Dozer类似。两者的主要区别在于,Orika使用字节码生成。这允许以最小的开销生成更快的映射器。
To use it, we need to add such dependency to our project:
为了使用它,我们需要在我们的项目中添加这种依赖。
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.4</version>
</dependency>
More detailed information about the usage of the Orika can be found in this article.
关于Orika使用的更多详细信息,可以在此文章中找到。
The actual documentation of the framework can be found here, and the latest version can be found here.
Warning: Since Java 16, illegal reflective accesses are denied by default. The 1.5.4 version of Orika uses such reflective accesses, so Orika is currently not usable in combination with Java 16. This problem will alledgedly be solved in the future with the release of the 1.6.0 version.
警告:自从Java 16以来,非法的反射访问被默认为拒绝。Orika的1.5.4版本使用了这种反射式访问,因此Orika目前无法与Java 16结合使用。据称这个问题将在未来的1.6.0版本发布后得到解决。
2.3. MapStruct
2.3.MapStruct
MapStruct is a code generator that generates bean mapper classes automatically.
MapStruct是一个代码生成器,可以自动生成Bean mapper类。
MapStruct also has the ability to convert between different data types. More information on how to use it can be found in this article.
MapStruct还具有在不同数据类型之间转换的能力。关于如何使用它的更多信息,可以在这个文章中找到。
To add MapStruct to our project we need to include the following dependency :
为了将MapStruct添加到我们的项目中,我们需要包括以下依赖性。
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.2.Final</version>
</dependency>
The documentation of the framework can be found here, and the latest version can be found here.
2.4. ModelMapper
2.4.模型映射器
ModelMapper is a framework that aims to simplify object mapping, by determining how objects map to each other based on conventions. It provides type-safe and refactoring-safe API.
ModelMapper是一个旨在简化对象映射的框架,通过确定对象如何基于惯例相互映射。它提供了类型安全和重构安全的API。。
More information about the framework can be found in the documentation.
关于框架的更多信息可以在文档中找到。
To include ModelMapper in our project we need to add the following dependency:
为了在我们的项目中包含ModelMapper,我们需要添加以下依赖关系。
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>3.1.0</version>
</dependency>
The latest version of the framework can be found here.
该框架的最新版本可以在这里找到。
2.5. JMapper
2.5.JMapper[/strong
JMapper is the mapping framework that aims to provide an easy-to-use, high-performance mapping between Java Beans.
JMapper是一个映射框架,旨在提供一个易于使用、高性能的Java Beans之间的映射。
The framework aims to apply the DRY principle using Annotations and relational mapping.
该框架旨在使用注释和关系映射来应用DRY原则。。
The framework allows for different ways of configuration: annotation-based, XML or API-based.
该框架允许不同的配置方式:基于注释、XML或基于API。
More information about the framework can be found in its documentation.
关于该框架的更多信息可以在其文档中找到。
To include JMapper in our project we need to add its dependency:
为了在我们的项目中包括JMapper,我们需要添加它的依赖性。
<dependency>
<groupId>com.googlecode.jmapper-framework</groupId>
<artifactId>jmapper-core</artifactId>
<version>1.6.1.CR2</version>
</dependency>
The latest version of the framework can be found here.
该框架的最新版本可以在这里找到。
3. Testing Model
3.测试模型
To be able to test mapping properly we need to have source and target models. We’ve created two testing models.
为了能够正确测试映射,我们需要有源模型和目标模型。我们已经创建了两个测试模型。
First one is just a simple POJO with one String field, this allowed us to compare frameworks in simpler cases and check whether anything changes if we use more complicated beans.
第一个只是一个简单的POJO,有一个String字段,这让我们可以在更简单的情况下比较框架,并检查如果我们使用更复杂的bean是否有什么变化。
The simple source model looks like below:
简单的源模型看起来如下。
public class SourceCode {
String code;
// getter and setter
}
And its destination is quite similar:
而它的目的地也很相似。
public class DestinationCode {
String code;
// getter and setter
}
The real-life example of source bean looks like that:
现实生活中源Bean的例子看起来是这样的。
public class SourceOrder {
private String orderFinishDate;
private PaymentType paymentType;
private Discount discount;
private DeliveryData deliveryData;
private User orderingUser;
private List<Product> orderedProducts;
private Shop offeringShop;
private int orderId;
private OrderStatus status;
private LocalDate orderDate;
// standard getters and setters
}
And the target class looks like below:
而目标类看起来像下面这样。
public class Order {
private User orderingUser;
private List<Product> orderedProducts;
private OrderStatus orderStatus;
private LocalDate orderDate;
private LocalDate orderFinishDate;
private PaymentType paymentType;
private Discount discount;
private int shopId;
private DeliveryData deliveryData;
private Shop offeringShop;
// standard getters and setters
}
The whole model structure can be found here.
整个模型结构可以在这里找到。
4. Converters
4.转换器
To simplify the design of the testing setup, we’ve created the Converter interface:
为了简化测试设置的设计,我们已经创建了Converter接口。
public interface Converter {
Order convert(SourceOrder sourceOrder);
DestinationCode convert(SourceCode sourceCode);
}
And all our custom mappers will implement this interface.
而我们所有的自定义映射器将实现这个接口。
4.1. OrikaConverter
4.1.OrikaConverter
Orika allows for full API implementation, this greatly simplifies the creation of the mapper:
Orika允许完全的API实现,这大大简化了映射器的创建。
public class OrikaConverter implements Converter{
private MapperFacade mapperFacade;
public OrikaConverter() {
MapperFactory mapperFactory = new DefaultMapperFactory
.Builder().build();
mapperFactory.classMap(Order.class, SourceOrder.class)
.field("orderStatus", "status").byDefault().register();
mapperFacade = mapperFactory.getMapperFacade();
}
@Override
public Order convert(SourceOrder sourceOrder) {
return mapperFacade.map(sourceOrder, Order.class);
}
@Override
public DestinationCode convert(SourceCode sourceCode) {
return mapperFacade.map(sourceCode, DestinationCode.class);
}
}
4.2. DozerConverter
4.2.DozerConverter
Dozer requires XML mapping file, with the following sections:
Dozer需要XML映射文件,包括以下部分。
<mappings xmlns="http://dozermapper.github.io/schema/bean-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://dozermapper.github.io/schema/bean-mapping
https://dozermapper.github.io/schema/bean-mapping.xsd">
<mapping>
<class-a>com.baeldung.performancetests.model.source.SourceOrder</class-a>
<class-b>com.baeldung.performancetests.model.destination.Order</class-b>
<field>
<a>status</a>
<b>orderStatus</b>
</field>
</mapping>
<mapping>
<class-a>com.baeldung.performancetests.model.source.SourceCode</class-a>
<class-b>com.baeldung.performancetests.model.destination.DestinationCode</class-b>
</mapping>
</mappings>
After defining the XML mapping, we can use it from code:
在定义了XML映射后,我们可以从代码中使用它。
public class DozerConverter implements Converter {
private final Mapper mapper;
public DozerConverter() {
this.mapper = DozerBeanMapperBuilder.create()
.withMappingFiles("dozer-mapping.xml")
.build();
}
@Override
public Order convert(SourceOrder sourceOrder) {
return mapper.map(sourceOrder,Order.class);
}
@Override
public DestinationCode convert(SourceCode sourceCode) {
return mapper.map(sourceCode, DestinationCode.class);
}
}
4.3. MapStructConverter
4.3.MapStructConverter
MapStruct definition is quite simple as it’s entirely based on code generation:
MapStruct的定义相当简单,因为它完全基于代码生成。
@Mapper
public interface MapStructConverter extends Converter {
MapStructConverter MAPPER = Mappers.getMapper(MapStructConverter.class);
@Mapping(source = "status", target = "orderStatus")
@Override
Order convert(SourceOrder sourceOrder);
@Override
DestinationCode convert(SourceCode sourceCode);
}
4.4. JMapperConverter
4.4.JMapperConverter
JMapperConverter requires more work to do. After implementing the interface:
JMapperConverter需要做更多工作。在实现该接口后。
public class JMapperConverter implements Converter {
JMapper realLifeMapper;
JMapper simpleMapper;
public JMapperConverter() {
JMapperAPI api = new JMapperAPI()
.add(JMapperAPI.mappedClass(Order.class));
realLifeMapper = new JMapper(Order.class, SourceOrder.class, api);
JMapperAPI simpleApi = new JMapperAPI()
.add(JMapperAPI.mappedClass(DestinationCode.class));
simpleMapper = new JMapper(
DestinationCode.class, SourceCode.class, simpleApi);
}
@Override
public Order convert(SourceOrder sourceOrder) {
return (Order) realLifeMapper.getDestination(sourceOrder);
}
@Override
public DestinationCode convert(SourceCode sourceCode) {
return (DestinationCode) simpleMapper.getDestination(sourceCode);
}
}
We also need to add @JMap annotations to each field of the target class. Also, JMapper can’t convert between enum types on its own and it requires us to create custom mapping functions :
我们还需要为目标类的每个字段添加@JMap注解。另外,JMapper不能自己在枚举类型之间进行转换,它需要我们创建自定义映射函数。
@JMapConversion(from = "paymentType", to = "paymentType")
public PaymentType conversion(com.baeldung.performancetests.model.source.PaymentType type) {
PaymentType paymentType = null;
switch(type) {
case CARD:
paymentType = PaymentType.CARD;
break;
case CASH:
paymentType = PaymentType.CASH;
break;
case TRANSFER:
paymentType = PaymentType.TRANSFER;
break;
}
return paymentType;
}
4.5. ModelMapperConverter
4.5.ModelMapperConverter
ModelMapperConverter requires us to only provide the classes that we want to map:
ModelMapperConverter要求我们只提供我们想要映射的类。
public class ModelMapperConverter implements Converter {
private ModelMapper modelMapper;
public ModelMapperConverter() {
modelMapper = new ModelMapper();
}
@Override
public Order convert(SourceOrder sourceOrder) {
return modelMapper.map(sourceOrder, Order.class);
}
@Override
public DestinationCode convert(SourceCode sourceCode) {
return modelMapper.map(sourceCode, DestinationCode.class);
}
}
5. Simple Model Testing
5.简单的模型测试
For the performance testing, we can use Java Microbenchmark Harness, more information about how to use it can be found in this article.
对于性能测试,我们可以使用Java Microbenchmark Harness,关于如何使用它的更多信息可以在这个文章中找到。
We’ve created a separate benchmark for each Converter with specifying BenchmarkMode to Mode.All.
我们为每个转换器创建了一个单独的基准,指定BenchmarkMode为Mode.All。
5.1. AverageTime
5.1.平均时间
JMH returned the following results for average running time (the lesser the better) :
JMH返回的平均运行时间的结果如下(越少越好)。
Framework Name | Average running time (in ms per operation) |
---|---|
MapStruct | 10 -5 |
JMapper | 10 -5 |
Orika | 0.001 |
ModelMapper | 0.002 |
Dozer | 0.004 |
This benchmark shows clearly that both MapStruct and JMapper have the best average working times.
这个基准清楚地表明,MapStruct和JMapper都有最好的平均工作时间。
5.2. Throughput
5.2.吞吐量
In this mode, the benchmark returns the number of operations per second. We have received the following results (more is better) :
在这种模式下,基准测试返回每秒钟的操作数。我们得到了以下结果(越多越好)。
Framework Name | Throughput (in operations per ms) |
---|---|
MapStruct | 58101 |
JMapper | 53667 |
Orika | 1195 |
ModelMapper | 379 |
Dozer | 230 |
In throughput mode, MapStruct was the fastest of the tested frameworks, with JMapper a close second.
在吞吐量模式下,MapStruct是测试框架中最快的,JMapper紧随其后。
5.3. SingleShotTime
5.3.SingleShotTime
This mode allows measuring the time of single operation from it’s beginning to the end. The benchmark gave the following result (less is better):
这种模式可以测量单个操作从开始到结束的时间。该基准给出了以下结果(越少越好)。
Framework Name | Single Shot Time (in ms per operation) |
---|---|
JMapper | 0.016 |
MapStruct | 1.904 |
Dozer | 3.864 |
Orika | 6.593 |
ModelMapper | 8.788 |
Here, we see that JMapper returns better result than MapStruct.
在这里,我们看到JMapper比MapStruct返回更好的结果。
5.4. SampleTime
5.4.SampleTime
This mode allows sampling of the time of each operation. The results for three different percentiles look like below:
这种模式允许对每个操作的时间进行采样。 三个不同百分位数的结果如下。
Sample Time (in milliseconds per operation) | |||
---|---|---|---|
Framework Name | p0.90 | p0.999 | p1.0 |
JMapper | 10-4 | 0.001 | 1.526 |
MapStruct | 10-4 | 10-4 | 1.948 |
Orika | 0.001 | 0.018 | 2.327 |
ModelMapper | 0.002 | 0.044 | 3.604 |
Dozer | 0.003 | 0.088 | 5.382 |
All benchmarks have shown that MapStruct and JMapper are both good choices depending on the scenario.
所有的基准测试都表明,MapStruct和JMapper都是不错的选择,这取决于场景.。
6. Real-Life Model Testing
6.现实生活中的模型测试
For the performance testing, we can use Java Microbenchmark Harness, more information about how to use it can be found in this article.
对于性能测试,我们可以使用Java Microbenchmark Harness,关于如何使用它的更多信息可以在这个文章中找到。
We have created a separate benchmark for each Converter with specifying BenchmarkMode to Mode.All.
我们为每个转换器创建了一个单独的基准,指定BenchmarkMode为Mode.All。
6.1. AverageTime
6.1.平均时间
JMH returned the following results for average running time (less is better) :
JMH返回的平均运行时间的结果如下(越少越好)。
Framework Name | Average running time (in ms per operation) |
---|---|
MapStruct | 10 -4 |
JMapper | 10 -4 |
Orika | 0.007 |
ModelMapper | 0.137 |
Dozer | 0.145 |
6.2. Throughput
6.2.吞吐量
In this mode, the benchmark returns the number of operations per second. For each of the mappers we’ve received the following results (more is better) :
在这种模式下,基准测试返回每秒的操作数。对于每个映射器,我们得到了以下结果(越多越好)。
Framework Name | Throughput (in operations per ms) |
---|---|
JMapper | 3205 |
MapStruct | 3467 |
Orika | 121 |
ModelMapper | 7 |
Dozer | 6.342 |
6.3. SingleShotTime
6.3.SingleShotTime
This mode allows measuring the time of single operation from it’s beginning to the end. The benchmark gave the following results (less is better):
这种模式可以测量单个操作从开始到结束的时间。基准测试给出了以下结果(越少越好)。
Framework Name | Single Shot Time (in ms per operation) |
---|---|
JMapper | 0.722 |
MapStruct | 2.111 |
Dozer | 16.311 |
ModelMapper | 22.342 |
Orika | 32.473 |
6.4. SampleTime
6.4.SampleTime
This mode allows sampling of the time of each operation. Sampling results are split into percentiles, we’ll present results for three different percentiles p0.90, p0.999, and p1.00:
这种模式允许对每个操作的时间进行采样。采样结果分为百分位数,我们将介绍三个不同百分位数p0.90、p0.999、和p1.00的结果。
Sample Time (in milliseconds per operation) | |||
---|---|---|---|
Framework Name | p0.90 | p0.999 | p1.0 |
JMapper | 10-3 | 0.006 | 3 |
MapStruct | 10-3 | 0.006 | 8 |
Orika | 0.007 | 0.143 | 14 |
ModelMapper | 0.138 | 0.991 | 15 |
Dozer | 0.131 | 0.954 | 7 |
While the exact results of the simple example and the real-life example were clearly different, but they do follow more or less the same trend. In both examples, we saw a close contest between JMapper and MapStruct for the top spot.
虽然简单的例子和现实生活中的例子的具体结果明显不同,但它们确实或多或少地遵循了相同的趋势。在这两个例子中,我们看到JMapper和MapStruct在争夺榜首位置上的激烈竞争。
6.5. Conclusion
6.5.总结
Based on the real-life model testing we performed in this section, we can see that the best performance clearly belongs to JMapper, although MapStruct is a close second. In the same tests, we see that Dozer is consistently at the bottom of our results table, except for SingleShotTime.
根据我们在本节进行的真实模型测试,我们可以看到,最佳性能显然属于JMapper,尽管MapStruct紧随其后。在同样的测试中,我们看到除了SingleShotTime之外,Dozer一直处于我们结果表的底部。
7. Summary
7.总结
In this article, we’ve conducted performance tests of five popular Java bean mapping frameworks: ModelMapper, MapStruct, Orika, Dozer, and JMapper.
在这篇文章中,我们对五个流行的Java Bean映射框架进行了性能测试。ModelMapper、MapStruct、Orika、Dozer 和 JMapper。
As always, code samples can be found over on GitHub.
一如既往,代码样本可以在GitHub上找到over。