RequestMapping Unable to populate Entity class where fields within EmbeddedId - java

I have the following Entity class:
#Entity
#Table(name="reporteddevicedata", schema="schemaName")
public class MobileDeviceData {
#EmbeddedId
MobileDeviceDataId mobileDeviceDataId;
#Column(name="activitydetecteddate")
private ZonedDateTime activityDetectedDate;
public void setFlagId(int flagId) {
mobileDeviceDataId.setFlagId(flagId);
}
......
}
#Embeddable
class MobileDeviceDataId implements Serializable {
#Column(name="clientid")
private int clientId;
#Column(name="flagid")
private int flagId;
}
My Controller code looks like this:
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
// code here
}
Originally I had my Entity class with just one primary key #ID on the clientId and it worked great. I would make a REST call and it would populate the MobileDeviceData class as expected. Then I switched to a composite ID using the #Embeddable and #EmbeddableId annotation and now the #RequestMapping is unable to populate the flagId parameter. Now when I make a REST call I get a null pointer exception for mobileDeviceDataId, thus its unable to update that field when it gets called and Throws a null pointer exception.
So my question is, how do I get an instance of the #Embeddable class? Can I just create one with new? I'm not sure of the implications of this since Spring may be expecting to make that value itself? What is the "normal" way this field would get updated via RequestMapping?

First of all you should avoid embedded id's, it just makes all things harder
surrogate primary keys are just easier to use, when you have a foreign key on a table with multi-column primary key it makes it much more complicated to deal with
now you faced these problems by yourself but according to your question
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
for(MobileDeviceData mobileDeviceData : deviceInfoList){
int clientId = mobileDeviceData.getMobileDeviceDataId().getClientId();
int flagId = mobileDeviceData.getMobileDeviceDataId().getFlagId();
MobileDeviceData foundMobileDeviceData = mobileDeviceDataService.findByClientIdAndFlagId(clientId, flagId);
if(foundMovileDeviceData == null){
mobileDeviceDataService.save(mobileDeviceData);
}else {
//update foundMobileDeviceData with mobileDeviceData fields
mobileDeviceDataService.save(foundMobileDeviceData);
}
}
}
else if you want to update just flag id
#RequestMapping(value="/mobile/device", method = RequestMethod.PUT)
public ResponseEntity<Object> flagDevice (#RequestBody List<MobileDeviceData> deviceInfoList) {
for(MobileDeviceData mobileDeviceData : deviceInfoList){
int clientId = mobileDeviceData.getMobileDeviceDataId().getClientId();
MobileDeviceData foundMobileDeviceData = mobileDeviceDataService.findByClientId(clientId);
if(foundMovileDeviceData == null){
mobileDeviceDataService.save(mobileDeviceData);
}else {
//update foundMobileDeviceData with mobileDeviceData
int flagId = foundMobileDeviceData.getMobileDeviceDataId().getFlagId();
MobileDeviceDataId mobileDeviceDataId = foundMobileDeviceData.getMobileDeviceDataId();
mobileDeviceDataId.setFlagId(mobileDeviceData)
mobileDeviceDataService.save(foundMobileDeviceData);
}
}
}
Next if you want to find something by client id just create a JPA Query like
"from MobileDeviceData WHERE mobileDeviceDataId.clientId = :clientId"
or native sql
"SELECT * FROM reporteddevicedata WHERE client_id = :someParam"
EXAMPLE JSON Request
[ {
"mobileDeviceDataId" : {
"clientId" : 0,
"flagId" : 0
},
"activityDetectedDate" : null
}, {
"mobileDeviceDataId" : {
"clientId" : 1,
"flagId" : 1
},
"activityDetectedDate" : null
}, {
"mobileDeviceDataId" : {
"clientId" : 2,
"flagId" : 2
},
"activityDetectedDate" : null
} ]
uglified ready to copy/paste version :
[{"mobileDeviceDataId":{"clientId":0,"flagId":3},"activityDetectedDate":null},{"mobileDeviceDataId":{"clientId":1,"flagId":1},"activityDetectedDate":null},{"mobileDeviceDataId":{"clientId":2,"flagId":2},"activityDetectedDate":null}]
Additionaly there should be some validation added on your MobileData object to avoid nullpointers when an invalid json request is send (with no mobileDeviceDataId present)
Finally answering the question:
Its not considered a good practice to use database model as container to share between API (because of primary keys, maybe some sensitive data, it depends).
Moreover if you want your embeddableId to work, request have to be build like in the example json. Proper fields have to be filled out. When requests arent build that way and they are just flat json without 'embedded id' you have to create some wrapper wich will fit the json format(this wrapper will be the requestbody class or List). Nextly you will have to convert wrapper to your db object with embedded id(created with a new keyword).
And this is why i suggest you not to use the composite or embedded id. This is simple example where do you have just one table, but when it comes to use foreign keys and multicolumn primary keys, the tabels are getting more complicated and messy, you make searching over db harder and this is why i suggest you to use surrogate id's without embedding anything, it makes things harder and is just messy

