DTO pattern: Best way to copy properties between two objects
Asked Answered
F

7

51

In my application's architecture I usually send the object or list of objects from the data access layer to the web layer via the service layer, in which these objects get transformed from a DAO object to a DTO object and vice versa. The web layer don't have any access to DAO objects and the DAO layer do not use DTOs.

To demonstrate, I usually write the code as:

@Transactional(readOnly = true)
public List<UserDTO> getAllUserAsUserDTO() {
    List<UserDTO> userDTOs = new ArrayList<UserDTO>();

    for(User user : getAllUser()) {
        userDTOs.add(constructUserDTO(user));
    }

    return userDTOs;
}

private UserDTO constructUserDTO(User user) {
    UserDTO userDTO = new UserDTO();
    userDTO.setFullName(user.getFullName());
    userDTO.setId(user.getId());
    userDTO.setUsername(user.getUsername());
    userDTO.setRole(user.getRole());
    userDTO.setActive(user.isActive());
    userDTO.setActiveText(user.isActive() ? "Active" : "Inactive");
    return userDTO;
}

Here the user is the database entity:

@javax.persistence.Entity
@Table(name = "USER")
public class User extends Entity {

    @Transient
    private static final long serialVersionUID = -112950002831333869L;

    private String username;
    private String fullName;
    private boolean active;
    private String role;
    // other fields

    public User() {
        super();
    }

    @NaturalId
    @Column(name = "USERNAME", nullable = false)
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Column(name = "FULL_NAME")
    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    @Column(name = "ACTIVE", nullable = false)
    public boolean isActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;
    }

    @Column(name = "ROLE")
    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }
}

And this is the UserDTO:

public class UserDTO extends BaseDTO {

    private static final long serialVersionUID = -3719463614753533782L;

    private String username;
    private String fullName;
    private String role;
    private String activeText;
    private Boolean active;
    //other properties

