I am currently working in a project where I have a User model and am using a REST API to fetch a list of users. (I have more entities.)
User has a password field. I do not want to include the password field in the result. So I excluded it in the DTO. But when I want to create a User, I want to include the password in the request. So Spring MVC gets the User entity (not the DTO).
I don't think it is good to do so.... For example, I have Event model which is connected to user with a many-to-many relationship. I don't want that in the request. I want only the user. So what do you suggest me to do? Have another kind-of DTO?
Use #JsonIgnore with Access.WRITE_ONLY for getter methods only.
Example
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
If you are using Jackson to serialize your response objects, you can annotate the property in question with #JsonIgnore and it will not be included in the response.
public User {
private String email;
#JsonIgnore
private String password
...getters and setters
}
It might also be a good idea to create separate response objects that only include the fields you want in case you add sensitive fields down the road and forget to hide them. Likewise, you would also have separate request objects for creating users that would include a password field. Business entities, like a User, are probably best to use only internally, so you can control what information goes public.
To avoid using #JsonIgnore, you can use json-view library.
For example, in your controller you can do something like this:
At first, declare this in your controller variable:
private JsonResult json = JsonResult.instance();
And then use this method:
#RequestMapping("/get/{id}")
public void getUserById(#PathVariable(value = "id") long id) {
User user = usersService.findOne(id);
json.use(JsonView.with(user)
.onClass(User.class, Match.match()
.exclude("password").exclude("yetAnothertopSecretField")));
}
It returns JSON without excluded fields.
The JsonView and JsonResult classes are imported from the json-view library.
I'm tried this JsonProperty.Access.WRITE_ONLY and it's working with me.
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
Make the field 'password' as null while sending the response and Jackson will not show that in response. Don't remove it completely from the model class.
Related
I have entities that represent my database - User, Recipe and Tag.
For data manipulation I use DTO. So UserDTO, RecipeDTO, TagDTO. When I define a relationship between entities, I use its basic User, Recipe, Tag form, but when I define these relationships in a DTO class, I use its DTO form.
For example:
DTO Class looks like this
public class UserDTO{
private String name;
private String email
private List<RecipeDTO>
}
public class RecipeDTO{
private String title;
private String description;
private UserDTO user;
}
I know how to map a DTO to an entity so that I can perform operations (CRUD) on the data in the database.
private Recipe convertToEntity(RecipeDTO recipeDTO){
Recipe recipe = new Recipe();
recipe.setTitle(recipeDTO.getTitle);
recipe.setDescription(recipeDTO.getDescription);
}
But the RecipeDTO also has a UserDTO in it, which I also need to map to an entity. How do I do this?
So I am trying to achieve a mapping inside the mapping .... (??)
I can think of the following solution.
Create method that converts UserDTO to User:
private User convertUser(UserDTO userDTO){
User user = new User();
user.setName(userDTO.getName());
user.setEmail(userDTO.getEmail());
}
And then use it while mapping RecipeDTO to Recipe.
private Recipe convertToEntity(RecipeDTO recipeDTO){
Recipe recipe = new Recipe();
recipe.setTitle(recipeDTO.getTitle());
recipe.setDescription(recipeDTO.getDescription());
//Convert UserDTO
recipe.setUser(convertUser(recipeDTO.getUser()));
}
I'm not sure if this is the right solution, as there will be more and more mappings as the code gets bigger.
The approach you described is not wrong and will work, but doing it that way will indeed involve a lot of hard work.
The way this is usually done in the industry is by letting a library do that work for you.
The two most popular mapping libraries for java are:
https://mapstruct.org/ (which uses annotation processing at compile time and auto-generates basically the same mapping code as in your example)
and
http://modelmapper.org/ (which uses black magic and reflection)
They are both easy to setup/learn and either will do the job (including mapping nested objects as in your example), so take a look at the “getting started“ section and pick the one you find more intuitive to use.
My personal recommendation would be to pick Mapstruct, as it has way fewer gotchas, generates clean human-readable code and avoids using reflection.
Let's consider we have two entities. User entity can have many offers and an Offer must have one User.
class User {
...
#OneToMany(mappedBy = "user", orphanRemoval = true)
private Set<Offer> offers;
}
class Offer {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fk_user")
private User user;
}
At this moment there are two controllers. UserController and the OrderController. The UserController is mapped under /api/v1/users/ and the OrderController is mapped under /api/v1/orders/.
What should an endpoint look like that fetches the user's offer list?
Should it be in the same controller? I do have by functionality project structure.
How to modify or delete an Offer for a particular User? In case we would have /api/v1/users/{username}/offers/{offerId} to delete or update an offer, should we have also /api/v1/offers/{offerId} endpoint that allows to edit or remove an offer? Perhaps it is worth having it for an administrator?
General rules of thumb that I use when creating my endpoints are:
URLs need to be clean and easy to understand
They should be as short as possible while still informative.
Try to build it in a such manner that it allows you to reuse it within the reasonable amount
Think about the user experience(whether this is called from browser or mobile app etc.)
I am not sure if there is a written rule exactly how one should build an URL though.
In your specific case I would use /users/{username}/offers/{offerId} only if this is the only place you are exposing and using offers, since you are separating your code by functionality.
If you have any further logic around offers and/or have beans that have such logic I would create a separate controller for Offers which would be under /offers.
Concerning your last question. This very much depends on what you are trying to achieve. If you need to be able to update/delete/create offers then it makes sense to have such functionality. Even if it only used by the administrator. You can restrict the access to the endpoint. How to do that depends on the way you are authorize your users and the information that you have on them. Most people use roles.
If you decide to have the full CRUD functionality I would suggest to use a single path with combination of request methods.
Personally I would create the following:
#RestController
#RequestMapping(value = "/users")
class UserController {
#GetMapping("{userId}/offers")
public Set<Offer> getAllOffers(#PathVariable("userId") String userId){
...
}
#GetMapping("{userId}/offers")
public Offer getOffer(#PathVariable("userId") String userId, #RequestParam(required = true) String offerId){
...
}
#PutMapping("{userId}/offers")
public Offer createOffer(#PathVariable("userId") String userId, #RequestBody Offer offer){
...
}
#PostMapping("{userId}/offers")
public Offer updateOffer(#PathVariable("userId") String userId, #RequestBody Offer offer){
...
}
#DeleteMapping("{userId}/offers")
public void deleteOffer(#PathVariable("userId") String userId, #RequestParam(required = true) String offerId){
...
}
}
In this scenario I think the POST/PUT for create and update will be cleaner as there will be no duplication of information. To be precise the IDs.
I agree that it should be in the same 'UserController', it makes sense because offers belong to the user, so having an endpoint like:
#GetMapping("{user}/offers")
public Set<OfferDTO> getOffers(#PathVariable("user") String user) {
return offerService.getOffers(user);
}
You can define special DTOs for getting the meta-data from the offers if you wanted to display them in a list for example, and you could display them as a list to your user.
You could set up a similar endpoint for updating the offer which could be a POST endpoint, and a DELETE endpoint for deleting. You might want to think about what would happen if the user is looking at an offer when you delete it though, like making an asynchronous task for deleting the offer in a background thread and updating the UI to inform the user that the offer is deleted.
Spring has some really nice annotations for security stuff (check this and this), you could write your own annotation for administrator endpoints:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#PreAuthorize("hasAuthority('" + ROLE_ADMIN + "')")
public #interface IsAdmin {}
Then annotate your method like this:
#DeleteMapping("/{user}/{offer}/delete")
#IsReceiverAdmin
public void delete(#PathVariable("user") String user, #PathVariable("offer") String offer){
return offerService.delete(user, offer);
}
Of course the implementation of the service layer would be quite important, but it could be as simple as calling your repositories and performing the operations there :)
I have a POJO called User which is also being used for inserting documents in MongoDb.
#Data
#Document(collection = Constants.COLLECTION_USERS)
public class User {
public ObjectId _id;
public String userID;
public String email;
public String name;
public String sex;
public String dob;
public String photo;
//have more variables
}
I have a simple application where a user registers by giving in a subset of data listed in the User class. The signature of the register method in my controller is as follows.
public GenericResponse registerUser(#RequestBody User userRegistrationRequest)
It can be noticed that I am using the same POJO for the registration request. Until now everything is fine. I can persist the data user object just fine.
This registration API is just used to persist a small set of a user's data. There would be other information as well in the MongoDb document, which would be accessed/persisted from some other APIs.
Suppose a user has registered with the basic information and also has persisted other information via APIs other than the registration one.
How would I make an API which can just get me some selective data from the User document again using the same User Pojo? If I call the repository to give data for a specific userID, it will give me the whole document mapped to the User class. I don't want my API to give all the information stored in the document.
One approach is to make another POJO with the details I want, and map the information selectively using a Converter. But, I want to avoid this approach, as I want to use the same class.
Second approach: Modify the Mongo query to return data selectively as given in the docs. But here I would have to specify all the fields I want in the result set. This would again be a length query.
Is there a better way to filter out data from the object?
How would I make an API which can just get me some selective data from the User document again using the same User Pojo?
How would I go off-road with a car I would like to take me girl to the restaurant at the evening? I would not - if I would have the same car for everything I would look stupid next to the restaurant, coming out in a suite or I would stuck in a swamp.
The biggest Java advantage is object creation time - you should not be afraid of it. Just create another model for registration, another as DTO for saving data, another for front-end presentation etc
Never mix responsibility of objects. You will finish with something like
#Entity
class ThePileOfShit {
#Id
private Long id;
#my.audit.framework.Id
private String anotherId;
#JsonIgnore
// just a front-end flag ignore
private boolean flag;
#Column
// not a field but getter because of any-weird-issue-you-want-to-put-here
public String getWeirdStuff() { ... }
// Useless converters
public ModelA getAsModelA() { ... }
public ModelB getAsModelB() { ... }
// etc
// etc
}
Four frameworks, five technologies - nobody knows what's going on.
If you are afraid of converting stuff use ModelMapper or another tool but keep your POJOs as simple as possible
You can use Gson's #Expose annotation only on the fields you want to return in the API.
To serialize the data, use:
Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
String json = gson.toJson(userData);
This may be a simple task, but I couldn't find a way to do it. Basically, I need to disallow some parameters at the time of using #RequestBody annotation in my controller.
Here is my model:
#Data
public class MyModel {
private int id;
private String name;
}
What I want to do is at the time of response, I want both of the properties to be serialized to JSON, but at the time of create or update, I prefer not to receive id as part of #RequestBody deserialization.
Right now, if I pass id in the JSON body, Spring initializes a MyModel object with its id set to the passed value.
Reason? The ID cannot be generated until the model is created, so the app shouldn't allow the ID to be set. On update, the ID needs to be passed in the URL itself e.g. (PUT /mymodels/43). This helps following the REST principles appropriately.
So, is there any way to achieve this functionality?
Update 1:
Right now, I am stuck with using a request wrapper. I created a new class MyModelRequestWrapper with only name as its property, and have used it with the #RequestBody annotation.
How you do this depends on what version of Jackson you are using. It's basically possible by a combination of the annotations #JsonIgnore and #JsonProperty on relevant fields/getters/setters.
Have a look at the answers here: Only using #JsonIgnore during serialization, but not deserialization
I'm using MongoDB with Spring Data. I'd like to have one document reference another (a user in fact), but I end up with having to do extra work. E.g.
class Watch {
String id;
User user;
}
That's nice, but I seem to end up with the whole user embedded in the document, so I do this:
class Watch {
String id;
String userId;
}
But then I want to use it in some JSTL, and I want to do this:
${watch.user.email}
But I have to add some mapping code.
Use #DBRef annotation on user.
You need to save user separately (no cascading), but you probably want to do that.
Beware that user will be loaded eagerly.