Related

How to ignore field/column only when return response from spring boot

I need to ignore the field when return the response from spring boot. Pls find below info,
I have one pojo called Student as below
Student {
id,
name,
lastName
}
i am getting a body for as PostRequest as below
{
id:"1",
name:"Test",
lname:"Test"
}
i want get all the data from frontEnd (id,name,Lname) But i just want to return the same pojo class without id as below,
{
name:"Test",
lName:"Test"
}
I have tried #JsonIgnore for column id, But it makes the id column as null(id=null -it is coming like this even when i send data to id field from postman) when i get the data from frontEnd.
I would like to use only one pojo to get the data with proper data(withoud getting id as Null), and need to send back the data by ignoring the id column.
Is there any way to achieve it instead of using another pojo?
You just need to use #JsonInclude(JsonInclude.Include.NON_NULL) at class level and it will be helpful for ignore all your null fields.
For example :
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
// Fields
// Constructors
// Getters - setters
}
As of now you are using only one POJO it's not good practice because it's your main entity into your project, so good practice is always make DTO for the same.
This is possible via the #JsonView annotation that is part of Jackson. Spring can leverage it to define the views used on the controller.
You'd define your DTO class like this:
class User {
User(String internalId, String externalId, String name) {
this.internalId = internalId;
this.externalId = externalId;
this.name = name;
}
#JsonView(User.Views.Internal.class)
String internalId;
#JsonView(User.Views.Public.class)
String externalId;
#JsonView(User.Views.Public.class)
String name;
static class Views {
static class Public {
}
static class Internal extends Public {
}
}
}
The Views internal class acts as a marker to jackson, in order to tell it which fields to include in which configuration. It does not need to be an inner class, but that makes for a shorter code snippet to paste here. Since Internal extends Public, all fields marked with Public are also included when the Internal view is selected.
You can then define a controller like this:
#RestController
class UserController {
#GetMapping("/user/internal")
#JsonView(User.Views.Internal.class)
User getPublicUser() {
return new User("internal", "external", "john");
}
#GetMapping("/user/public")
#JsonView(User.Views.Public.class)
User getPrivateUser() {
return new User("internal", "external", "john");
}
}
Since Spring is aware of the JsonView annotations, the JSON returned by the /public endpoint will contain only externalId and name, and the /internal endpoint will additionally include the internalId field.
Note that fields with no annotation will not be included if you enable any view. This behaviour can be controlled by MapperFeature.DEFAULT_VIEW_INCLUSION, which was false in the default Spring ObjectMapper when I used this for the last time.
You can also annotate your #RequestBody parameters to controller methods with JsonView, to allow/disallow certain parameters on input objects, and then use a different set of parameters for output objects.

Java record in spring RESTController - No primary or single unique constructor

I have tried the java records in new code. It looks nice but I face a trouble when deserializing from JSON in Spring REST controller.
Record
public record Order(UUID uuid, List<OrderItem> items) {}
Controller
#PostMapping(value = "/allocations")
public Allocation allocate(Order order) throws Exception {
return planningService.allocate(order);
}
Request:
{
"uuid": "123e4567-e89b-12d3-a456-556642440000",
"items": [
{ "amount" : 3000 }
]
}
This did not work because uuid in the record was null. There was no implicit String to UUID conversion. So I added the non default constructor:
public Order(String uuid, List<OrderItem> items) {
this(UUID.fromString(uuid), items);
}
But Spring does not like it:
java.lang.IllegalStateException: No primary or single unique constructor found for class com.example.fueltransport.beans.Order
at org.springframework.beans.BeanUtils.getResolvableConstructor(BeanUtils.java:267) ~[spring-beans-5.3.21.jar:5.3.21]
Am I supposed to write some unmarshaller for the record or is there some easier way?
add default constructor to your class:
class Order{
//some code and field
public Order(){
this.uuid = null;
this.items = null;
}
}
I think the solution will be fixed this exception but the problem remains because the Controller with JSON converter first creates an object and second fill the received fields for the object. but the java record has final fields and can't set the fields of the java record again.
for solving your problem you converter to support converting java records for example if you use the JACKSON converter should use the Jackson 2.12+. read this link maye be useful.
For anyone who may happen to stumble upon this, I think a better solution may look like this (assuming OrderItem class is serializable):
public record Order(UUID uuid, List<OrderItem> items) {
#ConstructorProperties({"uuid", "items"})
public Order(String uuid, List<OrderItems> items) {
this(UUID.fromString(uuid), items);
}
}

