Repository GET returns JSON which contains both a content object and content array - spring-boot

I have an entity with a ManyToOne relationship, when I use restTemplate.getForEntity(), the nested entity has its values wrapped in a content field at the same time there is a content array added overriding it
#ManyToOne(fetch = FetchType.LAZY)
#RestResource(exported = false)
#JoinColumn(name = "namespace", nullable = false)
private Namespace namespace;
A GET on the entity with this relation returns the following output body
{
"id" : "some_containing_id",
"alertDefinition" : null,
"namespace" : {
"content" : {
"id" : "some_namespace_id",
"emailSenderName" : "some sender",
"emailSenderId" : "foo#bar.com",
"createdAt" : "2018-07-19T05:24:04.473Z",
"updatedAt" : "2018-07-19T05:24:04.473Z"
},
"content" : [ ],
"links" : [ ]
},
...
So the namespace is being serialized containing 2 content fields with the array replacing the content object containing the values
SpringBoot 2.0.3.RELEASE
Namespace.java
#Entity
#Table(name = "namespace"
, schema = "alert_notification"
)
public class Namespace implements java.io.Serializable {
public transient static final String
EMAIL_SENDER_NAME_DEFAULT = "some sender";
public transient static final String
EMAIL_SENDER_ID_DEFAULT = "foo#vbar.com";
#Id
#Column(name = "id", unique = true, nullable = false)
private String id;
#Builder.Default
#Column(name = "email_sender_name")
private String emailSenderName = EMAIL_SENDER_NAME_DEFAULT;
#Builder.Default
#Column(name = "email_sender_id")
private String emailSenderId = EMAIL_SENDER_ID_DEFAULT;
#CreationTimestamp
#Column(name = "created_at", nullable = false, updatable = false)
private OffsetDateTime createdAt;
#UpdateTimestamp
#Column(name = "updated_at", nullable = false)
private OffsetDateTime updatedAt;
}

For some reason the issue is caused by the Hibernate5 module included in com.fasterxml.jackson.datatype:jackson-datatype-hibernate5 which was recently introduced.
Interestingly enough the module doesn't even need to be enabled, having the dependency on the classpath alone will cause the problem.

Related

how to specify List of entites in an entity using JPA

I have two entities. A vulnerability can have multiple vulnerability identifiers.
#Entity
#JsonInclude(Include.NON_NULL)
#ApiModel(parent = ApprovableEntity.class)
public class Vulnerability {
...
#JsonProperty("vulnerabilityIdentifiers")
#JoinColumn(name = "vulnerabilityidentifier_id")
#JsonView(JsonViews.BasicChangeLogView.class)
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private List<VulnerabilityIdentifier> vulnerabilityIdentifiers;
...
}
#Entity
#ApiModel(parent = ApprovableEntity.class)
public class VulnerabilityIdentifier {
...
#ManyToOne
#JoinColumn(name = "vulnerability_id", referencedColumnName = "id")
#NotNull(message = "vulnerability is required")
#JsonView({JsonViews.BasicApprovableView.class, JsonViews.BasicChangeLogView.class,
JsonViews.ChangeLogAnswerView.class, JsonViews.DraftAnswerView.class})
#ApiModelProperty(hidden = true)
private Vulnerability vulnerability;
#Column(name = "type")
#JsonProperty("type")
#Size(max = 12)
#NotNull(message = "CVEID type required")
#ApiModelProperty(accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String cveIdType;
#Column(name = "value")
#JsonProperty("value")
#Size(max = 24)
#NotNull(message = "value is required")
#ApiModelProperty(accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String value;
...
}
Now when I send in a json request to the endpoint like as under, the application throws exception that it cannot map the type and value fields in the vulnerabilityIdentifier field.
A sample json request
{
"vulnerabilityImpacts": {
},
"vulnerabilityIdentifiers": [{"type": "cveId", "value": "CVE-1234-12345"}],
"vulnerableThreeppcomponents": [],
"internalSource": "**",
"cveId": "*****",
......
}
Both the cveId and value properties are annotated with #ApiModelProperty(accessMode = ApiModelProperty.AccessMode.READ_ONLY) which means they are ignored when deserializing. Remove this annotation from both properties.

Why lazy init entity is null when assigned to DTO inside transaction boundary?

