I use GlassFish 4.1 (NetBeans), EclipseLink and the default MOXy json binding.
I have an entity with a composite primary key. I need a flat json structure for both input and output. It seems straight-forward, but...
If I do nothing special, I get a flatten json when marshalling, but the unmarshalling does not work (key = null).
If I add the annotation #XmlPath("."), then it is the opposite: the unmarshalling works, but the key fields are duplicated in the json.
Also, MOXy seems the add a type field in the json, which I never asked.
Entity classes:
The PK:
#Embeddable
public class SensorPk implements Serializable {
#Column(name = "sensor_id")
private Integer id;
#Column(name = "sensor_address")
#NotNull
#Size(min = 1, max = 10)
private String address = ADDRESS_DEFAULT;
// + getter/setters
}
The entity:
(the #org.eclipse.persistence.oxm.annotations.XmlPath is commented)
#Entity
#XmlElement
#Table(name = "sensors")
public class Sensor implements Serializable{
#EmbeddedId
// #XmlPath(".")
private SensorPk composedId;
#Column(name = "sensor_name")
#Size(min = 1, max = 45)
private String name;
// + getter/setters
}
The application configuration:
#javax.ws.rs.ApplicationPath("api")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
packages("ch.derlin.glf.bbdata");
}
}
I also tried to install jackson (1.X and 2.X), but impossible to make it work on glassfish 4.
The output without any annotation:
XML:
<sensors>
<sensor>
<address>noaddress</address>
<id>24</id>
<name>TEST</name>
</sensor>
</sensors>
JSON:
[
{
"type":"sensor",
"address":"noaddress",
"id":24,
"name":"TEST MONNEY"
}
]
Nice, but the unmarshalling of the same json fails: id and address are null. And also, what the hell is this type field ?
With annotation:
XML: idem.
JSON:
[
{
"type":"sensor",
"address":"noaddress",
"id":24,
"address":"noaddress",
"id":24,
"name":"TEST MONNEY"
}
]
But the unmarshalling works properly.
Any idea guys ?
Ok, for those with the same problem, I finally made it work be replacing #EmbeddedId with #IdClass.
The class SensorPk is left untouched, but the Sensor class is rewritten like this:
#IdClass(SensorPk.class)
public class Sensor implements Serializable {
#Column(name = "sensor_id")
#Id private Integer id;
#Column(name = "sensor_address")
#NotNull
#Size(min = 1, max = 10)
#Id private String address = ADDRESS_DEFAULT;
#Column(name = "sensor_name")
#Size(min = 1, max = 45)
private String name;
}
Changes are:
the annotation #IdClass is added at the top,
the SensorPk fields are copy pasted with the annotation #Id
Related
I have this JPA Class, where I have 3 columns id, name and date. The Database is already filled with data, where each entry has an id.
#Data
#Entity
#Table(name = "TEST", schema = "TESTSCHEMA")
public class TestDataJpaRecord implements Serializable {
private static final long serialVersionUID = 1L;
TestDataJpaRecord(){
// default constructor
}
public TestDataJpaRecord(
String name,
Date date,
){
this.name = name;
this.date = date;
}
#Id
#Column(name = "ID", nullable = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "TEST_SEQUENCE")
#SequenceGenerator(
sequenceName = "TEST_SEQUENCE", allocationSize = 1,
name = "TEST_SEQUENCEx")
private Long id;
#Column(name = "NAME")
private String name;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "DATE")
private Date date;
}
I created a JPA repository for all the data.
public interface TestDataJpaRecordRepository extends JpaRepository<TestDataJpaRecord, Long> {
}
I want to get the data from the DB in a JSON format.
Here is my Rest GET Api. Here I return the data as a string just, but I want to return them as JSON.
#GetMapping(value = "data/{id}")
private ResponseEntity<?> getDataFromTheDB(#PathVariable("id") Long id) {
// get one entry form the DB
TestDataJpaRecord testDataJpaRecord =testDataJpaRecordRepository.findOne(id);
// Here I want to return a JSON instead of a String
return new ResponseEntity<>(testDataJpaRecord.toString(), HttpStatus.OK);
}
Any idea on how I could return the data as JSON and not as a string from the DB?
I would very very much appreciate any suggestion.
If you have Jackson on the classpath which you should if you have used the spring-boot-starter-web then simply:
#GetMapping(value = "data/{id}")
private ResponseEntity<TestDataJpaRecord> getDataFromTheDB(#PathVariable("id") Long id) {
TestDataJpaRecord testDataJpaRecord =testDataJpaRecordRepository.findOne(id);
return new ResponseEntity.ok(testDataJpaRecord);
}
This assumes you have annoted your controller with #RestController rather than #Controller. If not then you can either do that or, annotate your controller method with #ResponseBody.
With Spring Data's web support enabled (which it should be by default with Spring Boot) then you can also simplify as below:
#GetMapping(value = "data/{id}")
private ResponseEntity<TestDataJpaRecord>
getDataFromTheDB(#PathVariable("id") TestDataJpaRecord record) {
return new ResponseEntity.ok(record);
}
See:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#core.web.basic.domain-class-converter
How to bind my new object user with an role object in my spring boot application when I receive an request post with a json/application that has all data for the new user?
What is the best approach (inform the role in the json)? If yes, how must be the json concerned the role information?
I will try to explain myself. First, I am sorry, I am not a native English speaker.
I want to create a new object user mapping the json received from a HTTP request post. The problem is that I have an internal object from my model named role. Roles are always either a common or an admin. Then I want to reference an already instantiated role object.
So, I want to know how to indicate the role from my user. You should consider that the model can not be modified because some internal team restriction. I don't know if the correct restful approach is send the role information in the json. How would you do this task?
My code
Class Controller
#RestController
public class UserController {
#RequestMapping(method=RequestMethod.POST, value="/users", produces = "application/json")
public #ResponseBody ResponseEntity<EntityUser> create(#Validated #RequestBody EntityUser user)
{
return new ResponseEntity<>(userService.add(user), HttpStatus.CREATED);
}
}
The Service class
#Service
public class UserService {
public EntityUser add(EntityUser user)
{
if (userRepository.findByName(user.getName()) == null)
return userRepository.save(user);
return null;
}
}
My plain object EntityUser (the json is mapped to it).
#Entity
public class EntityUser {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator="user_sequence")
private long id;
#Column(name = "name")
#NotNull
private String name;
#Column(name = "email", unique = true)
#NotNull
private String email;
#ManyToOne(fetch=FetchType.EAGER)
// #NotNull
private EntityRole role;
...
}
and finally my EntityRole class
#Entity
public class EntityRole {
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="role_sequence")
private long id;
#NotNull
private String label;
#NotNull
private String permission;
...
}
I am trying to implement a JsonView to selectively serialize fields from an entity but the json that is serialized has empty objects with no fields. Below is my code:
ViewClass:
public class AuditReportView {
public interface Summary {}
}
Entity:
#Entity
#SequenceGenerator(name = "AUDIT_REPORT_SEQUENCE_GENERATOR", sequenceName = "EJB_AUDIT_REPORT_SEQ", initialValue = 1, allocationSize = 1)
#Table(name = "DEVICE_AUDIT_REPORT")
#Data
public class AuditReport implements Serializable {
private static final long serialVersionUID = 1246376778314918671L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "AUDIT_REPORT_SEQUENCE_GENERATOR")
#Column(name = "ID", nullable = false)
#JsonView(AuditReportView.Summary.class)
private Long id;
#Column(name = "DEVICE_ID", nullable = false)
#JsonView(AuditReportView.Summary.class)
private String deviceId;
#Column(name = "REPORT_TIMESTAMP", nullable = false)
#JsonView(AuditReportView.Summary.class)
private Calendar reportTimestamp;
#Column(name = "USER_ID", nullable = false)
#JsonView(AuditReportView.Summary.class)
private long userId;
#Column(name = "USERNAME", nullable = false)
#JsonView(AuditReportView.Summary.class)
private String username;
#Column(name = "START_DATE", nullable = false)
#JsonView(AuditReportView.Summary.class)
private Calendar startDate;
#Column(name = "END_DATE", nullable = false)
#JsonView(AuditReportView.Summary.class)
private Calendar endDate;
#OneToMany(mappedBy = "auditReport", fetch = FetchType.LAZY, orphanRemoval = true, cascade={CascadeType.ALL})
private Set<AuditEntry> auditEntries = new HashSet<AuditEntry>();
}
Controller:
#JsonView(AuditReportView.Summary.class)
#RequestMapping(method = RequestMethod.GET, value = "auditReportSummary")
public #ResponseBody ResponseEntity<?> getAuditReportSummary()
{
final List<AuditReport> auditReports = auditDAO.getAuditReportSummary();
return new ResponseEntity<>(auditReports, HttpStatus.OK);
}
Json from Postman:
[
{},
{},
{}
]
The database only has 3 results and when I debug it is definately pulling them out, it is just that no members are being serialized. I'm using Spring 4.3.7 and Jackson 2.8.7. Any ideas of what could be wrong or where to start debugging the issue?
Thanks
You must create getters and setters methods for attributes. I did it and it worked.
I guess the issue is due to the #ResponceBody ResponseEntity<?>
Please try with the following code :
#JsonView(AuditReportView.Summary.class)
#RequestMapping(method = RequestMethod.GET, value = "auditReportSummary" produces = MediaType.APPLICATION_JSON_VALUE)
public List<AuditReport getAuditReportSummary()
{
final List<AuditReport> auditReports = auditDAO.getAuditReportSummary();
return auditReports;
}
I am not much sure about it, but you can try if it works..
Try adding a default constructor - ex:
public AuditReport() {}
The default constructor is generated by the java compiler if no custom constructor is specified in the code. However if a custom constructor is specified, the default constructor is no longer automatically added which can break serialization libraries / spring, etc..
BUT - you haven't specified a constructor - how could this be?
One thing I noticed is that you're using Lombok - due to the Data annotation. Lombok can generate constructors for classes. So its possible one of the annotations or libraries you're using is adding a constructor, making the compiler skip generation of a default constructor, which may be breaking your serialization.
So, I hope adding a default constructor works out for you.
I have One-To-Many relationship, here is my code
#Entity
#Table(name = "catalog")
public class Catalog {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "catalog_id")
private int catalog_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy="mycatalogorder")
private List<Order> orders;
#OneToMany(mappedBy="mycatalog")
private List<CatalogItem> items;
// setters and getters
}
#Entity
#Table(name = "catalogitem")
public class CatalogItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "catalogitem_id")
private int catalogitem_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#NotEmpty
#Column(name = "price", nullable = false)
private Double price;
#OneToOne(mappedBy="ordercatalogitem", cascade=CascadeType.ALL)
private OrderItem morderitem;
#ManyToOne
#JoinColumn(name="catalog_id", nullable=false)
private Catalog mycatalog;
// setters and getters
}
#Entity
#Table(name = "orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "order_id")
private int order_id;
#NotEmpty
#Size(min = 3, max = 255)
#Column(name = "name", nullable = false)
private String name;
#NotEmpty
#Size(min = 3, max = 1024)
#Column(name = "note", nullable = false)
private String note;
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(pattern = "ddmmYYYY HH:mm:ss")
#Column(name = "created", nullable = false)
private Date created;
#OneToMany(mappedBy="myorder")
private Set<OrderItem> orderItems;
#ManyToOne
#JoinColumn(name="catalog_id", nullable=false)
private Catalog mycatalogorder;
#PrePersist
protected void onCreate() {
created = new Date();
}
// setters and getters
}
#Entity
#Table(name = "orderitem")
public class OrderItem {
#Id
#Column(name="catalogitem_id", unique=true, nullable=false)
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="catalogitem"))
private int catalogitem_id;
#Column(name = "quantity")
private int quantity;
#OneToOne
#PrimaryKeyJoinColumn
private CatalogItem ordercatalogitem;
#ManyToOne
#JoinColumn(name="order_id", nullable=false)
private Order myorder;
// setters and getters
}
And I am getting the exception:
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write content: failed to lazily initialize a collection of
role: com.example.helios.model.Catalog.items, could not initialize
proxy - no Session; nested exception is
com.fasterxml.jackson.databind.JsonMappingException: failed to lazily
initialize a collection of role:
com.example.helios.model.Catalog.items, could not initialize proxy -
no Session
org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.writeInternal(AbstractJackson2HttpMessageConverter.java:271)
org.springframework.http.converter.AbstractGenericHttpMessageConverter.write(AbstractGenericHttpMessageConverter.java:100)
org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:222)
org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:183)
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:80)
org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:126)
My versions is:
SpringFramework 4.2.4.RELEASE
Hibernate 4.3.11.Final
Jackson 2.7.4
Jacksontype 2.7.1
This is the normal Hibernate behaviour
In one to many relations, hibernate loads the father entity (Catalog in your case) but it will load the children entities List (List items and List orders in your case) in a LAZY mode
This means you can't access to these objects because they are just proxies and not real objects
This is usefull in order to avoid to load the full DB when you execute a query
You have 2 solution:
Load children entities in EAGER mode (I strongly suggest to you to not do it because you can load the full DB.... but it is something related to your scenario
You don't serialize in your JSON the children entities by using the com.fasterxml.jackson.annotation.JsonIgnore property
Angelo
A third option which can be useful if you don't want to use EAGER mode and load up everything is to use Hibernate::initialize and only load what you need.
Session session = sessionFactory.openSession();
Catalog catalog = (Catalog) session.load(Catalog.class, catalogId);
Hibernate.initialize(shelf);
More information
I had the same problem but a fixed by:
#OneToMany
#JoinColumn(name = "assigned_ingredient", referencedColumnName = "ingredient_id")
#Fetch(FetchMode.JOIN) // Changing the fetch profile you can solve the problem
#Where(clause = "active_ind = 'Y'")
#OrderBy(clause = "meal_id ASC")
private List<Well> ingredients;
you can have more information here: https://vladmihalcea.com/the-best-way-to-handle-the-lazyinitializationexception/
It's caused by an infinite loop when parsing datas to JSON.
You can solve this by using #JsonManagedReference and #JsonBackReference annotations.
Definitions from API :
JsonManagedReference (https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonManagedReference.html) :
Annotation used to indicate that annotated property is part of two-way
linkage between fields; and that its role is "parent" (or "forward")
link. Value type (class) of property must have a single compatible
property annotated with JsonBackReference. Linkage is handled such
that the property annotated with this annotation is handled normally
(serialized normally, no special handling for deserialization); it is
the matching back reference that requires special handling
JsonBackReference: (https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonBackReference.html):
Annotation used to indicate that associated property is part of
two-way linkage between fields; and that its role is "child" (or
"back") link. Value type of the property must be a bean: it can not be
a Collection, Map, Array or enumeration. Linkage is handled such that
the property annotated with this annotation is not serialized; and
during deserialization, its value is set to instance that has the
"managed" (forward) link.
Example:
Owner.java:
#JsonManagedReference
#OneToMany(mappedBy = "owner", fetch = FetchType.EAGER)
Set<Car> cars;
Car.java:
#JsonBackReference
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "owner_id")
private Owner owner;
Another solution is to use #JsonIgnore which will just set null to the field.
Here is my solution for this task with Hibernate. I marked hibernate releation with #JsonIgnore and use custom field for jackson, in which I check if the field is loaded. If you need serialize collection to json then you should manualy call collection getter during hibernate transaciton.
#JsonIgnore
#OneToMany(mappedBy = "myorder")
private List<OrderItem> orderItems = new ArrayList<>();
#JsonProperty(value = "order_items", access = JsonProperty.Access.READ_ONLY)
private List<OrderItem> getOrderItemsList() {
if(Hibernate.isInitialized(this.relatedDictionary)){
return this.relatedDictionary;
} else{
return new ArrayList<>();
}
}
#JsonProperty(value = "order_items", access = JsonProperty.Access.WRITE_ONLY)
private void setOrderItemsList(List<OrderItem> orderItems) {
this.orderItems = orderItems;
}
I know this is an old post but this might still help someone facing a similar issue. To solve the problem, iterate through the list of items and set the lazy-loadable collection to null. Then set your mapper to include NON-NULL
for (Catalog c : allCatalogs) {
c.setItems(null);
}
objectMapper.setSerializationInclusion(Include.NON_NULL)
Using FetchType.LAZY , if still getting the error "Could not write content: failed to lazily initialize a collection of role" , that may be probably caused by somewhere in the logic (perhaps in a controller) , Catalog is being tried to be deserialized that contains list of catalog items which is a proxy but the transaction has already ended to get that.
So create a new model ('CatalogResource' similar to catalog but without the list of items).
Then create a catalogResource object out of the Catalog (which is returned from the query)
public class CatalogResource {
private int catalog_id;
private String name;
private List<Order> orders;
}
I think the best solution to your problem (which also is the simplest) is to set your FetchType to LAZY and simply annotate the oneToMany collection fields using #transient.
Setting FetchType to EAGER isn't a good idea most times.
Best of luck.
"You don't serialize in your JSON the children entities by using the com.fasterxml.jackson.annotation.JsonIgnore property"
Add #JsonIgnore for hibernate lazy loading properties eg. #ManyToOne. That should work
I have such two entities:
#XmlRootElement(name = "provider")
#XmlAccessorType(XmlAccessType.PROPERTY)
#Entity
public class Provider {
//...
#XmlElementWrapper(name = "industries")
#XmlElement(name = "industry")
#XmlIDREF
#ManyToMany(mappedBy = "providers", fetch = FetchType.EAGER)
public Set<Industry> getIndustries() {
return industries;
}
}
And second entity:
#XmlRootElement
#XmlAccessorType(XmlAccessType.PROPERTY)
#Entity
public class Industry implements Serializable {
//...
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "industry_id", nullable = false /*, columnDefinition = "BIGINT UNSIGNED" */)
public Long getIndustryId() {
return industryId;
}
public void setIndustryId(Long industryId) {
this.industryId = industryId;
}
//...
#XmlID
#Transient
public String getSelfLink() {
return getIndustryId().toString();
}
}
And now using RESTEasy to generate both XML and JSON I'm getting expected result only in XML and in JSON there is instead of id return entire Industry entity + exception thrown.
Correct XML received:
<provider>
<userId>8</userId>
//.... - deleted for simplicity
<industries>
<industry>1</industry>
</industries>
</provider>
But incorrect result in JSON:
{"provider":{ /* deleted for simplicity */
"industry":[{"industryId":1,"name":"Bran?a medyczna","description":null,"providers"}]}}
And "industry" : 1 should return only identifier!
Exception thrown:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: pl.salonea.entities.Industry.providers, could not initialize proxy - no Session (through reference chain: pl.salonea.jaxrs.utils.ResourceList["resources"]->java.util.ArrayList[0]->pl.salonea.entities.Provider["industry"]->org.hibernate.collection.internal.PersistentSet[0]->pl.salonea.entities.Industry["providers"])
at com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:210)
And:
Caused by: org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: pl.salonea.entities.Industry.providers, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:575)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:214)
at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:155)
at org.hibernate.collection.internal.PersistentSet.size(PersistentSet.java:160)
at com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serialize(CollectionSerializer.java:88)
I experienced the same thing and I solved it by adding an #XmlAttribute annotation to the #XmlID. Here's what worked for me. (Note: my code is written in Ceylon, not Java.)
shared entity class Employee(name, manager) {
xmlAttribute xmlID
generatedValue id
shared late Integer id;
column { length = 50; }
shared String name;
shared variable Integer? year = null;
xmlIDREF
manyToOne
shared Employee? manager;
xmlTransient
oneToMany { mappedBy = "manager"; }
shared Set<Employee> employees = set {};
}
I have no clue why this is necessary, especially since AFAIK #XmlAttribute vs #XmlElement is otherwise not meaningful for JSON.