Prestaties van Java Mapping Frameworks
1. Invoering
Om grote Java-applicaties te maken die uit meerdere lagen zijn samengesteld, moeten meerdere modellen worden gebruikt, zoals persistentiemodel, domeinmodel of zogenaamde DTO's. Door meerdere modellen voor verschillende toepassingslagen te gebruiken, moeten we een manier bieden om bonen in kaart te brengen.
Als u dit handmatig doet, kan er snel veel standaardcode worden gemaakt en kan dit veel tijd in beslag nemen. Gelukkig voor ons zijn er meerdere frameworks voor objecttoewijzing voor Java.
In deze zelfstudie gaan we de prestaties van de meest populaire Java-mappingframeworks vergelijken.
2. Kaders in kaart brengen
2.1. Bulldozer
Dozer is een mapping-framework dat recursie gebruikt om gegevens van het ene object naar het andere te kopiëren. Het framework kan niet alleen eigenschappen tussen de bonen kopiëren, maar kan ook automatisch tussen verschillende typen converteren.
Om het Dozer-framework te gebruiken, moeten we een dergelijke afhankelijkheid aan ons project toevoegen:
com.github.dozermapper dozer-core 6.5.0
Meer informatie over het gebruik van het Dozer-framework vindt u in dit artikel.
De documentatie van het framework is hier te vinden.
2.2. Orika
Orika is een bean-to-bean mapping-framework dat recursief gegevens van het ene object naar het andere kopieert.
Het algemene werkprincipe van de Orika is vergelijkbaar met die van Dozer. Het belangrijkste verschil tussen de twee is het feit dat Orika maakt gebruik van bytecode-generatie. Hierdoor kunnen snellere mappers worden gegenereerd met minimale overhead.
Om het te gebruiken,we moeten een dergelijke afhankelijkheid aan ons project toevoegen:
ma.glasnost.orika orika-core 1.5.4
Meer gedetailleerde informatie over het gebruik van de Orika vind je in dit artikel.
De feitelijke documentatie van het framework is hier te vinden.
2.3. MapStruct
MapStruct is een codegenerator die automatisch bean-mapper-klassen genereert.
MapStruct heeft ook de mogelijkheid om te converteren tussen verschillende gegevenstypen. Meer informatie over het gebruik ervan vindt u in dit artikel.
Om MapStructvoor ons project moeten we de volgende afhankelijkheid opnemen:
org.mapstruct mapstruct 1.3.1.Final
De documentatie van het framework is hier te vinden.
2.4. ModelMapper
ModelMapper is een raamwerk dat tot doel heeft objecttoewijzing te vereenvoudigen door te bepalen hoe objecten aan elkaar worden toegewezen op basis van conventies. Het biedt type-veilige en refactoring-veilige API.
Meer informatie over het framework is te vinden in de documentatie.
Om ModelMapper in ons project op te nemen, moeten we de volgende afhankelijkheid toevoegen:
org.modelmapper modelmapper 2.3.8
2.5. JMapper
JMapper is het mappingraamwerk dat tot doel heeft een gebruiksvriendelijke, hoogwaardige mapping tussen Java Beans te bieden.
Het raamwerk heeft tot doel het DRY-principe toe te passen met behulp van annotaties en relationele mapping.
Het framework maakt verschillende configuratiemogelijkheden mogelijk: op annotatie, XML of API.
Meer informatie over het framework is te vinden in de documentatie.
Om JMapper in ons project op te nemen, moeten we zijn afhankelijkheid toevoegen:
com.googlecode.jmapper-framework jmapper-core 1.6.1.CR2
3. TestenModel
Om mapping goed te kunnen testen, hebben we bron- en doelmodellen nodig. We hebben twee testmodellen gemaakt.
De eerste is gewoon een simpele POJO met één Draad veld, dit stelde ons in staat om frameworks in eenvoudigere gevallen te vergelijken en te controleren of er iets verandert als we meer gecompliceerde bonen gebruiken.
Het eenvoudige bronmodel ziet er als volgt uit:
openbare klasse SourceCode {String-code; // getter en setter}
En de bestemming is vrij gelijkaardig:
openbare klasse DestinationCode {String-code; // getter en setter}
Het real-life voorbeeld van source bean ziet er zo uit:
openbare klasse SourceOrder {privé String orderFinishDate; privé PaymentType paymentType; particuliere korting korting; privé DeliveryData deliveryData; privégebruiker bestellenGebruiker; privélijst bestelde producten; privéwinkelaanbodWinkel; privé int orderId; privé OrderStatus-status; privé LocalDate orderDate; // standaard getters en setters}
En de doelklasse ziet er als volgt uit:
openbare klasse Bestelling {privé gebruiker ordergebruiker; privélijst bestelde producten; privé OrderStatus orderStatus; privé LocalDate orderDate; privé LocalDate orderFinishDate; privé PaymentType paymentType; particuliere korting korting; privé int shopId; privé DeliveryData deliveryData; privéwinkelaanbodWinkel; // standaard getters en setters}
De hele modelstructuur is hier te vinden.
4. Omvormers
Om het ontwerp van de testopstelling te vereenvoudigen, hebben we de Converter koppel:
openbare interface Converter {Order converteren (SourceOrder sourceOrder); DestinationCode converteren (SourceCode sourceCode); }
En al onze aangepaste mappers zullen deze interface implementeren.
4.1. OrikaConverter
Orika maakt volledige API-implementatie mogelijk, dit vereenvoudigt het maken van de mapper aanzienlijk:
openbare klasse OrikaConverter implementeert Converter {private MapperFacade mapperFacade; openbare OrikaConverter () {MapperFactory mapperFactory = nieuwe 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 openbare DestinationCode converteren (SourceCode sourceCode) {return mapperFacade.map (sourceCode, DestinationCode.class); }}
4.2. DozerConverter
Dozer vereist XML-toewijzingsbestand, met de volgende secties:
com.baeldung.performancetests.model.source.SourceOrder com.baeldung.performancetests.model.destination.Order status bestelstatus com.baeldung.performancetests.model.source.SourceCode com.baeldung.performancetests.model.destination.DestinationCode
Nadat we de XML-toewijzing hebben gedefinieerd, kunnen we deze gebruiken vanuit code:
openbare klasse DozerConverter implementeert Converter {private final Mapper mapper; openbare DozerConverter () {this.mapper = DozerBeanMapperBuilder.create () .withMappingFiles ("dozer-mapping.xml") .build (); } @Override public Order convert (SourceOrder sourceOrder) {return mapper.map (sourceOrder, Order.class); } @Override openbare DestinationCode converteren (SourceCode sourceCode) {return mapper.map (sourceCode, DestinationCode.class); }}
4.3. MapStructConverter
De MapStruct-definitie is vrij eenvoudig omdat het volledig is gebaseerd op het genereren van code:
@Mapper openbare interface MapStructConverter breidt Converter uit {MapStructConverter MAPPER = Mappers.getMapper (MapStructConverter.class); @Mapping (source = "status", target = "orderStatus") @Override Order converteren (SourceOrder sourceOrder); @Override DestinationCode converteren (SourceCode sourceCode); }
4.4. JMapperConverter
JMapperConverter vereist meer werk. Na het implementeren van de interface:
openbare klasse JMapperConverter implementeert Converter {JMapper realLifeMapper; JMapper simpleMapper; openbare JMapperConverter () {JMapperAPI api = nieuwe JMapperAPI () .add (JMapperAPI.mappedClass (Order.class)); realLifeMapper = nieuwe JMapper (Order.class, SourceOrder.class, api); JMapperAPI simpleApi = nieuwe JMapperAPI () .add (JMapperAPI.mappedClass (DestinationCode.class)); simpleMapper = nieuwe JMapper (DestinationCode.class, SourceCode.class, simpleApi); } @Override public Order convert (SourceOrder sourceOrder) {return (Order) realLifeMapper.getDestination (sourceOrder); } @Override openbare DestinationCode converteren (SourceCode sourceCode) {return (DestinationCode) simpleMapper.getDestination (sourceCode); }}
We moeten ook toevoegen @JMap annotaties op elk veld van de doelklasse. JMapper kan ook niet zelfstandig tussen enum-typen converteren en vereist dat we aangepaste toewijzingsfuncties maken:
@JMapConversion (van = "paymentType", naar = "paymentType") openbare PaymentType-conversie (com.baeldung.performancetests.model.source.PaymentType-type) {PaymentType paymentType = null; schakelaar (type) {case CARD: paymentType = PaymentType.CARD; breken; geval CASH: paymentType = PaymentType.CASH; breken; geval TRANSFER: paymentType = PaymentType.TRANSFER; breken; } return paymentType; }
4.5. ModelMapperConverter
ModelMapperConverter vereist dat we alleen de klassen leveren die we in kaart willen brengen:
openbare klasse ModelMapperConverter implementeert Converter {private ModelMapper modelMapper; openbare ModelMapperConverter () {modelMapper = nieuwe ModelMapper (); } @Override public Order convert (SourceOrder sourceOrder) {return modelMapper.map (sourceOrder, Order.class); } @Override openbare DestinationCode converteren (SourceCode sourceCode) {return modelMapper.map (sourceCode, DestinationCode.class); }}
5. Eenvoudig testen van modellen
Voor de prestatietests kunnen we Java Microbenchmark Harness gebruiken, meer informatie over het gebruik ervan is te vinden in dit artikel.
We hebben voor elk een aparte benchmark gemaakt Converter met specificeren BenchmarkMode naar Mode.Alles.
5.1. Gemiddelde tijd
JMH retourneerde de volgende resultaten voor de gemiddelde looptijd (hoe minder hoe beter):
Framework naam | Gemiddelde looptijd (in ms per operatie) |
---|---|
MapStruct | 10 -5 |
JMapper | 10 -5 |
Orika | 0.001 |
ModelMapper | 0.001 |
Bulldozer | 0.002 |
Deze benchmark laat duidelijk zien dat zowel MapStruct als JMapper de beste gemiddelde werktijden hebben.
5.2. Doorvoer
In deze modus retourneert de benchmark het aantal bewerkingen per seconde. We hebben de volgende resultaten ontvangen (meer is beter) :
Framework naam | Doorvoer (in bewerkingen per ms) |
---|---|
MapStruct | 133719 |
JMapper | 106978 |
Orika | 1800 |
ModelMapper | 978 |
Bulldozer | 471 |
In doorvoermodus was MapStruct de snelste van de geteste frameworks, met JMapper een goede tweede.
5.3. SingleShotTime
Met deze modus kan de tijd van een enkele bewerking van het begin tot het einde worden gemeten. De benchmark leverde het volgende resultaat op (minder is beter):
Framework naam | Single Shot-tijd (in ms per operatie) |
---|---|
JMapper | 0.015 |
MapStruct | 0.450 |
Bulldozer | 2.094 |
Orika | 2.898 |
ModelMapper | 4.837 |
Hier zien we dat JMapper een beter resultaat oplevert dan MapStruct.
5.4. Proeftijd
In deze modus kunt u de tijd van elke bewerking bemonsteren. De resultaten voor drie verschillende percentielen zien er als volgt uit:
Monstertijd (in milliseconden per bewerking) | |||
---|---|---|---|
Framework naam | p0,90 | p0.999 | p1.0 |
JMapper | 10-4 | 0.001 | 2.6 |
MapStruct | 10-4 | 0.001 | 3 |
Orika | 0.001 | 0.010 | 4 |
ModelMapper | 0.002 | 0.015 | 3.2 |
Bulldozer | 0.003 | 0.021 | 25 |
Alle benchmarks hebben aangetoond dat MapStruct en JMapper beide goede keuzes zijn, afhankelijk van het scenario.
6. Testen van modellen in de praktijk
Voor de prestatietests kunnen we Java Microbenchmark Harness gebruiken, meer informatie over het gebruik ervan is te vinden in dit artikel.
We hebben voor elk een aparte benchmark gemaakt Converter met specificeren BenchmarkMode naar Mode.Alles.
6.1. Gemiddelde tijd
JMH retourneerde de volgende resultaten voor de gemiddelde looptijd (minder is beter):
Framework naam | Gemiddelde looptijd (in ms per operatie) |
---|---|
MapStruct | 10 -4 |
JMapper | 10 -4 |
Orika | 0.004 |
ModelMapper | 0.059 |
Bulldozer | 0.103 |
6.2. Doorvoer
In deze modus retourneert de benchmark het aantal bewerkingen per seconde. Voor elk van de mappers hebben we de volgende resultaten ontvangen (meer is beter):
Framework naam | Doorvoer (in bewerkingen per ms) |
---|---|
JMapper | 7691 |
MapStruct | 7120 |
Orika | 281 |
ModelMapper | 19 |
Bulldozer | 10 |
6.3. SingleShotTime
Met deze modus kunt u de tijd van een enkele bewerking van het begin tot het einde meten. De benchmark leverde de volgende resultaten op (minder is beter):
Framework naam | Single Shot-tijd (in ms per operatie) |
---|---|
JMapper | 0.253 |
MapStruct | 0.532 |
Bulldozer | 9.495 |
ModelMapper | 16.288 |
Orika | 18.081 |
6.4. Proeftijd
In deze modus kunt u de tijd van elke bewerking bemonsteren. Bemonsteringsresultaten worden opgesplitst in percentielen, we presenteren resultaten voor drie verschillende percentielen p0.90, p0.999, en p1.00:
Monstertijd (in milliseconden per bewerking) | |||
---|---|---|---|
Framework naam | p0,90 | p0.999 | p1.0 |
JMapper | 10-3 | 0.008 | 64 |
MapStruct | 10-3 | 0.010 | 68 |
Orika | 0.006 | 0.278 | 32 |
ModelMapper | 0.083 | 2.398 | 97 |
Bulldozer | 0.146 | 4.526 | 118 |
Hoewel de exacte resultaten van het eenvoudige voorbeeld en het praktijkvoorbeeld duidelijk verschillend waren, volgen ze min of meer dezelfde trend. In beide voorbeelden zagen we een nauwe strijd tussen JMapper en MapStruct om de eerste plaats.
6.5. Conclusie
Op basis van de real-life modeltesten die we in deze sectie hebben uitgevoerd, kunnen we zien dat de beste prestatie duidelijk bij JMapper hoort, hoewel MapStruct een goede tweede is. In dezelfde tests zien we dat Dozer consequent onderaan onze resultatentabel staat, behalve SingleShotTime.
7. Samenvatting
In dit artikel hebben we prestatietests uitgevoerd van vijf populaire Java-bean mapping-frameworks: ModelMapper, MapStruct, Orika, Dozer en JMapper.
Zoals altijd zijn codevoorbeelden te vinden op GitHub.