I am porting my legacy code to spring boot + angular application, which was spring + angular js earlier. My pojo has #ManyToOne relationship in it which is lazy.
When i try to create a DTO object from original object, the child object inside orginal object is null, when sent to client. In my legacy application same code works perfectly.
If i make it eager or i call sysout on that child element before creating DTO then it works, probably because getters of child object is called internally.
Parent object
public class ComponentInfo implements Serializable{
private static final long serialVersionUID = -1135710750835719391L;
#Id
#Column(name="TAGGING_KEY")
private String taggingKey;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "COMPONENT_CONFIG_ID",nullable = true)
private ReportComponentConfig componentConfig;
#Column(name = "DESCRIPTION", length = 200)
private String description;
#Column(name = "ORIGINAL_FILE_NAME",length=50)
private String originalFileName;
#Column(name = "OVERRIDE_DOCUMENT")
private Boolean overrideDocument = false;
#Column(name = "START_DATE")
private Date startDate;
#OneToMany(mappedBy="componentInfo",cascade=CascadeType.ALL,orphanRemoval=true)
private List<TransactionComponentInfo> transactionComponentInfo = new ArrayList<>(0);
#Column(name = "SHOW_GLOBAL")
private Boolean showGlobal = false;
}
Child Object
public class ReportComponentConfig {
#Id
#TableGenerator(name = "COMPONENT_CONFIG_ID", table = "ID_GENERATOR", pkColumnName = "GEN_KEY", valueColumnName = "GEN_VALUE", pkColumnValue = "COMPONENT_CONFIG_ID", allocationSize = 1, initialValue = 1)
#GeneratedValue(strategy = GenerationType.TABLE, generator = "COMPONENT_CONFIG_ID")
#Column(name = "COMPONENT_CONFIG_ID", unique = true, nullable = false)
private int id;
#Column(name = "NAME", nullable = false, length = 200)
private String name;
#Column(name = "TAG", nullable = false, length = 200)
private String tag;
#Column(name = "COMP_CONFIG", nullable = false, columnDefinition = "VARCHAR(MAX)")
private String config;
#Column(name = "PUBLISHED_CONFIG", columnDefinition = "VARCHAR(MAX)")
private String publishedConfig;
#Column(name = "IS_PUBLISHED")
private boolean published = false;
#ManyToOne
#JoinColumn(name = "COMPONENT_ID", nullable = false)
private Component component;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "CONFIG_REPORT_MAPPING", joinColumns = #JoinColumn(name = "COMPONENT_CONFIG_ID"))
#Column(name = "REPORT_ID")
private Set<Long> reports = new HashSet<>(0);
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(name = "CONFIG_TRANSACTION_MAPPING", joinColumns = #JoinColumn(name = "COMPONENT_CONFIG_ID"))
#Column(name = "TRANSACTION_ID")
private Set<Long> transactions = new HashSet<>(0);
#Column(name = "VIEWS", columnDefinition = "VARCHAR(MAX)")
private String views;
}
DTO
public class ComponentInfoDTO implements Cloneable {
private ReportComponentConfig componentConfig;
private String taggingKey;
private String description;
private String originalFileName;
private Date startDate;
private Boolean overrideDocument;
private Boolean showGlobal;
private ComponentInfoDTO parentComponentInfo;
public ComponentInfoDTO() {
}
public ComponentInfoDTO(ComponentInfo ci, TransactionComponentInfo transactionComponentInfo) {
this.componentConfig = ci.getComponentConfig();//this is object is null
this.taggingKey = ci.getTaggingKey();
this.description = ci.getDescription();
this.originalFileName = ci.getOriginalFileName();
this.startDate = ci.getStartDate();
this.overrideDocument = ci.getOverrideDocument();
this.showGlobal = ci.getShowGlobal();
if (transactionComponentInfo != null) {
this.parentComponentInfo = this.clone();
this.startDate = transactionComponentInfo.getStartDate();
this.overrideDocument = transactionComponentInfo.getOverrideDocument();
this.description = transactionComponentInfo.getDescription();
this.originalFileName = transactionComponentInfo.getOriginalFileName();
this.showGlobal = true;
}
}
}
New Code image
Old Code image
Edit:
Both are same but in older case i get the child object on client side and in new case i get null.
This is the data i'm getting in my older application with lazy init
[ {
"componentConfig" : {
"id" : 3,
"name" : "Monthly Origination By Region",
"tag" : "CHART_monthlyOriginationByRegion",
"config" : "xyz",
"published" : true,
"component" : {
"id" : "CHART",
"name" : "Chart",
"defaultConfig" : null,
"htmlTag" : "<chart></chart>",
"filePath" : "chart/chart.component.js",
"dependentScriptsSrc" : [ ],
"dependencies" : null
},
"reports" : [ 3 ],
"transactions" : [ 2 ],
"views" : "{\"monthlyOriginationByRegion\": {\"key\": \"MONTHLY_ORIGINATION_BY_REGION\"}}"
},
"taggingKey" : "3",
"description" : "asdfasd\nasdfadf",
"originalFileName" : "Citi Tape - 2141 - GEBL0501 - 2019 Oct 04.xlsm",
"startDate" : "2019-10-28T18:30:00.000+0000",
"overrideDocument" : true,
"showGlobal" : true,
"parentComponentInfo" : null
} ]
This is the data in new application
[ {
"componentConfig" : null,
"taggingKey" : "3",
"description" : "asdfasd\nasdfadf",
"originalFileName" : "Citi Tape - 2141 - GEBL0501 - 2019 Oct 04.xlsm",
"startDate" : "2019-10-28T18:30:00.000+0000",
"overrideDocument" : true,
"showGlobal" : true,
"parentComponentInfo" : null
} ]
Component config should not be null, if make it eager fetch it works in new app but in my older application, it is working with lazy fetch.
What you see in the debugger window is a HibernateProxy. The fields of that proxy are never initialized! The getters are intercepted and delegated to the loaded entity which is located somewhere inside the proxy.
This means you have to never call fields directly, as you don’t know if the object is a proxy or your loaded entity. You always need to work on the getters.
How are you mapping the entity to the DTO? Most likely you use field access, while you should use the getters.

Add entity with OneToOne Relation using JPA and REST

I am using Spring JPA Restful, and I don't understand how to insert an entity with a foreign key.
Activity Entity:
#Entity
#Table(name= "Activity")
public class Activity implements Serializable{
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name="uuid", strategy = "uuid2")
#Column(name = "uuid", nullable = false, unique = true)
private UUID uuid;
#OneToOne(fetch = FetchType.EAGER, cascade=CascadeType.MERGE)
#JoinColumn(name="type", nullable = false)
private ActivityType type;
#Column(nullable = false)
private String label;
ActivityType Entity:
#Entity
#Table(name= "ActivityType")
public class ActivityType implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(nullable = false, unique = true)
private String code;
#Column(nullable = false
private String label;
Is it possible to insert Activity simply? With something like this JSON where ActivityType's id "1" exists:
createActivity:
{"label":"LABEL","type":1}
With this code I have to do:
createActivity:
{"label":"LABEL","type":{"id":1}}
which return value is:
{
"uuid": "a54b27aa-8d49-41fd-8976-70c019c40e3b",
"type": {
"id": 1,
"code": null,
"label": null
},
"label": "LABEL",
"details": null
}
I use the library gson for parsing domain classes into JSON.
//... code for making your activity, let's say you have an Activity object myActivity
Just add the following code where you want to parse your object into JSON.
Gson gson = new GSON();
String json = gson.toJson(myActivity);

Mapping of oneToMany with composite key using eclipselink gives me ORA-01400

I am trying to map the classic bi-directional oneToMany using eclipselink.
My problem is that when i want to insert a new 'child' i get
SQLIntegrityConstraintViolationException.
The database is described like this :
#Entity
#IdClass(KuponPK.class)
#Table(name = "KUPON", schema = "POST", catalog = "")
public class Kupon implements Serializable {
private Integer id;
private String spil;
private Collection<Kombination> kombinationList;
#OneToMany(mappedBy = "kupon", cascade = CascadeType.PERSIST)
public Collection<Kombination> getKombinationList() {
return kombinationList;
}
public class KuponPK implements Serializable {
private Integer id;
private String spil;
#Id
#Column(name = "ID", nullable = false, insertable = true, updatable = true, precision = 0)
public Integer getId() {
return id;
}
#Id
#Column(name = "SPIL", nullable = false, insertable = true, updatable = true, length = 5)
public String getSpil() {
return spil;
}
#Entity
#Table(name = "KOMBINATION", schema = "POST", catalog = "")
public class Kombination {
private Integer id;
private String sorteringOrden;
private Integer sorteringNr;
private Integer antalSpillede;
private BigDecimal odds;
private Kupon kupon;
#ManyToOne
#JoinColumns({#JoinColumn(name = "KUPON_ID", referencedColumnName = "ID", nullable = false, insertable=false, updatable=false),
#JoinColumn(name = "KUPON_SPIL", referencedColumnName = "SPIL", nullable = false, insertable=false, updatable=false)})
public Kupon getKupon() {
return kupon;
}
In my stateless session i have a Kupon object and i create a new Kombination where i set the Kupon and try to merge, but im getting
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: ORA-01400: cannot insert NULL into ("KOMBINATION"."KUPON_ID")
which is obvious since its part of primary key
I am setting the Kombination to Kupon and the Kupon to Kombination, but that doesnt make any difference
How can can i tell that the key is inside the Kupon object which im setting in the Kombination object ??

Spring Data Rest 2.1.0 Cannot POST or PUT Complex Resource

EDIT: This appears to be happening with PUTs as well.
Using spring-data-rest-webmvc version 2.1.0.BUILD-SNAPSHOT I have found that I am unable to POST a resource with a relation pointing to an already existing resource. I have 2 such entities which require references to be instantiated and POSTing to either of their endpoints results in the behavior below.
POSTing a resource without required references works well.
I did a bit of digging and it appears that PersistentEntityResourceHandlerMethodArgumentResolver finds the MappingJackson2HttpMessageConverter just fine, but it ends up throwing an exception while checking whether the ObjectMapper can deserialize the type. The cause of the exception is a NullPointerException.
example POST w/ relations to /reservations:
{
"description" : "test_post",
"dateMade" : "2014-03-03T08:04:44.293-0600",
"timeLastChanged" : null,
"userLastChanged" : null,
"courseTypeId" : null,
"numCredits" : null,
"instructor" : null,
"numParticipants" : null,
"reservationType" : "MCU",
"status" : "REQUESTED",
"abstract" : null,
"requestor" : "http://localhost:8080/users/2",
"submitter" : "http://localhost:8080/users/2",
"conferences" : []
}
RESPONSE:
{
cause: null
message: "No suitable HttpMessageConverter found to read request body into object of type class domain.Reservation from request with content type of application/json!"
}
POST w/ no relations to /roomGroups:
{
"description" : "All Rooms",
"isOffNetwork" : false,
"roomGroupType" : "STANDARD"
}
RESPONSE:
201 Created
Is there something wrong about the JSON I am POSTing which is resulting in an NPE from the ObjectMapper? Is there a workaround of some kind? This was working for me in 2.0.0.RC1 using a slightly different scheme for including reference links in the JSON and since the version of the Jackson dependencies appears to have stayed the same I wonder what is causing this issue...
Thanks for any help!
UPDATE:
This issue now seems un-related to the associated entities...
I created a new #Entity ConnectionRequest as follows:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "CONNECTION_REQUEST_ID")
private Long id;
#Column(name = "FROM_ENTITY_ID", nullable = false)
private Long fromId;
#Column(name = "TO_ENTITY_ID", nullable = false)
private Long toId;
#Convert(converter = EntityTypeConverter.class)
#Column(name = "FROM_ENTITY_TYPE_ID", nullable = false)
private EntityType fromType;
#Convert(converter = EntityTypeConverter.class)
#Column(name = "TO_ENTITY_TYPE_ID", nullable = false)
private EntityType toType;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE})
#JoinColumn(name = "CONFERENCE_ID", nullable = false)
private Conference conference;
I can POST a new ConnectionRequest record with a Conference relation included in the json as such {"conference" : ".../conferences/1"}.
I am however still getting the same exception w/ this #Entity Reservation:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "RESERVATION_ID")
private Long id;
#Column(name = "DESCRIPTION", length = 50, nullable = false)
private String description;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "DATE_MADE", nullable = false)
private Date dateMade;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "TIME_LAST_CHANGED")
private Date timeLastChanged;
#Column(name = "USER_LAST_CHANGED")
private Integer userLastChanged; // TODO why is this an int?
#Column(name = "ABSTRACT", length = 2000)
private String _abstract;
#Column(name = "COURSE_TYPE_ID")
private Integer courseTypeId;
#Column(name = "NUMBER_OF_CREDITS")
private Integer numCredits;
#Column(name = "INSTRUCTOR", length = 255)
private String instructor;
#Column(name = "NUMBER_OF_PARTICIPANTS")
private Integer numParticipants;
#Convert(converter = ReservationTypeConverter.class)
#Column(name = "RESERVATION_TYPE_ID", nullable = false)
private ReservationType reservationType;
#Convert(converter = StatusConverter.class)
#Column(name = "STATUS_ID")
private Status status;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE})
#JoinColumn(name="REQUESTOR_USER_ID", nullable=false)
private User requestor;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE})
#JoinColumn(name="SUBMITTER_USER_ID", nullable=false)
private User submitter;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "reservation", cascade = {CascadeType.REMOVE})
private Set<Conference> conferences = new HashSet<>();
I'm not sure what's special about this class that's causing things to go awry...
The issue was the following:
Both of the non-postable entities had a property called _abstract due to it being a reserved word in Java. I had named the getter and setter for this property getAbstract() and setAbstract() respectively.
Jackson appears to have been throwing a null pointer exception since the getter and setter did not match the property name as expected.
When I changed the property name to resvAbstract and updated the accessors to getResvAbstract() and setResvAbstract() everything came together and started working.
I'm still curious about the change that led to this issue showing up, but I'm glad it's working!

Resources