@Mapper,mapstruct与自定义注解:实体间字段映射的优雅写法

背景

实体之间进行covert操作时,如果使用函数将会出现大量的set、get方法,代码很丑陋。

查阅到两种优化方案。

lombok提供的mapping注解

参考

语法官方说明:https://mapstruct.org/documentation/stable/reference/html/#invoking-custom-mapping-method

完整引入参考:Lombok
与MapStruct实现实体映射

优雅的对象转换解决方案-MapStruct使用进阶(二)

其他用法

mapstruct遇到空值放弃填充

@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface StudentConverter {}

优雅的对象转换解决方案-MapStruct使用进阶(二)
多入参的场景

@Mapping(source = "person.description", target = "description")
@Mapping(source = "hn", target = "houseNumber")
DeliveryAddress personAndAddressToDeliveryAddressDto(Person person, Integer hn);

在多对一转换时, 遵循以下几个原则
1,当多个对象中, 有其中一个为 null, 则会直接返回 null
2,如一对一转换一样, 属性通过名字来自动匹配。 因此, 名称和类型相同的不需要进行特殊处理
3,当多个原对象中,有相同名字的属性时,需要通过 @Mapping 注解来具体的指定, 以免出现歧义(不指定会报错)。 如上面的 description

https://blog.csdn.net/qq_35211818/article/details/104714363
在这里插入图片描述

核心代码

@Mapper
public interface PersonConverter {
    PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
    
    @Mappings({
    	// 如果target与source中的字段同名,可以不用写,会自动处理
        @Mapping(source = "address", target = "address"),
        // 非同名字段需要显示来写
        @Mapping(source = "birthday", target = "birth"),
        // 日期类可以指定映射格式
        @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
        // 使用表达式进行格式处理
        @Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"),
        // 忽视该部分的转换,维持target中的原值
        @Mapping(target = "email", ignore = true),
        // 简单对象可以直接通过字段.字段获取
        @Mapping(source = "area.province", target = "province"),
        // 复杂对象mapping通过expression,获取封装在DTO对象里的list字段里的值
        @Mapping(expression = "java(companyVerifyInfoDTO.contactInfo.website.get(0).getWebsite())", target = "website"),
        // 通过表达式进行处理,如果source为null,映射为空字符串
        @Mapping(expression = "java(branchDTO.operName == null? joptsimple.internal.Strings.EMPTY : branchDTO.operName)", target = "oper_name"),
    })
    PersonDTO domain2dto(Person person);
	
	// List之间的转换建立在上述实体的转换的基础上,必须有上述domain2dto,否则无法实现List之间的转换。
    List<PersonDTO> domain2dto(List<Person> people);
}

该接口的实现类通过lombok自动生成,点击maven项目的compile即可。

使用方法如下:

DTO convertResult = PersonConverter.INSTANCE.convert(person);

自定义注解与切面

参考:https://cloud.tencent.com/developer/article/1771768

自定义注解

import java.lang.annotation.*;

@Target({ElementType.FIELD,ElementType.TYPE}) //Target 注解的使用域,FIELD表示使用在属性上面,TYPE表示使用在类上面
@Retention(RetentionPolicy.RUNTIME) //Retention 设置注解的生命周期 ,这里定义为RetentionPolicy.RUNTIME 非常关键
@Documented
public @interface RelMapper {
    //自定义属性
    String value() default ""; 
    String type() default "";  // value : status(标记属性值为Y/N的属性) / date(标记属性类型为时间) 
}

自定义属性,大家可以根据自己项目中的需求增加不同的属性。

工具类方法实现

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.Date;
import org.apache.commons.lang.StringUtils;
import com.ctccbs.common.annotation.RelMapper;

