MapStruct is a type-safe Bean mapping class that generates Java annotation processors. All we need to do is define a mapper interface that declares any necessary mapping methods. During compilation, MapStruct generates an implementation of this interface. The implementation uses mappings between source and target objects called by pure Java methods, and MapStruct saves time by generating code to complete tedious and error-prone code logic. Let’s take a look at its mystery

Objective in this chapter

MapStruct mapping framework is integrated based on SpringBoot platform.

Build the project

We use the IDEA development tool to create a SpringBoot project and add the corresponding dependencies. The POM.xml configuration file is shown as follows:

. <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> The < version > 1.5.6. RELEASE < / version > < relativePath / > <! -- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> < project. Reporting. OutputEncoding > utf-8 < / project. Reporting. OutputEncoding > < Java version > 1.8 < / Java version > < org. Mapstruct. Version > 1.2.0. CR1 < / org. Mapstruct. Version > < / properties > < dependencies > < the dependency > <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>  <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <! --<scope>provided</scope>--> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <! --> <dependency> <groupId>org. mapStruct </groupId> <artifactId> mapStruct -jdk8</artifactId> <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.31</version> </dependency> <groupId>org.projectlombok</groupId>  <artifactId>lombok</artifactId> </dependency> </dependencies> .... Omit some codeCopy the code

The MapStruct plugin provides two ways to integrate MapStruct dependencies. The MapStruct plugin provides two ways to integrate MapStruct dependencies. The MapStruct plugin provides two ways to integrate MapStruct dependencies.

. Citing official documentation... < the properties > < org. Mapstruct. Version > 1.2.0. CR1 < / org. Mapstruct. Version > < / properties >... <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-jdk8</artifactId> <version>${org.mapstruct.version}</version> </dependency> </dependencies> ... <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> The < version > 3.5.1 track of < / version > < configuration > <source> 1.8 < /source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>
...Copy the code

I personally prefer to use the first way, do not need to configure too many plug-ins, more convenient dependence. Next we start to configure the database connection information and the SpringDataJPA interface for the simple two tables.

Database Connection Information

Create a new application.yml file under resource and add the following database connection configuration:

spring:
  datasource:
    type: com. Alibaba. Druid. Pool. DruidDataSource driver - class - name: com. Mysql.. JDBC driver url: JDBC: mysql: / / 127.0.0.1:3306 /test? characterEncoding=utf8 username: root password: 123456# Maximum active number
    maxActive: 20
    # initialize the number
    initialSize: 1
    # Maximum connection wait timeout
    maxWait: 60000
    Turn on PSCache and specify the size of PSCache for each connection
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
    Enable mergeSql via the connectionProperties property; Slow SQL record
    #connectionProperties: druid.stat.mergeSql=true; druid.stat.slowSqlMillis=5000
    minIdle: 1
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: select 1 from dual
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    # Configure the filters for monitoring statistics interception. After removing the filters, the MONITORING interface SQL cannot be counted. 'wall' is used for firewall
    filters: stat, wall, log4j
  jpa:
    properties:
      hibernate:
        show_sql: true
        format_sql: trueCopy the code

For more information about SpringDataJPA, please visit chapter 3: SpringBoot. Using SpringDataJPA to complete CRUD, we create two tables in the database, namely basic commodity information table and commodity type table. The two tables are related. We simulate the use of MapStruct without connection query, and the table information is as follows:

CREATE TABLE 'good_types' (' tgt_id' int(11) NOT NULL AUTO_INCREMENT, 'tgt_name' varchar(30) DEFAULT NULL, `tgt_is_show` int(1) DEFAULT NULL, `tgt_order` int(255) DEFAULT NULL, PRIMARY KEY (`tgt_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; CREATE TABLE 'good_infos' (' tg_id' int(11) NOT NULL AUTO_INCREMENT, 'tg_type_id' int(11) DEFAULT NULL, 'tg_title' varchar(30) DEFAULT NULL, 'tg_price' decimal(8,2) DEFAULT NULL, 'tg_order' int(2) DEFAULT NULL, PRIMARY KEY (`tg_id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO `good_types` VALUES ('1'.'green'.'1'.'1');
INSERT INTO `good_infos` VALUES ('1'.'1'.'celery'.'12.40'.'1');Copy the code

Now let’s create the corresponding entity class based on these two tables.

Commodity type entity

package com.yuqiyu.chapter30.bean; import lombok.Data; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; / * * * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: Time: 2017/8/20 * and * yards cloud: http://git.oschina.net/jnyqy * ======================== */ @Entity @Table(name ="good_types")
@Data
public class GoodTypeBean
{
    @Id
    @Column(name = "tgt_id")
    private Long id;

    @Column(name = "tgt_name")
    private String name;
    @Column(name = "tgt_is_show")
    private int show;
    @Column(name = "tgt_order")
    private int order;

}Copy the code

Commodity basic information entity

package com.yuqiyu.chapter30.bean; import lombok.Data; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; / * * * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: Time: 2017/8/20 * * yards were cloud: http://git.oschina.net/jnyqy * ======================== */ @Entity @Table(name ="good_infos")
@Data
public class GoodInfoBean
{
    @Id
    @Column(name = "tg_id")
    private Long id;
    @Column(name = "tg_title")
    private String title;
    @Column(name = "tg_price")
    private double price;
    @Column(name = "tg_order")
    private int order;
    @Column(name = "tg_type_id")
    private Long typeId;
}Copy the code

Next we continue to create the associated JPA.

Product type JPA

package com.yuqiyu.chapter30.jpa; import com.yuqiyu.chapter30.bean.GoodTypeBean; import org.springframework.data.jpa.repository.JpaRepository; / * * * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: Time: 2017/8/20 * after * yards cloud: http://git.oschina.net/jnyqy * ======================== */ public interface GoodTypeJPA extends JpaRepository<GoodTypeBean,Long> { }Copy the code

Commodity information JPA

package com.yuqiyu.chapter30.jpa; import com.yuqiyu.chapter30.bean.GoodInfoBean; import org.springframework.data.jpa.repository.JpaRepository; / * * * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: Time: 2017/8/20 * and * yards cloud: http://git.oschina.net/jnyqy * ======================== */ public interface GoodInfoJPA extends JpaRepository<GoodInfoBean,Long> { }Copy the code

Configuration MapStruct

Now that we’re almost done, let’s start configuring to use MapStruct. Our final goal is to return a custom DTO entity, so we will first create the DTO, DTO code as follows:

package com.yuqiyu.chapter30.dto; import lombok.Data; / conversion Dto * * * * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: 2017/8/20 * Time: * any code: http://git.oschina.net/jnyqy * = = = = = = = = = = = = = = = = = = = = = = = = * / @ Data public class GoodInfoDTO {/ / product id private String goodId; // Private String goodName; // private double goodPrice; // Type name private StringtypeName;
}Copy the code

It can be seen that the GoodInfoDTO entity integrates the data in the two tables of commodity information and commodity type. After querying the corresponding information, we need to use MapStruct to automatically map to GoodInfoDTO.

Create Mapper

The definition of Mapper is widely used in MyBatis semi-automatic ORM framework, and Mapper here has no relation to MyBatis. Let’s take a look at the code, as follows:

package com.yuqiyu.chapter30.mapper; import com.yuqiyu.chapter30.bean.GoodInfoBean; import com.yuqiyu.chapter30.bean.GoodTypeBean; import com.yuqiyu.chapter30.dto.GoodInfoDTO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; / configure mapping * * * * = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: 2017/8/20 * Time: as often * yards cloud: http://git.oschina.net/jnyqy * ======================== */ @Mapper(componentModel ="spring")
//@Mapper
public interface GoodInfoMapper
{
    //public static GoodInfoMapper MAPPER = Mappers.getMapper(GoodInfoMapper.class);

    @Mappings({
            @Mapping(source = "type.name",target = "typeName"),
            @Mapping(source = "good.id",target = "goodId"),
            @Mapping(source = "good.title",target = "goodName"),
            @Mapping(source = "good.price",target = "goodPrice")
    })
    public GoodInfoDTO from(GoodInfoBean good, GoodTypeBean type);
}Copy the code

As you can see, GoodInfoMapper exists as an interface, but it can also be an abstract class. If you need to customize it during transformation, you can use the abstract class. The corresponding code configuration is stated in the official documentation. The @mapper annotation is used to annotate the interface, and the abstract class is automatically mapped by MapStruct. Only the existence of this annotation will automatically implement the internal interface methods. MapStruct provides us with a variety of ways to obtain Mapper, the two commonly used are respectively

The default configuration

By default, we don’t need to do much configuration, and the way to get the Mapper is by using Mappers to get the Mapper implementation class using dynamic factory internal reflection. The default Mapper is as follows:

Public static GoodInfoMapper Mapper = Mappers. GetMapper (GoodInfoMapper. Class); / / called outside GoodInfoMapper. MAPPER. The from (goodBean goodTypeBean);Copy the code
Spring Configuration

Spring way, we need to add componentModel attribute value in @Mapper annotation, after configuration, we can use @AutoWired method to inject Mapper implementation class to complete the mapping method call externally. The Mapper obtained by Spring is as follows:

// @mapper (componentModel ="spring"Autowired Private GoodInfoMapper GoodInfoMapper; / / call goodInfoMapper. The from (goodBean goodTypeBean);Copy the code
@Mappings & @Mapping

The Mapper interface definition method declares a series of @mapping and @mappings annotations. What are these two annotations used for? We use two attributes, source and target

Source represents the parameter name in the mapping interface method. If it is a parameter of basic type, the parameter name can be directly used as the source content. If it is an entity type, the entity parameter name can be used. The method of field name is used as the source content, as shown in the GoodInfoMapper content above.

Target represents the field name mapped to the method method value, as shown above in GoodInfoMapper.

View the Mapper implementation

Annotations to target/generated-sources/ Annotations to view the corresponding Mapper implementation class:

package com.yuqiyu.chapter30.mapper;

import com.yuqiyu.chapter30.bean.GoodInfoBean;
import com.yuqiyu.chapter30.bean.GoodTypeBean;
import com.yuqiyu.chapter30.dto.GoodInfoDTO;
import javax.annotation.Generated;
import org.springframework.stereotype.Component;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2017-08-20T12:52:52+0800",
    comments = Version: 1.2.0.cr1, Compiler: JavAC, Environment: Java 1.8.0_111 (Oracle Corporation)
)
@Component
public class GoodInfoMapperImpl implements GoodInfoMapper {

