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);
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.
I am building a spring boot application which uses REST services to deliver content to the front-end
But my DTO does not correspond to my database.
Here is a diagram of the database
And my DTO should look something like this
public class GlobeEntity extends BaseEntity {
// for all definition years
private List<Instance> instances;
class Instance {
// CountryInstance.definitionYear
private String definitionYear;
// for all countries
private List<Country> countries;
class Country {
// Country.countryId
String id;
// Country.externalIdentifier
String externalIdentifier;
// CountryInstanceCompatibility.total
String com;
// CountryInstanceUtility.total
String uti;
}
}
}
Is this possible to do with hibernate annotations?
For the DTO, what is turned into JSON when you return that class from a controller depends on your JSON serializer you have configured in Spring. Then you make use of the JSON annotations to have more control over what is returned.
Depending on how you do things, you may just choose to convert between an entity and a DTO, and have a class for each. Although this is a bit annoying because it does add allot of overhead of having more classes and other classes depending on the two, it can be come a real real mess if you use the same class for both DTO and Entity representations when they are very different. For instance if you use a method in your controller, it's not clear wether this is an entity or a DTO. There are many cases when you have a controller you return a response which is not a resource in a database.
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.
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.