Spring Controller: use domain objects as @RequestB

2019-04-07 04:24发布

问题:

I have a domain object class User (it is a JPA entity):

@Entity
public class User {
   private String name;
   private boolean enabled = true;
   // getters/setters
}

And I am trying to offer a REST API to allow clients to create new users, using Spring 3 MVC:

@Controller
public class UserController {
    @RequestMapping(value="/user", method=RequestMethod.POST)
    @ResponseBody
    public String createRealm(@RequestBody User user) {
            user.setEnabled(true); // client is not allowed to modify this field
            userService.createUser(user);
            ...
    }
}

It works great, but I do not know if it is a good idea to use the domain objects as @RequestBody, because I have to protect some fields that should not be directly modified by the client (i.e. "enabled" in this case).

What are the pros/cons of these alternatives:

  1. Use the domain objects and protect the fields the user is not allowed to modify (for example set them to null or to its default value by hand)
  2. Use a new set of auxiliar objects (something similar to a DTO), such as a UserRequest that only contains the fields I want to expose through the REST API, and map them (i.e. with Dozer) to the domain objects.

The second alternative looks like this:

@Entity
public class User {
   private String name;
   private boolean enabled = true;
   // getters/setters
}

public class UserRequest {
   private String name;
   // enabled is removed
   // getters/setters
}

@Controller
public class UserController {
    @RequestMapping(value="/user", method=RequestMethod.POST)
    @ResponseBody
    public String createRealm(@RequestBody UserRequest userRequest) {
            User user = ... // map UserRequest -> User
            userService.createUser(user);
            ...
    }
}

Is there any other way that avoids code duplication and is easier to maintain?

回答1:

There is another option - you can disallow the submission of a given set of properties, using the DataBinder.setDisallowedFields(..) (or using .setAllowedFields(..))

@InitBinder
public void initBinder(WebDataBinder binder) {
    binder.setDisallowedFields(..);
}

This is fine if you have one or two properties that differ.

Otherwise, having a special object (like ProfileDetails or UserRequest) makes more sense. I am using such a DTO-like object for this scenario and then transfer the fields with BeanUtils.copyProperties(..) from commons-beanutils

A third, perhaps better option, is to put all profile-related fields into a separate entity (mapped with @OneToOne with user) or to an @Embeddable object, and use it instead.