Handle Json non existent keys spring boot

I am creating a requestModel and let say a person doesn't send me some keys.
If that key is not present I want to put null if i get the value of the key.
I don't want to investigate if a key is present or not .
public class CustomerModel {
private Optional<String> s3Bucket;
private Optional<String> docType;
public String getS3Bucket() {
if(s3Bucket.isPresent()) {
return s3Bucket.get();
} else {
return null;
}
}
public void setS3Bucket(Optional<String> s3Bucket) {
this.s3Bucket = s3Bucket;
}
public Optional<String> getDocType() {
return docType;
}
public void setDocType(Optional<String> docType) {
this.docType = docType;
}
}
Do we have any library or something where.
1. If i get the key and it is not present in the coming request json, i will get the null out of it and if the key is present and has value . It will be stored as value.
2. When writing the getter for s3bucket (getS3Bucket), i dont want to write it for everykey value. Is there a automatic way to do this.
I looked at lot of posts but the scenario is not there.
P.S - I am new to java
I believe Jackson is exactly what you need. And if you are using Spring - it already uses Jackson under the hood I guess.
Here you can find some examples and documentation of how JSON mapping on to model class is done.
If you need to customize some behavior, you can use annotations like #JsonProperty (there are many).
If properties in your model class have the same names as properties in JSON, most probably you won't need to provide any further configs.
Here is a simple example:
public class User {
#JsonProperty("userName")
private String name;
private int age;
// getters and setters
}
And if you have JSON like this:
{
"userName" : "Foo Bar",
"age" : 18
}
Jackson will do all the magic for you unless you need something very specific.
If something is not in JSON you get (let's say you received JSON without age) - corresponding property in model class will be null if it is object type and default value (0, false, etc.) for primitives (in our case age would be 0).

Jackson: referencing an object as a property

In my java spring application, I am working with hibernate and jpa, and i use jackson to populate data in DB.
Here is the User class:
#Data
#Entity
public class User{
#Id
#GeneratedValue
Long id;
String username;
String password;
boolean activated;
public User(){}
}
and the second class is:
#Entity
#Data
public class Roles {
#Id
#GeneratedValue
Long id;
#OneToOne
User user;
String role;
public Roles(){}
}
In the class Roles i have a property of User
and then i made a json file to store the data:
[ {"_class" : "com.example.domains.User", "id": 1, "username": "Admin", "password": "123Admin123","activated":true}
,
{"_class" : "com.example.domains.Roles", "id": 1,"user":1, "role": "Admin"}]
Unfortunately, when i run the app it complains with:
.RuntimeException: com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.example.domains.User: no int/Int-argument constructor/factory method to deserialize from Number value (1)
at [Source: N/A; line: -1, column: -1] (through reference chain: com.example.domains.Roles["user"])
The problem comes from
{"_class" : "com.example.domains.Roles", "id": 1,"user":1, "role": "Admin"}
and when i remove the above line the app works well.
I think, it complains because it cannot make an instance of user.
So, how can i fix it?
Do yourself a favor and stop using your Entities as DTOs!
JPA entities have bidirectional relations, JSON objects don't, I also believe that the responsibilities of an Entity is very different from a DTO, and although joining these responsibilities into a single Java class is possible, in my experience it is a very bad idea.
Here are a couple of reasons
You almost always need more flexibility in the DTO layer, because it is often related to a UI.
You should avoid exposing primary keys from your database to the outside, including your own UI. We always generate an additional uniqueId (UUID) for every publicly exposed Entity, the primary key stays in the DB and is only used for joins.
You often need multiple views of the same Entity. Or a single view of multiple entities.
If you need to add a new entity to a relation with an existing, you will need find the existing one in the database, so posting the new and old object as a single JSON structure has no advantage. You just need the uniqueId of the existing, and then new.
A lot of the problems developers have with JPA, specifically with regards to merging comes from the fact that they receive a detached entity after their json has been deserialized. But this entity typically doesn't have the OneToMany relations (and if it does, it's the parent which has a relation to the child in JSON, but in JPA it is the child's reference to the parent which constitutes the relationship). In most cases you will always need to load the existing version of the entity from the database, and then copy the changes from your DTO into the entity.
I have worked extensively with JPA since 2009, and I know most corner cases of detachment and merging, and have no problem using an Entity as a DTO, but I have seen the confusion and types of errors that occur when you hand such code over to some one who is not intimately familiar with JPA. The few lines you need for a DTO (especially since you already use Lombok), are so simple and allows you much more flexibility, than trying to save a few files and breaking the separation of concerns.
Jackson provide ObjectIdResolver interface for resolving the objects from ids during de-serialization.
In your case you want to resolve the id based from the JPA/hibernate. So you need to implement a custom resolver to resolve id by calling the JPA/hierbate entity manager.
At high level below are the steps:
Implement a custom ObjectIdResolver say JPAEntityResolver (you may extends from SimpleObjectIdResolver). During resolving object it will call JPA entity manager class to find entity by given id and scope(see. ObjectIdResolver#resolveId java docs)
//Example only;
#Component
#Scope("prototype") // must not be a singleton component as it has state
public class JPAEntityResolver extends SimpleObjectIdResolver {
//This would be JPA based object repository or you can EntityManager instance directly.
private PersistentObjectRepository objectRepository;
#Autowired
public JPAEntityResolver (PersistentObjectRepository objectRepository) {
this.objectRepository = objectRepository;
}
#Override
public void bindItem(IdKey id, Object pojo) {
super.bindItem(id, pojo);
}
#Override
public Object resolveId(IdKey id) {
Object resolved = super.resolveId(id);
if (resolved == null) {
resolved = _tryToLoadFromSource(id);
bindItem(id, resolved);
}
return resolved;
}
private Object _tryToLoadFromSource(IdKey idKey) {
requireNonNull(idKey.scope, "global scope does not supported");
String id = (String) idKey.key;
Class<?> poType = idKey.scope;
return objectRepository.getById(id, poType);
}
#Override
public ObjectIdResolver newForDeserialization(Object context) {
return new JPAEntityResolver(objectRepository);
}
#Override
public boolean canUseFor(ObjectIdResolver resolverType) {
return resolverType.getClass() == JPAEntityResolver.class;
}
}
Tell Jackson to use a custom id resolver for a class, by using annotation JsonIdentityInfo(resolver = JPAEntityResolver.class). For e.g.
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id",
scope = User.class,
resolver = JPAObjectIdResolver.class)
public class User { ... }
JPAObjectIdResolver is a custom implementation and will have dependency on other resources( JPA Entity Manager) which might not be known to Jackson. So Jackson need help to instantiate resolver object. For this purpose, you need to supply a custom HandlerInstantiator to ObjectMapper instance. (In my case I was using spring so I asked spring to create instance of JPAObjectIdResolver by using autowiring)
Now de-serialization should work as expected.
Hope this helps.
I have changed the json file to :
[
{"_class" : "com.example.domains.User",
"id": 1,
"username": "Admin",
"password": "123Admin123",
"activated":true
},
{
"_class" : "com.example.domains.Roles",
"id": 1,
"user":{"_class" : "com.example.domains.User",
"id": 1,
"username": "Admin",
"password": "123Admin123",
"activated":true
},
"role": "Admin"
}
]
But i still think, the best ways is using a foreign key to user record.
Any solution is welcomed
If your bean doesn't strictly adhere to the JavaBeans format, Jackson has difficulties.
It's best to create an explicit #JsonCreator constructor for your JSON model bean, e.g.
class User {
...
#JsonCreator
public User(#JsonProperty("name") String name,
#JsonProperty("age") int age) {
this.name = name;
this.age = age;
}
..
}
1-1 mapping of fields works well , but when it comes to complex object mapping , better to use some API.
You can use Dozer Mapping or Mapstruct to map Object instances.
Dozer has spring integration also.
You could specify non default constructors and then use a custom deserialiser.
Something like this should work (it has not been tested).
public class RolesDeserializer extends StdDeserializer<Roles> {
public RolesDeserializer() {
this(null);
}
public RolesDeserializer(Class<?> c) {
super(c);
}
#Override
public Roles deserialize(JsonParser jp, DeserializationContext dsctxt)
throws IOException, JsonProcessingException {
JsonNode node = jp.getCodec().readTree(jp);
long id = ((LongNode) node.get("id")).longValue();
String roleName = node.get("role").asText();
long userId = ((LongNode) node.get("user")).longValue();
//Based on the userId you need to search the user and build the user object properly
User user = new User(userId, ....);
return new Roles(id, roleName, user);
}
}
Then you need to register your new deserialiser (1) or use the #JsonDeserialize annotation (2)
(1)
ObjectMapper mapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(Item.class, new RolesDeserializer());
mapper.registerModule(module);
Roles deserializedRol = mapper.readValue(yourjson, Roles.class);
(2)
#JsonDeserialize(using = RolesDeserializer.class)
#Entity
#Data
public class Roles {
...
}
Roles deserializedRol = new ObjectMapper().readValue(yourjson, Roles.class);
public class Roles {
#Id
#GeneratedValue
Long id;
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
#JsonIdentityReference(alwaysAsId = true)
#OneToOne
User user;
String role;
public Roles(){}
}

Separate database model from Network model

Im using GreenDAO and Volley. So I have the following problem: When I make a network request I need to parse with GSON so I have a model to represent entities retrieved from server and other model to represent the GreenDAO objects. Is there any way to only have 1 class per model to represent as a GSON and a Class of ORM?
class Product:
#SerializedName("id")
private String id;
#SerializedName("pictures")
private List<Picture> pictures;
get & set
class PersistentProduct:
private Long id;
private List<Picture> pictures;
/** To-many relationship, resolved on first access (and after reset). Changes to to-many relations are not persisted, make changes to the target entity. */
public List<PersistencePicture> getPictures() {
if (pictures == null) {
if (daoSession == null) {
throw new DaoException("Entity is detached from DAO context");
}
PersistencePictureDao targetDao = daoSession.getPersistencePictureDao();
List<PersistencePicture> picturesNew = targetDao._queryPersistenceProduct_Pictures(id);
synchronized (this) {
if(pictures == null) {
pictures = picturesNew;
}
}
}
return pictures;
}
First I thought to make a Interface, but when you retrieve the data from a DAO the DAO returns the class and not the interface, so I think cannot do in this way, the only solution I found is to make a "ProductUtils" that converts from a "PersistentProduct" to a "Product" and vice versa.
The most elegant way would be to implement a small extension for greendao, so that you can specify the serialized name during schema-creation.
For Example:
de.greenrobot.daogenerator.Property.java:
// in PropertyBuilder append these lines
public PropertyBuilder setSerializedName(String sname) {
// Check the sname on correctness (i.e. not empty, not containing illegal characters)
property.serializedName = sname;
return this;
}
// in Property append these lines
private String serializedName = null;
public boolean isSerialized() {
return serializedName != null;
}
In entity.ftl add this line after line 24 (after package ${entity.javaPackage};):
<#if property.serializedName??>
import com.google.gson.annotations.SerializedName;
</#if>
And after line 55 (after: <#list entity.properties as property>)
<#if property.serializedName??>
#SerializedName("${property.serializedName}")
</#if>
Afterwards you should be able to use you generated greendao-entity for volley with the following restrictions:
If you get a Product over network, nothing is changed in the db, yet. You have to call insertOrReplace().
If you get a Product from db and send it via network some undesired fields might be serialized (i.e. myDao and daoSession)
If you get a Product via network and call insertOrReplace() the "network"-Product will be persisted and a already existing Product will be replaced by it BUT the referenced entities won't get updated or persisted if insertOrReplace() isn't called for each of them!
If you get a Product via network and call insertOrReplace() for every referenced entity toMany-entities that were referenced by the db-Product are still referenced by the updated Product, although they are not listed in the updated Product. You have to call resetPictures() and getPictures() to get the correct list, which will contain all toMany()-entities references by either the original Product stored in DB or the updated Product from network.
Update addressing 2.
To prevent daoSession and myDao from being serialized, you can use the following ExclusionStrategy:
private static class TransientExclusionStrategy implements ExclusionStrategy {
public boolean shouldSkipClass(Class<?> clazz) {
return (clazz.getModifiers() & java.lang.reflect.Modifier.TRANSIENT) != 0;
}
public boolean shouldSkipField(FieldAttributes f) {
return f.hasModifier(java.lang.reflect.Modifier.TRANSIENT);
}
}
Update addressing 1.,3. and 4.
As a fast solution you can add the following method in the KEEP-SECTIONS of your entity:
public void merge(DaoSession s) {
s.insertOrReplace(this);
// do this for all toMany-relations accordingly
for (Picture p : getPictures()) {
s.insertOrReplace(p);
newPics.add(p.getId());
}
resetPictures();
}
This will result in the original entity being updated and attached to the session and dao. Also every Picture that is references by the network-product will be persisted or updated. Pictures reference by the original entity, but not by the network-entity remain untouched and get merged into the list.
This is far from perfect, but it shows where to go and what to do. The next steps would be to do everything that is done in merge() inside one transaction and then to integrate different merge-methods into dao.ftl.
NOTE
The code given in this answer is neither complete nor tested and is meant as a hint on how to solve this. As pointed out above this solution still has some restrictions, that have to be dealt with.

Categories

Resources