    public UserDTO() {
        super();
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public String getActiveText() {
        return activeText;
    }

    public void setActiveText(String activeText) {
        this.activeText = activeText;
    }

    public Boolean getActive() {
        return active;
    }

    public void setActive(Boolean active) {
        this.active = active;
    }
}

So I was wondering if this is the only way to copy properties between two objects. I guess I am not sure. Also I am using lambdaj, so is there a method in this API by which I can copy all these properties to create list of other objects?

This topic may be sounds subjective, but I really want to know from you experts the ways by which the transformation of object from one form to another can be done where the maximum fields are having same string.

Fatback answered 27/2, 2013 at 16:39 Comment(1)
Possible duplicate of any tool for java object to object mapping?Kellyekellyn
S
22

You can have a look at dozer which is a

Java Bean to Java Bean mapper that recursively copies data from one object to another. Typically, these Java Beans will be of different complex types.

Another better link...

Swellhead answered 27/2, 2013 at 16:45 Comment(1)
From their GitHub: The project is currently not active and will more than likely be deprecated in the future. If you are looking to use Dozer on a greenfield project, we would discourage that.Atiana
P
29

You can use Apache Commmons Beanutils. The API is

org.apache.commons.beanutils.PropertyUtilsBean.copyProperties(Object dest, Object orig).

It copies property values from the "origin" bean to the "destination" bean for all cases where the property names are the same.

Now I am going to off topic. Using DTO is mostly considered an anti-pattern in EJB3. If your DTO and your domain objects are very alike, there is really no need to duplicate codes. DTO still has merits, especially for saving network bandwidth when remote access is involved. I do not have details about your application architecture, but if the layers you talked about are logical layers and does not cross network, I do not see the need for DTO.

Prefect answered 2/3, 2013 at 5:2 Comment(3)
True, this is an anti-pattern in EJB land. But in the new smart client world (i.e. client side MVC) it is quickly becoming a necessity. You do not want to pull your entire object graph to the client side but only the stuff you really need there. Hence a DTO.Binky
This is a good suggestion. But what if the DTO has only 4 properties and the actual object has 50 properties. I my case, when I use the copyProperties as mentioned here, it overwrites by actual object with only properties. The rest of the 46 properties become null. Is this how it is expected to behave ?Bestialize
is this deep copy?Coimbatore
S
22

You can have a look at dozer which is a

Java Bean to Java Bean mapper that recursively copies data from one object to another. Typically, these Java Beans will be of different complex types.

Another better link...

Swellhead answered 27/2, 2013 at 16:45 Comment(1)
From their GitHub: The project is currently not active and will more than likely be deprecated in the future. If you are looking to use Dozer on a greenfield project, we would discourage that.Atiana
P
7

I had an application that I needed to convert from a JPA entity to DTO, and I thought about it and finally came up using org.springframework.beans.BeanUtils.copyProperties for copying simple properties and also extending and using org.springframework.binding.convert.service.DefaultConversionService for converting complex properties.

In detail my service was something like this:

@Service("seedingConverterService")
public class SeedingConverterService extends DefaultConversionService implements ISeedingConverterService  {
    @PostConstruct
    public void init(){
        Converter<Feature,FeatureDTO> featureConverter = new Converter<Feature, FeatureDTO>() {

            @Override
            public FeatureDTO convert(Feature f) {
                FeatureDTO dto = new FeatureDTO();
                //BeanUtils.copyProperties(f, dto,"configurationModel");
                BeanUtils.copyProperties(f, dto);
                dto.setConfigurationModelId(f.getConfigurationModel()==null?null:f.getConfigurationModel().getId());
                return dto;
            }
        };

        Converter<ConfigurationModel,ConfigurationModelDTO> configurationModelConverter = new Converter<ConfigurationModel,ConfigurationModelDTO>() {
            @Override
            public ConfigurationModelDTO convert(ConfigurationModel c) {
                ConfigurationModelDTO dto = new ConfigurationModelDTO();
                //BeanUtils.copyProperties(c, dto, "features");
                BeanUtils.copyProperties(c, dto);
                dto.setAlgorithmId(c.getAlgorithm()==null?null:c.getAlgorithm().getId());
                List<FeatureDTO> l = c.getFeatures().stream().map(f->featureConverter.convert(f)).collect(Collectors.toList());
                dto.setFeatures(l);
                return dto;
            }
        };
        addConverter(featureConverter);
        addConverter(configurationModelConverter);
    }
}
Pinchhit answered 25/2, 2016 at 2:48 Comment(0)
S
4

I suggest you should use one of the mappers' libraries: Mapstruct, ModelMapper, etc. With Mapstruct your mapper will look like:

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

    UserDTO toDto(User user);
}

The real object with all getters and setters will be automatically generated from this interface. You can use it like:

UserDTO userDTO = UserMapper.INSTANCE.toDto(user);

You can also add some logic for your activeText filed using @AfterMapping annotation.

Sclera answered 20/6, 2018 at 16:11 Comment(0)
M
2

You can use reflection to find all the get methods in your DAO objects and call the equivalent set method in the DTO. This will only work if all such methods exist. It should be easy to find example code for this.

Moneywort answered 27/2, 2013 at 16:42 Comment(0)
R
2

Wouldn't lambdaj's project function do what you are looking for?

It'll look something like this:

List<UserDTO> userNDtos = project(users, UserDTO.class, on(User.class).getUserName(), on(User.class).getFullName(), .....);

(Define the constructor for UserDTO accordingly...)

Also see here for examples...

Rosariarosario answered 12/3, 2013 at 14:23 Comment(0)
T
0

The simplest trick is SERIALIZE the Object_A and DESERIALIZE it using Object_B.class:

if you are using Spring or Spring-boot probably you already have Jackson library in your classpath:

import com.fasterxml.jackson.databind.ObjectMapper;

then:

ObjectMapper objectMapper = new ObjectMapper();
Object_B objB = objectMapper.readValue(objectMapper.writeValueAsString(objA), Object_B.class);
Tailpipe answered 9/8, 2022 at 14:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.