public class RelationMapperUtils {
/**
     * Entity and Dto Mapper  
     * @param entry
     * @param dto
     * @param enToDto  
     *             ture  : Entity To Dto (defult)
     *             false : Dto To Entry
     *     Rule: 
     *         实现相互转换前提: Dto field name(dto和entry的field name相同并且 类上有@RelMapper) 或 field的@RelMapper(value="Entity field name") 满足其一即可转换  
     * @return
     * @throws Exception
     */
    public static Object entryAndDtoMapper(Object entity, Object dto) throws Exception{
        return EnAndDtoMapper(entity, dto,true);
    }

    public static Object entryAndDtoMapper(Object entity, Object dto,boolean enToDto) throws Exception{
        return EnAndDtoMapper(entity, dto,false);
    }
    //last version 
    public static Object EnAndDtoMapper(Object entry, Object dto,boolean enToDto) throws Exception{
        if(enToDto == true ? entry == null : dto == null){ return null;}
        Class<? extends Object> entryclazz = entry.getClass();    //获取entity类
        Class<? extends Object> dtoclazz = dto.getClass();    //获取dto类
        boolean dtoExistAnno = dtoclazz.isAnnotationPresent(RelMapper.class);    //判断类上面是否有自定义注解
        Field [] dtofds = dtoclazz.getDeclaredFields();    //dto fields 
        Field [] entryfds = entryclazz.getDeclaredFields();    //entity fields
        Method entrys[] = entryclazz.getDeclaredMethods();    //entity methods
        Method dtos[] = dtoclazz.getDeclaredMethods();    //dto methods
        String mName,fieldName,dtoFieldType=null,entFieldType=null,dtoMapName = null,dtoFieldName =null;Object value = null;
        for(Method m : (enToDto ? dtos : entrys)) {    //当 enToDto=true 此时是Entity转为Dto,遍历dto的属性
            if((mName=m.getName()).startsWith("set")) {    //只进set方法
                fieldName = mName.toLowerCase().charAt(3) + mName.substring(4,mName.length());  //通过set方法获得dto的属性名
                tohere:
                for(Field fd: dtofds) {
                    fd.setAccessible(true);    //setAccessible是启用和禁用访问安全检查的开关
                    if(fd.isAnnotationPresent(RelMapper.class)||dtoExistAnno){    //判断field上注解或类上面注解是否存在
                        //获取与Entity属性相匹配的映射值(两种情况:1.该field上注解的value值(Entity的field name 和Dto 的field name 不同)  2.该field本身(本身则是Entity的field name 和Dto 的field name 相同))
                        dtoMapName = fd.isAnnotationPresent(RelMapper.class) ? (fd.getAnnotation(RelMapper.class).value().toString().equals("")?fd.getName().toString():fd.getAnnotation(RelMapper.class).value().toString()):fd.getName().toString();
                        if(((enToDto ? fd.getName() : dtoMapName)).toString().equals(fieldName)) { 
                            dtoFieldType = fd.getGenericType().toString().substring(fd.getGenericType().toString().lastIndexOf(".") + 1); // 获取dto属性的类型(如 private String field 结果 = String)
                            for(Field fe : entryfds) {
                                fe.setAccessible(true);
                                if(fe.getName().toString().equals(enToDto ? dtoMapName : fieldName) ) {//遍历Entity类的属性与dto属性注解中的value值匹配
                                    entFieldType = fe.getGenericType().toString().substring(fe.getGenericType().toString().lastIndexOf(".") + 1); //获取Entity类属性类型
                                    dtoFieldName = enToDto ? dtoMapName : fd.getName().toString();
                                    break tohere;
                                }
                            }
                        }
                    }
                }
                if(dtoFieldName!= null && !dtoFieldName.equals("null")) {
                    for(Method md : (enToDto ? entrys : dtos)) {
                        if(md.getName().toUpperCase().equals("GET"+dtoFieldName.toUpperCase())){
                            dtoFieldName = null; 
                            if(md.invoke(enToDto ? entry : dto) == null) { break;} //去空操作
                            //Entity类field 与Dto类field类型不一致通过TypeProcessor处理转换
                            value = (entFieldType.equals(dtoFieldType))? md.invoke(enToDto ? entry : dto) :TypeProcessor(entFieldType, dtoFieldType,md.invoke(enToDto ? entry : dto),enToDto ? true : false);
                            m.invoke(enToDto ? dto : entry, value); //得到field的值 通过invoke()赋值给要转换类的对应属性
                            value = null;
                            break;
                        }
                    }
                }
            }
        }
        return enToDto ? dto : entry;
    }

