Entity via REST not all values set - java

this is more like a "best-practice" question.
I am playing around with spring-boot and hibernate to build a small CRUD Service.
Let's assume, I have an entity called Customer (which is annotated as hibernate entity...) which has all kinds of properties like name, middlename, lastname, ...
#Entity
#Table(name = "OCUS")
public CustomerEntity {
// Props
#Column(name = "Name")
#SerializedName("name")
#Expose
private String name;
#Column(name = "MName")
#SerializedName("mname")
#Expose
private String middlename;
...
#Column(name = "Status")
#SerializedName("status")
#Expose
private CustomerEntityType status = CustomerEntityType.NEW;
#Column(name = "Valid")
#SerializedName("valid")
#Expose
private Boolean valid = false;
// getters and setters
}
I also created a CustomerEntityRepository which extends the JPARepository like this:
#Repository
public interface CustomerEntityRepository extends JpaRepository {
}
After that, I created a RestController to have GET/POST/DELETE/... Methods for my customers.
#RestController
#RequestMapping("/v1/customers")
public class CustomerRestController {
#Autowired
CutomerEntityRepository customerRepo;
#PostMapping
public ResponseEntity<CustomerEntity> createCustomer(#RequestBody CustomerEntity customer) {
customerRepo.save(customer);
// ... Build ResponseEntity...
}
...
}
Let's assume, the RequestBody does not include all properties from my CustomerEntity, maybe I have Properties like "status" or "valid".
e.g.:
{
"name" : "Jon",
"lastName" : "Doe",
"field1" : "value1",
...
}
My question now is:
What has to be done, so that the customer I am going to save into the CustomerEntityRepository gets all the standard values set (see property: status and valid) without having to check all properties if it was included in the request body.
My Goal is only to save customers, which represent my CustomerEntity with all the default values when getting customers via my Rest Controller.
Thank you for any feedback
Robert
[EDIT]:
Because my question seems to be unclear, I'll try to elaborate it further:
Assume my REST Endpoint receives the following Json which represents a CustomerEntity:
{
"name" : "Jon",
"lastName" : "Doe"
}
The Deserializer from the spring-boot packages seems to look up for a suitable constructor of my customerentity.
So if I implement a constructor like this:
public CustomerEntity(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
The deserializer tries to construct the customerEntity nicely via this constructor. All my standard values like valid will be set to false and status will be set to CustomerEntityType.NEW;
Let's assume the next incoming Json is:
{
"name" : "Jon",
"lastName" : "Doe",
"middleName" : "Harry"
}
But this time, I have no suitable constructor to create the customer via a constructor. So creating a vast number of constructors to represent all possible variants is not the right way, I think.
I don't know how exactly the deserializer now creates my CustomerEntity object out of the JSON, but the object I receive in my method:
#PostMapping
public ResponseEntity<CustomerEntity> createCustomer(#RequestBody CustomerEntity customer) {
customerRepo.save(customer);
// ... Build ResponseEntity...
}
Has only set the 3 properties: name, lastName, middleName and all other properties of CustomerEntity are null.
And I need to prevent such CustomerEntities to be saved to the database.
Hope this case is now more clear.

You can use hibernate validator which is an implementation of JSR 380.
and btw, if you are using any of the annotation provided by hibernate-validator, you'll have to use #valid annotation in your controller from package javax.validation.
so your controller will be something like,
#PostMapping
public ResponseEntity<CustomerEntity> createCustomer(#Valid #RequestBody CustomerEntity customer) { }
you can read more about that in detail here : https://spring.io/guides/gs/validating-form-input/
http://hibernate.org/validator/

Not eally sure what you are asking , if you want to set the default values of fields/attribute
you can use :
#Column(name="status", columnDefinition="Decimal(10,2) default '100.00'")
ref: Setting default values for columns in JPA

Related

I wan't to create unit test that handle an exeption when dto fields != entity fields

I want to create unit test that handle an exception when dto fields != entity fields. And print missing and extra fields. Is it possible to make it with MapStruct?
Simple example without annotations and boilerplate code.
I have
public class TestDto {
long id;
String name;
int age;
long customerId;
String phone;
}
And Entity
public class TestEntity {
long id;
String name;
int age;
Customer customer;
String address;
}
#Mapping(source = "person.id", target = "personId") // in my Mapper class
TestDto toDto(Test entity);
output: Missing fields for dto. Please add!: address;
Extra fields for dto. Please remove! : phone;
I tried to use reflection and it didn't work. https://stackoverflow.com/questions/75082277/how-to-find-all-missing-and-extra-fields-in-dto-by-their-entity-using-reflec?noredirect=1#comment132500771_75082277
I'd love to hear any ideas on how to do this.
Mapstruct itself cannot do this, because it runs compile time. However you can use the generated code to check this if you have completely filled objects.
Simply map a completely filled dto to an entity, and then back to the dto object. Now compare the resulting dto with your original dto and you can figure out which fields are not the same anymore.
Repeat this for a completely filled entity by mapping it to a dto and back to entity and you've got the entity class covered.
In code it looks something like this:
#Test
void dtoFieldTest() {
TestDto original = createFilledTestDto();
TestMapper testMapper = TestMapper.INSTANCE;
TestDto result = testMapper.mapEntityToDto(testMapper.mapDtoToEntity(original));
// assertj assertions
assertThat(result).isEqualTo(original);
// or if you cannot use the dto equals method.
assertThat(result).usingRecursiveComparison().isEqualTo(original);
}

How to have few properties in Request Body as optional in Spring Boot

I have a REST endpoint(/users) in my controller which is of HTTP type POST, this REST endpoint accepts a RequestBody of users which has the following properties :
{
name: 'abc',
address: 'xyz',
phoneNo: '123',
age: '12',
email: 'xyz#gmail.com'
}
My requirement is such that, the age should be completely optional, as in if the user invokes the REST endpoint without specifying the age(keyword) in the payload it should work like a charm.
For e.g.
{
name: 'abc',
address: 'xyz',
phoneNo: '123',
email: 'xyz#gmail.com'
}
So, if in case the user doesn't specify age keyword in the payload I have a default business logic to execute, on the other hand, if the user specifies age keyword along with its value then I have some other logic to process.
FYI - I have a DTO class created of Users where I've declared all the properties, here's how it looks
#Data
class Users{
#NotNull
private String name;
#NotNull
private String address;
#NotNull
private String phoneNo;
private String age;
#NotNull
private String email;
}
So I kindly appreciate it if someone provides me with a way to handle my problem statement.
Thanks, Aliens!
Use the annotation #JsonIgnoreProperties(ignoreUnknown = true) on the DTO class, so that if the RequestBody is not having the property then that property will be ignored.
#Data
#JsonIgnoreProperties(ignoreUnknown = true)
class Users {
.......
.......
}

how to convert from String to enum during BeanUtils.copyProperties in Spring Boot Rest API

I have to copy the properties from dto to entity class.
I am using BeanUtils.copyProperties().
In request body I am sending like below:
{
"userName":"test",
"userStatus": "I",
}
DTO class:
public class UserDto {
private String userName;
private String userStatus;
public User buildUser() {
User user = new User();
BeanUtils.copyProperties(this, user);
return user;
}
}
Entity class:
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#Column(name = "user_name")
private String userName;
#Enumerated(EnumType.STRING)
#Column(name = "user_status")
private UserStatus userStatus;
}
note: userStatus can be nullable field in table.
Service code:
User user = userDto.buildUser();
I am getting userStatus value as null in User entity class.
When I changed UserDto.userStatus to enum type, then request body is not accepting empty value.
How do I convert from String to enum during BeanUtils.copyProperties() ?
Spring BeanUtils is not designed for such customizations.
You should set the field manually with.
While MapStruct or Dozen are.
As alternative to keep BeanUtils and no explicit setter invocation you have :
defining a factory method for the enum Jackson processing (a static method annotated #JsonCreator in the enum class such as :
#JsonCreator public static UserStatus getValue(String name) {
return
Stream.of(UserStatus.values())
.findAny(s -> s.name().equals(name))
.orElse(null);
}
In most of cases, this is the best solution as it handles the issue at the root.
setting the flag to ignore unknown value for any field of the class:
public class UserDto {
#JsonIgnoreProperties(ignoreUnknown = true)
//...
}
Fastest solution but I don't like a lot as it may hide some other serialization/deserialization issues.
adding an enum value representing the emptiness. You could so define the enum in the DTO.
In order to not store it in the database, the mapping of this enum value to null should be done in the entity itself.
For example :
public void setUserStatus(UserStatus userStatus){
if (userStatus != UserStatus.EMPTY){
this.userStatus = userStatus;
}
}
It should work but I am not a big fan either...
Enums cannot be null because their underlining values are int but you can set the FIRST value in the enum as a default value. tyou can also define your field in DTO as an Enum type instead of String.
UserStatus
public enum UserStatus {
NULL,
ACTIVE,
INACTIVE;
}
Service code:
userDto.setUserStatus(UserStatus.NULL);
userDto.buildUser();
OR If you want to set this override of copyProperties method to ignore userStatus field while converting:
public static void copyProperties(Object source, Object target,
#Nullable Class<?> editable,
#Nullable String... ignoreProperties);

Spring data MongoDB unable to map _id in group aggregation

I'm using Spring Data MongoDB to generate an aggregated query. At one point I do this:
// 5. Rejoin the array with group.
group("email", "name", "surname", "birthday", "creationTime", "updateTime", "technology")
.push(SCORES_FIELD).as(SCORES_FIELD));
The generated step (in the log) is this:
"$group" : {
"_id" : {
"email" : "$_id",
"name" : "$name" ,
"surname" : "$surname" ,
"birthday" : "$birthday" ,
"creationTime" : "$creationTime" ,
"updateTime" : "$updateTime" ,
"technology" : "$technology"
} ,
"scores" : { "$push" : "$scores"}
}
Which is perfectly fine, I've tested it on the Mongo shell and gives back exactly what I want.
The problem is that when I do the same with Spring Data, the email field (which is the _id field in Mongo) is mapped as null. There's probably something wrong in my mapping but I haven't been able to figure out what is it exactly. Here's the model:
#Document(collection = "user")
public class User implements UserDetails {
private static final long serialVersionUID = 1L;
private String name;
private String surname;
private LocalDate birthday;
#Id
#Field("_id")
private String email;
private Collection<? extends GrantedAuthority> authorities;
private String password;
private Set<Score> scores;
private LocalDateTime creationTime;
private LocalDateTime updateTime;
private String technology;
// Getters and setters, hashmap, equals and toString
}
I've done other queries and everything works out perfectly. I'm having this problem only on this one which is the only aggregation I do.
Promoting my comment to answer.
The _id can't be mapped into email because group stage returns multiple keys in _id document. previousOperation() is just a convenience method to return the _id from previous group operation. You can try changing to group("email").first("name").as("name").... and see if it helps.
I would expect spring to read the Field annotation from the model and map the _id field back to email now.

Spring Mongo DB #DBREF

I am having trouble writing code that would allow get a user and claim details in a straightforward way. This is my MongoDB structure,
db.user.find();
user:
{
"name" : "KSK",
"claim" : [objectId("52ffc4a5d85242602e000000"),objectId("52ffc4a5d85242602e000001")]
}
claim:
[
{
"_id" : "52ffc4a5d85242602e000001",
"claimName" :"XXXX"
},
{
"_id" : "52ffc4a5d85242602e000000",
"claimName" :"YYY"
}
]
My Entity class is:
#Document(collection="user")
public class User{
#Id
private String id;
private String name;
#DBRef
private List<Claim> claim;
// setter and getter
}
Claim Class:
#Document(collection="Claim")
public class Claim{
#Id
private String id;
private String claimName;
}
I have a method to get the users by name like below,
public User findByName(String name);
If I try to hit this method am getting an error that,
No converter found capable of converting from type org.bson.types.ObjectId to type java.lang.String
So I changed my User entity class as like below,
Instead of private List<Claim> claim;
Changed as Private List<ObjectId> claim;
Now if I execute a method(findByName), I get a user object that has both claimed object ids ("52ffc4a5d85242602e000001","52ffc4a5d85242602e000000"), then iterate the claim list and get the claim details corresponding to the claim object Id.
Instead of doing this, when I execute findByName method I want to get a user and claim details. How can I achieve this functionality?
If you reference your Claims in the User class with #DBRef, your JSON should not only contain the ID but the reference to the collection where to find the ID as well, like this:
{
"name" : "KSK",
"claim" : [
{
"$ref" : "claim", // the target collection
"$id" : ObjectId("52ffc4a5d85242602e000000")
}
]
}
That is how Spring-Data maps your Java objects to MongoDB. If you start with a blank database and let Spring create and save the relations, you should have no problems using
#DBRef List<Claim> claims;
My suggestion is not to set that Claim class into separate #Document or just switch back to Relational Databases, because it's not a Mongo approach.
Also, if you insist on current architecture you can try using #DBRef above that List in User.class into smth like this:
public class ParentModel {
#Id
private String id;
private String name;
private ParentType parentType;
private SubType subType;
#DBRef
private List<Model> models;
....
}
as an alternative to #DBRef, take a look at RelMongo (link)
which provides a powerfull way to manage relations, in your case it will be like this :
#OneToMany(fetch = FetchType.LAZY)
private list<Claim> claims;

Categories

Resources