    @Override
    public GoodInfoDTO from(GoodInfoBean good, GoodTypeBean type) {
        if ( good == null && type == null ) {
            return null;
        }

        GoodInfoDTO goodInfoDTO = new GoodInfoDTO();

        if( good ! = null ) {if( good.getId() ! = null ) { goodInfoDTO.setGoodId( String.valueOf( good.getId() ) ); } goodInfoDTO.setGoodName( good.getTitle() ); goodInfoDTO.setGoodPrice( good.getPrice() ); }if ( type! = null ) { goodInfoDTO.setTypeName( type.getName() ); }returngoodInfoDTO; }}Copy the code

MapStruct automatically assigns the fields in the source entity to the setXxx method of the fields in the target entity according to the @mapping annotation we configured, and does all the parameter verification. We use the Spring approach to get Mapper, and MapStruct automatically adds @ComponentSpring declarative injection annotation configuration for us on the automatically generated implementation class.

Run the test

Now let’s create a test Controller, which is used to query the basic information of the product and the type of the product when accessing the specific requested address. Then call goodinfomapper. from(XXX, XXX) method to complete and return the GoodInfoDTO instance. The Controller code is implemented as follows:

package com.yuqiyu.chapter30.controller; import com.yuqiyu.chapter30.bean.GoodInfoBean; import com.yuqiyu.chapter30.bean.GoodTypeBean; import com.yuqiyu.chapter30.dto.GoodInfoDTO; import com.yuqiyu.chapter30.jpa.GoodInfoJPA; import com.yuqiyu.chapter30.jpa.GoodTypeJPA; import com.yuqiyu.chapter30.mapper.GoodInfoMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; / * * * * test controller = = = = = = = = = = = = = = = = = = = = = = = = * Created with IntelliJ IDEA. * User: heng yu young * Date: Time: 2017/8/20 * you * code: http://git.oschina.net/jnyqy * ======================== */ @RestController public class GoodInfoController { /** * Jpa */ @autoWired Private GoodInfoJPA GoodInfoJPA; @autoWired Private GoodTypeJPA GoodTypeJPA; /** * Autowired private GoodInfoMapper GoodInfoMapper; /** ** query product details * @param id * @return
     */
    @RequestMapping(value = "/detail/{id}")
    public GoodInfoDTO detail(@PathVariable("id"GoodInfoBean GoodInfoBean = goodinfojpa. findOne(id); goodinFojpa. findOne(id); // Query basic commodity type information GoodTypeBeantypeBean = goodTypeJPA.findOne(goodInfoBean.getTypeId()); // Return the conversion DTOreturn goodInfoMapper.from(goodInfoBean,typeBean); }}Copy the code

In Controller we inject GoodInfoJPA, GoodTypeJPA, and GoodInfoMapper, which maps the item details method. Then we start the project access address http://127.0.0.1:8080/detail/1 to check the interface effect on output, as shown below:

{
goodId: "1",
goodName: "Celery",
goodPrice: 12.4,
typeName: "Green vegetables"
}Copy the code

You can see that the interface outputs all the fields in GoodInfoDTO and assigns the corresponding configured target field through the FROM method.

conclusion

This chapter mainly describes the integration of MapStruct automatic Mapping framework based on SpringBoot development framework, and some fields will be automatically mapped to the fields specified by DTO entity instance through @Mapping configuration after data acquisition from simulated multiple tables. MapStruct official documentation address: mapstruct.org/documentati…

This chapter code has been uploaded to the code cloud: SpringBoot matching source address: gitee.com/hengboy/spr… SpringCloud source code address: gitee.com/hengboy/spr… SpringBoot can be found in: directory: SpringBoot Learning directory: QueryDSL General Query Framework Learning directory: SpringDataJPA SpringDataJPA Learning Directory Thanks for reading! Welcome to join QQ technical exchange group, common progress.