    //类型转换处理
    public static Object TypeProcessor(String entFieldType,String dtoFieldType, Object obj,boolean enToDto) {
        if(entFieldType.equals(dtoFieldType)) return obj;

        if(!entFieldType.equals(dtoFieldType)) {
            switch(entFieldType) {
                case "Date":
                    return (enToDto)?TypeConverter.dateToString((Date) obj):TypeConverter.stringToDate(obj.toString());
                case "Timestamp":
                    return TypeConverter.timestampToTimestampString((Timestamp)obj);
                case "Integer":
                    return (enToDto) ? obj.toString() : Integer.parseInt((String)obj) ;
            }
        }
        return obj;
    }

上面EnAndDtoMapper()方法的实现是Entity和Dto之间互相转换结合在一起,enToDto = true 表示的是Entity转Dto实现,false则相反。

使用

1)Entity类 与 Dto类对应
在这里插入图片描述

2)调用

public static void main(String[] args) {
        //Entity数据转成Dto数据集
        Person person = dao.getPersonRecord();
        RelationMapperUtils.entryAndDtoMapper(person,new PersonDto());
        //Dto数据转成Entity数据
        RelationMapperUtils.entryAndDtoMapper(new Person(),personDto,false);
    }
### 回答1: mybatis是一个用于Java应用程序的持久层框架。它提供了一种将应用程序与数据库连接的方式,并将数据库操作封装在映射语句中。 mybatis的mapper是一种使用XML或注解的方式来配置映射语句的方式。你可以在mapper中写入查询、插入、更新或删除数据的SQL语句,然后通过mybatis的API在应用程序中调用这些语句。 下面是一个使用XML配置映射语句的示例: ``` <mapper namespace="org.mybatis.example.BlogMapper"> <select id="selectBlog" resultType="Blog"> SELECT * FROM BLOG WHERE ID = #{id} </select> </mapper> ``` 这段代码定义了一个名为"selectBlog"的映射语句,它将返回一个名为"Blog"的对象。在应用程序中,你可以使用mybatis的API调用这个映射语句,如下所示: ``` BlogMapper mapper = sqlSession.getMapper(BlogMapper.class); Blog blog = mapper.selectBlog(1); ``` 你也可以使用注解的方式来配置映射语句,如下所示: ``` @Mapper public interface BlogMapper { @Select("SELECT * FROM BLOG WHERE ID = #{id}") Blog selectBlog(int id); } ``` 在应用程序中,你可以通过注入mapper的方式来使用这个接口: ``` @Autowired private BlogMapper mapper; Blog blog = mapper.selectBlog(1); ``` 希望这些信息对你有帮助。如果你有任何关于mybatis的疑问,请随 ### 回答2: Mybatis 是一款开源的持久层框架,可以方便地与关系型数据库进行交互。在使用 Mybatis 进行开发时,我们需要通过编写 Mapper 文件来定义 SQL 语句和映射关系。 Mybatis 的 Mapper 文件通常以 XML 格式编写,主要包含以下内容: 1. Namespace 命名空:用于唯一标识该 Mapper 文件,一般以 Mapper 接口的完全限定名命名。 2. SQL 语句定义:通过 `<sql>` 标签定义 SQL 片段,可以在不同的 SQL 语句中重复使用。例如,可以将查询条件、插入语句等常用语句定义为 SQL 片段。 3. ResultMap 结果映射:通过 `<resultMap>` 标签定义实体类与数据库表之映射关系。可以通过 `<result>` 标签指定数据库字段实体类属性的对应关系。 4. Select 语句:通过 `<select>` 标签定义查询语句。其中可以使用 `<include>` 标签引用 SQL 片段,还可以通过 `<if>` 标签实现动态条件查询。 5. Insert、Update、Delete 语句:通过 `<insert>`、`<update>`、`<delete>` 标签定义插入、更新、删除语句。 6. 参数传递:可以通过 `<parameterType>` 标签指定参数类型,也可以通过 `#{}` 占位符接收传入的参数。 使用 Mybatis 的 Mapper 写法,可以将 SQL 语句与 Java 代码分离,使得代码更加清晰、易于维护。此外,Mybatis 还提供了注解方式的 Mapper 写法,可以使用注解直接在 Mapper 接口上定义 SQL 语句,更加简洁方便。 总而言之,Mybatis 的 Mapper 写法包括定义命名空、定义 SQL 语句、定义结果映射、定义各种 CRUD 操作等。通过合理地使用这些标签,我们能够更方便地编写和管理数据库操作语句,提高开发效率。 ### 回答3: Mybatis是一个Java的持久层框架,它提供了一种通过配置文件或注解的方式来实现对象与关系数据库之映射Mapper是Mybatis中重要的组成部分,它用于定义CRUD操作以及其他与数据库交互相关的方法。 在Mybatis中,我们可以使用XML文件或注解来编写Mapper。下面是使用XML文件编写Mapper的示例: 1. 首先,创建一个XML文件,命名为"UserMapper.xml"。在该文件中,定义了Mapper的命名空以及方法的映射关系: ```xml <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.UserMapper"> <select id="getUserById" parameterType="int" resultType="com.example.User"> SELECT * FROM user WHERE id = #{id} </select> <insert id="addUser" parameterType="com.example.User"> INSERT INTO user (id, name, age) VALUES (#{id}, #{name}, #{age}) </insert> <update id="updateUser" parameterType="com.example.User"> UPDATE user SET name = #{name}, age = #{age} WHERE id = #{id} </update> <delete id="deleteUserById" parameterType="int"> DELETE FROM user WHERE id = #{id} </delete> </mapper> ``` 2. 在Java中创建一个接口,命名为"UserMapper",用于定义Mapper接口中的方法: ```java package com.example; public interface UserMapper { User getUserById(int id); void addUser(User user); void updateUser(User user); void deleteUserById(int id); } ``` 3. 编写一个类,实现UserMapper接口。在该类中使用Mybatis的SqlSessionFactory和SqlSession来执行SQL语句: ```java package com.example; public class UserMapperImpl implements UserMapper { private SqlSessionFactory sqlSessionFactory; public UserMapperImpl(SqlSessionFactory sqlSessionFactory) { this.sqlSessionFactory = sqlSessionFactory; } @Override public User getUserById(int id) { SqlSession sqlSession = sqlSessionFactory.openSession(); User user = sqlSession.selectOne("com.example.UserMapper.getUserById", id); sqlSession.close(); return user; } @Override public void addUser(User user) { SqlSession sqlSession = sqlSessionFactory.openSession(); sqlSession.insert("com.example.UserMapper.addUser", user); sqlSession.commit(); sqlSession.close(); } @Override public void updateUser(User user) { SqlSession sqlSession = sqlSessionFactory.openSession(); sqlSession.update("com.example.UserMapper.updateUser", user); sqlSession.commit(); sqlSession.close(); } @Override public void deleteUserById(int id) { SqlSession sqlSession = sqlSessionFactory.openSession(); sqlSession.delete("com.example.UserMapper.deleteUserById", id); sqlSession.commit(); sqlSession.close(); } } ``` 通过以上步骤,我们就成功编写了一个使用Mybatis的Mapper。XML文件中定义了SQL语句,Java接口定义了Mapper接口方法,而实现类中用于执行SQL语句和数据库交互。这样,我们就可以通过调用Mapper接口来实现对数据库的增删改查操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值