[JAVA] Perform bean mapping at high speed using MapStruct

http://mapstruct.org/ MapStruct is a library that aims to perform Bean Mapping easily and at high speed by automatically generating code at compile time using annotation information defined in the class as a hint using a mechanism called Annotation Processor.

This library is also introduced in Awesome Java (https://github.com/akullpp/awesome-java/blob/master/README.md#bean-mapping), but it generates static and solid code at compile time. I use this at the development site because the approach of being able to do it fits my personal use case and it is used in tools such as Swagger.

Hello World

I feel like it's a slapstick, but let's go.

By writing the following, the amount of code written by humans can be reduced as much as possible, and bean mapping can be realized at high speed (faster than reflection etc.) by the automatically generated code. Please do not worry about the contents of the class as they have no particular meaning.

//BeanA (Sada)
import lombok.Data;

@Data
public class Sada {
	String name;
	int age;
	double score;
}
//BeanB (Masashi)
import lombok.Data;

@Data
public class Masashi {
	String title;
	int age;
}
//BeanMapper
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;

@Mapper
public interface SadaMapper {
	SadaMapper INSTANCE = Mappers.getMapper(SadaMapper.class);

	@Mapping(source = "name", target = "title")
	Masashi sadaToMasashi(Sada sada);
}
//Test
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

import org.junit.Test;

public class SadaMapperTest {

	@Test
	public void test() {
		Sada sada = new Sada() {
			{
				setName("masashi");
				setAge(65);
				setScore(Double.MAX_VALUE);
			}
		};
		Masashi masashi = SadaMapper.INSTANCE.sadaToMasashi(sada);

	    assertThat(masashi.getTitle(), is(sada.getName()));
	    assertThat(masashi.getAge(), is(sada.getAge()));
	}
}

Generated Source MapStruct will automatically generate an implementation class if you describe the Interface according to the specified format. If you take a look at what kind of source is actually automatically generated based on the above code with a decompiler, the following source is generated.


public class SadaMapperImpl
  implements SadaMapper
{
  public Masashi sadaToMasashi(Sada sada)
  {
    if (sada == null) {
      return null;
    }
    Masashi masashi = new Masashi();
    
    masashi.setTitle(sada.getName());
    masashi.setAge(sada.getAge());
    
    return masashi;
  }
}

How to use MapStutct (advanced edition)

For the application of MapStruct, I recommend the following Qiita because it is very well organized.

Here, I will write contents that are not listed in the above Qiita.

Where MapStruct was introduced

Since it is a tool with an approach of automatically generating code at compile time using Annotation Processor, it requires more ingenuity in usage than a normal library.

Configuration MapStruct does not work in IDE etc. just by adding it to the dependency of pom.xml, and it is necessary to set it according to the environment. The following is described assuming a Java8 environment.

pom.xml Add the following description.

pom.xml



	<properties>
		<org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.mapstruct</groupId>
			<artifactId>mapstruct-processor</artifactId>
			<version>${org.mapstruct.version}</version>
		</dependency>
		<dependency>
			<groupId>org.mapstruct</groupId>
			<artifactId>mapstruct-jdk8</artifactId>
			<version>${org.mapstruct.version}</version>
			<scope>provided</scope>
		</dependency>
	</dependencies>

mapstruct-processor contains the processing of Annotation Processor. Classes to be referenced at the time of implementation are included in mapstruct-jdk8.

Since mapstruct is a library that doesn't seem to be completely dead yet, it behaves differently from version to version. For now (as of November 24, 2017), be sure to specify 1.2.0.Final.

Eclipse Plugin The following Plugins are recommended, so let's put them in. https://marketplace.eclipse.org/content/mapstruct-eclipse-plugin

BeanMapper Annotation description is not reflected immediately on Eclipse

I haven't found a fundamental solution, but I can work around it with the following measures. Note that there is no problem using the maven command from the command line.

Eclipse Project Clean
[Project] - [Clean]

Maven package or maven test on eclipse
[Run As] - [maven test]

For the time being, the FAQ on the official website states that if you install the plugin, it will be automatically compiled without any problems, so depending on the environment, it may work without the above measures.

http://mapstruct.org/faq/#can-i-use-mapstruct-within-eclipse

Coexistence with Lombok

It was said that it is not compatible with Lombok (https://projectlombok.org/), an approach library that automatically generates code based on annotation definitions like MapStruct.

However, since 1.2.0.Final, MapStruct has been fixed to generate Lombok code first, so if you are using the latest version, the problem will not occur.

For more information, see the FAQ below.

http://mapstruct.org/faq/#can-i-use-mapstruct-together-with-project-lombok

Project Lombok is an annotation processor that (amongst other things) adds getters and setters to the AST (abstract syntax tree) of compiled bean classes. AST modifications are not foreseen by Java annotation processing API, so quite some trickery was required within Lombok as well MapStruct to make both of them work together. Essentially, MapStruct will wait until Lombok has done all its amendments before generating mapper classes for Lombok-enhanced beans.

In addition, the following github issue also describes the detailed exchanges about the past history and the correspondence contents. https://github.com/mapstruct/mapstruct/issues/510

Coexistence with swagger-ui

Since swagger-ui also uses MapStruct, and depending on the version, the version of MapStruct referenced is out of date, it may not be possible to compile correctly in an environment where swagger-ui is included in the dependency. (At least swagger-ui will not compile correctly under 2.7.0 version)

As mentioned above, if MapStruct is used in other libraries you are using and the version is different from the one you want to use, write the exclude definition in each library.

pom.xml


		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger2</artifactId>
			<version>2.7.0</version>
			<exclusions>
				<exclusion>
					<groupId>org.mapstruct</groupId>
					<artifactId>mapstruct</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>io.springfox</groupId>
			<artifactId>springfox-swagger-ui</artifactId>
			<version>2.7.0</version>
			<exclusions>
				<exclusion>
					<groupId>org.mapstruct</groupId>
					<artifactId>mapstruct</artifactId>
				</exclusion>
			</exclusions>
		</dependency>

Recommended Posts

Perform bean mapping at high speed using MapStruct
Bean copy using MapStruct
Bean mapping with MapStruct Part 1
Bean mapping with MapStruct Part 3
Bean mapping with MapStruct Part 2
Use PHP + YoutubeDataAPIv3 at explosive speed using Docker-compose