JAXB returns mutually exculasive annotations exception - java

I am receiving following response and am trying to to parse it using JAXB but following code returns error below.
Exception in thread "main" org.springframework.http.converter.HttpMessageConversionException: Could not instantiate JAXBContext for class [class com.easytobook.EasytobookResponse]: 1 counts of IllegalAnnotationExceptions; nested exception is com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions
com.easytobook.Roomtype#totalTaxesAndFees has mutually exclusive annotations #javax.xml.bind.annotation.XmlAttribute and #javax.xml.bind.annotation.XmlElement
this problem is related to the following location:
at #javax.xml.bind.annotation.XmlAttribute(name=EUR, required=false, namespace=##default)
I know exception is related to totalTaxsesAndFees but I am not sure how to solve the issue.
EasytobookResponse response = restTemplate.postForObject(
url, easy, EasytobookResponse.class);
System.err.println("RESPONSE >>>>"
+ response.getResponse().getHotelInfo().get(0).getId());
#XmlRootElement(name = "EasytobookResponse")
#XmlAccessorType(XmlAccessType.FIELD)
public class EasytobookResponse {
#XmlAttribute(name = "target")
private String target;
#XmlAttribute(name = "username")
private String username;
#XmlElement(name = "Response")
private Response response;
getters and setters
#XmlRootElement(name = "Response")
#XmlAccessorType(XmlAccessType.FIELD)
public class Response {
#XmlElementWrapper(name="Hotelinfo")
#XmlElement(name="Hotel")
private List<Hotel> hotelInfo;
getters and setters
#XmlRootElement(name = "Hotel")
#XmlAccessorType(XmlAccessType.FIELD)
public class Hotel {
#XmlElement(name = "Id")
private int id;
#XmlElement(name = "FacilityGroups")
private FacilityGroups facilityGroups;
#XmlElement(name = "exact")
private int exact;
#XmlElementWrapper(name = "Roomtype")
#XmlElement(name = "Roomtype")
private List<Roomtype> roomType;
getters and setters
#XmlRootElement(name = "FacilityGroups")
#XmlAccessorType(XmlAccessType.FIELD)
public class FacilityGroups {
#XmlElement(name = "HandicapFacilities")
private int handicapFacilities;
#XmlElement(name = "HasInternet")
private int hasInternet;
#XmlElement(name = "HasParking")
private int hasParking;
#XmlElement(name = "PetsAllowed")
private int petsAllowed;
#XmlElement(name = "HasChildDiscount")
private int hasChildDiscount;
#XmlElement(name = "HasSwimmingPool")
private int hasSwimmingPool;
#XmlElement(name = "HasAirCon")
private int hasAirCon;
#XmlElement(name = "HasFitnessFacilities")
private int hasFitnessFacilities;
#XmlElement(name = "NonSmoking")
private int nonSmoking;
getters and setters
#XmlRootElement(name = "Roomtype")
#XmlAccessorType(XmlAccessType.FIELD)
public class Roomtype {
#XmlAttribute(name = "parentid")
private String parentid;
#XmlElement(name = "Roomid")
private long roomId;
#XmlElement(name = "Roomname")
private String roomName;
#XmlElement(name = "Capacity")
private int capacity;
#XmlElement(name = "Available")
private int available;
#XmlElement(name = "Totalrate")
private double totalRate;
#XmlAttribute(name="currency")
private String currency;
#XmlAttribute(name="EUR")
#XmlElement(name = "TotalTaxesAndFees")
private double totalTaxesAndFees;
#XmlElement(name = "TotalChargeable")
private double totalChargeable;
#XmlElement(name = "Reckratetotal")
private double reckRateTotal;
#XmlElement(name = "Breakfast")
private int breakfast;
#XmlElement(name = "Booklink")
private String bookLink;
#XmlElement(name = "Hoteldetailslink")
private String hotelDetailsLink;
#XmlElement(name = "Rtoken")
private String rToken;
#XmlElement(name = "Specialoffers")
private String specialOffers;
#XmlElement(name = "Bookingconditions")
private BookingConditions bookingConditions;
#XmlElement(name = "Rateinfo")
private RateInfo rateInfo;
getters and setters
#XmlRootElement(name = "Bookingconditions")
#XmlAccessorType(XmlAccessType.FIELD)
public class BookingConditions {
#XmlElement(name = "Bookable")
private int bookable;
#XmlElement(name = "Chargepoint")
private String chargePoint;
#XmlElement(name = "Requirements")
private Requirement requirement;
getters and setters
#XmlRootElement(name = "Requirements")
#XmlAccessorType(XmlAccessType.FIELD)
public class Requirement {
#XmlElement(name = "Capacity")
private int capacity;
#XmlElement(name = "Creditcardcvc")
private int creditCardCvc;
#XmlElement(name = "Billingaddress")
private int billingAddress;
getters and setters
#XmlRootElement(name = "Rateinfo")
#XmlAccessorType(XmlAccessType.FIELD)
public class RateInfo {
#XmlElement(name = "Underoccupancy")
private int underoccupancy;
#XmlElement(name = "Earlybooking")
private int earlyBooking;
#XmlElement(name = "Lastminutebooking")
private int lastMinuteBooking;
#XmlElement(name = "Nonrefundable")
private int nonRefundable;
#XmlElement(name = "Breakfastincluded")
private int breakfastIncluded;
getters and setters
Response
<?xml version="1.0" encoding="UTF-8"?>
<EasytobookResponse>
<Response target="test" username="project121">
<Hotelinfo>
<Hotel>
<Id>436924</Id>
<FacilityGroups>
<HandicapFacilities>0</HandicapFacilities>
<HasInternet>1</HasInternet>
<HasParking>1</HasParking>
<PetsAllowed>0</PetsAllowed>
<HasChildDiscount>0</HasChildDiscount>
<HasSwimmingPool>0</HasSwimmingPool>
<HasAirCon>1</HasAirCon>
<HasFitnessFacilities>0</HasFitnessFacilities>
<NonSmoking>1</NonSmoking>
</FacilityGroups>
<exact>436924</exact>
<Roomtype>
<Roomid parentid="2069840686">2069840686</Roomid>
<Roomname>Two Persons Standard + Breakfast</Roomname>
<Capacity>2</Capacity>
<Available>8</Available>
<Totalrate currency="AUD" EUR="1204.3398">1800</Totalrate>
<TotalTaxesAndFees currency="AUD" EUR="0">0
</TotalTaxesAndFees>
<TotalChargeable currency="AUD" EUR="1204.3398">1800
</TotalChargeable>
<Reckratetotal currency="AUD" EUR="1204.3398">1800
</Reckratetotal>
<Breakfast>1</Breakfast>
<Booklink>https://stage-site.easytobook.us/booking_screen.php?hotel_id=436924&exact=436924&city_id=23&arrival=20-11-2016&departure=29-11-2016&currency=EUR&lang=en&room[2069840686]=1&persons=2&rooms=1&amu=280828207&utm_source=project121&utm_medium=affiliate&utm_term=Sydney&utm_content=etb5&utm_campaign=en&rtoken=1VSwnz7whjuxw-RGo5aP6cp-XmdDQSHC7twXmVrwPejOzYDFXdGY_bdbs9xbHuP8xU83qSmzJGz_vgQZHjeE5kVENJkuPBAtuKd6jTqIWCk,
</Booklink>
<Hoteldetailslink>http://stage-site.easytobook.us/hotel_proxy.php?hotel_id=436924&lang=en&arrival=20-11-2016&departure=29-11-2016&currency=EUR&prs_arr%5B0%5D=2&amu=280828207&utm_source=project121&utm_medium=affiliate&utm_term=Sydney&utm_content=etb5&utm_campaign=en&rtoken=1VSwnz7whjuxw-RGo5aP6cp-XmdDQSHC7twXmVrwPejOzYDFXdGY_bdbs9xbHuP8xU83qSmzJGz_vgQZHjeE5kVENJkuPBAtuKd6jTqIWCk,
</Hoteldetailslink>
<Rtoken>1VSwnz7whjuxw-RGo5aP6cp-XmdDQSHC7twXmVrwPejOzYDFXdGY_bdbs9xbHuP8xU83qSmzJGz_vgQZHjeE5kVENJkuPBAtuKd6jTqIWCk,
</Rtoken>
<Specialoffers />
<Bookingconditions>
<Bookable>1</Bookable>
<Chargepoint>hotel</Chargepoint>
<Requirements>
<Capacity>1</Capacity>
<Creditcardcvc>1</Creditcardcvc>
<Billingaddress>1</Billingaddress>
</Requirements>
</Bookingconditions>
<Rateinfo>
<Underoccupancy>0</Underoccupancy>
<Earlybooking>0</Earlybooking>
<Lastminutebooking>0</Lastminutebooking>
<Nonrefundable>0</Nonrefundable>
<Breakfastincluded>1</Breakfastincluded>
</Rateinfo>
</Roomtype>
<Roomtype>
<Roomid parentid="1379794752">1379794752</Roomid>
<Roomname>Two Persons Superior + Breakfast</Roomname>
<Capacity>2</Capacity>
<Available>4</Available>
<Totalrate currency="AUD" EUR="1264.5568">1890</Totalrate>
<TotalTaxesAndFees currency="AUD" EUR="0">0
</TotalTaxesAndFees>
<TotalChargeable currency="AUD" EUR="1264.5568">1890
</TotalChargeable>
<Reckratetotal currency="AUD" EUR="1264.5568">1890
</Reckratetotal>
<Breakfast>1</Breakfast>
<Booklink>https://stage-site.easytobook.us/booking_screen.php?hotel_id=436924&exact=436924&city_id=23&arrival=20-11-2016&departure=29-11-2016&currency=EUR&lang=en&room[1379794752]=1&persons=2&rooms=1&amu=280828207&utm_source=project121&utm_medium=affiliate&utm_term=Sydney&utm_content=etb5&utm_campaign=en&rtoken=ocGkvI7xuJJwz1BWWYNBztr7n-__tI8fVNfz3cZsrwRMGrtuHAEGziCH-0poK2ZoveEs-4Fz1_Y4U8pwE4KGKjJc4iwdSKM4ewIJwMU8omA,
</Booklink>
<Hoteldetailslink>http://stage-site.easytobook.us/hotel_proxy.php?hotel_id=436924&lang=en&arrival=20-11-2016&departure=29-11-2016&currency=EUR&prs_arr%5B0%5D=2&amu=280828207&utm_source=project121&utm_medium=affiliate&utm_term=Sydney&utm_content=etb5&utm_campaign=en&rtoken=ocGkvI7xuJJwz1BWWYNBztr7n-__tI8fVNfz3cZsrwRMGrtuHAEGziCH-0poK2ZoveEs-4Fz1_Y4U8pwE4KGKjJc4iwdSKM4ewIJwMU8omA,
</Hoteldetailslink>
<Rtoken>ocGkvI7xuJJwz1BWWYNBztr7n-__tI8fVNfz3cZsrwRMGrtuHAEGziCH-0poK2ZoveEs-4Fz1_Y4U8pwE4KGKjJc4iwdSKM4ewIJwMU8omA,
</Rtoken>
<Specialoffers />
<Bookingconditions>
<Bookable>1</Bookable>
<Chargepoint>hotel</Chargepoint>
<Requirements>
<Capacity>1</Capacity>
<Creditcardcvc>1</Creditcardcvc>
<Billingaddress>1</Billingaddress>
</Requirements>
</Bookingconditions>
<Rateinfo>
<Underoccupancy>0</Underoccupancy>
<Earlybooking>0</Earlybooking>
<Lastminutebooking>0</Lastminutebooking>
<Nonrefundable>0</Nonrefundable>
<Breakfastincluded>1</Breakfastincluded>
</Rateinfo>
</Roomtype>
</Hotel>
.....

The error message says it all:
com.easytobook.Roomtype#totalTaxesAndFees has mutually exclusive annotations #javax.xml.bind.annotation.XmlAttribute and #javax.xml.bind.annotation.XmlElement
Your code has:
#XmlRootElement(name = "Roomtype")
#XmlAccessorType(XmlAccessType.FIELD)
public class Roomtype {
. . .
#XmlAttribute(name="EUR")
#XmlElement(name = "TotalTaxesAndFees")
private double totalTaxesAndFees;
That is not allowed.
Your problem is these XML elements:
<Roomtype>
<Roomid parentid="2069840686">2069840686</Roomid>
. . .
<Totalrate currency="AUD" EUR="1204.3398">1800</Totalrate>
<TotalTaxesAndFees currency="AUD" EUR="0">0</TotalTaxesAndFees>
<TotalChargeable currency="AUD" EUR="1204.3398">1800</TotalChargeable>
<Reckratetotal currency="AUD" EUR="1204.3398">1800</Reckratetotal>
For example, the following field annotation tells JAXB that parentid is an attribute of Roomtype, when you want it as an attribute of Roomid:
public class Roomtype {
#XmlAttribute(name = "parentid")
private String parentid;
Instead, in order to have an attribute on the <Roomid> element, you need a Roomid class (and you don't need the #XmlRootElement):
#XmlAccessorType(XmlAccessType.FIELD)
public class Roomid {
#XmlAttribute(name = "parentid")
private String parentid;
#XmlValue
private long roomId;
}
Your 4 total elements have the same structure, so they can share a single class:
#XmlAccessorType(XmlAccessType.FIELD)
public class Total {
#XmlAttribute(name = "currency")
private String currency;
#XmlAttribute(name = "EUR")
private double eur;
#XmlValue
private double total;
}
These two classes are then used like this:
#XmlAccessorType(XmlAccessType.FIELD)
public class Roomtype {
#XmlElement(name = "Roomid")
private Roomid roomid;
. . .
#XmlElement(name = "Totalrate")
private Total totalrate;
#XmlElement(name = "TotalTaxesAndFees")
private Total totalTaxesAndFees;
#XmlElement(name = "TotalChargeable")
private Total totalChargeable;
#XmlElement(name = "Reckratetotal")
private Total reckratetotal;

Related

Order Attribute in JacksonXmlProperty

In the below code, I am expecting the attributes in the order of properties defined, but not able to get it. The order is always mixed up.
Parent Tag
#JacksonXmlRootElement
public class ParentSpec{
#JacksonXmlProperty(localName = "parentSpec")
#JacksonXmlElementWrapper
public MySpec mySpec;
}
ChildTag
public class MySpec {
#JacksonXmlProperty(localName = "mySpec")
#JacksonXmlElementWrapper(useWrapping = false)
public List<MyData> myDataList;
}
Error Child
#JsonPropertyOrder({"id", "type", "name", "data"})
public class MyData{
#JacksonXmlProperty(isAttribute = true)
public String id;
#JacksonXmlProperty(isAttribute = true)
public String type;
#JacksonXmlProperty(isAttribute = true)
public String name;
#JacksonXmlProperty(isAttribute = true)
public String data;
//Other Pojo
}
I tried using the #JsonPropertyOrder, but it works only for elements it seems.

How to marshall XML with dynamic element using JAXB2

I'm developing an SOAP client and I'm looking for more sophisticated solution to marshalling objects into XML-string using Jaxb2 library.
The goal is to marshall an object, which acts as a wrapper for any-type element. Example:
<Action id="5">
<Employee id="10">
<Name>John</Name>
</Employee>
</Action>
or.
<Action id="5">
<Department id="ABC">
<Name>Economy Department</Name>
<ParentId>CDE</ParentId>
</Department>
</Action>
Note: The xml root (Action) contains either "Employee" or "Department" or anything else.
My current working solution is as follows:
#XmlRootElement(name = "Action")
abstract class Action {
#XmlAttribute(name = "id")
protected String id;
}
class EmployeeAction extends Action {
#XmlElement(name = "Employee")
protected Employee employee;
}
class DepartmentAction extends Action {
#XmlElement(name = "Department")
protected Department department;
}
This works fine, but I'm looking for more universal solution, without the need to create class for each type (*Action extends Action). Name of the element must always be the same as the className of the (dynamic) type. My idea is something like this:
public class Action<T> {
#XmlAttribute(name = "id")
protected String id;
#XmlElement(name = "getClass().getSimpleName()") //???
protected T element;
}
... and marshalling something like:
Action<?> action = ...;
JAXBContext context = JAXBContext.newInstance(Action.class, action.getElement().getClass());
Marshaller marshaller = context.createMarshaller();
try(ByteArrayOutputStream outStream = new ByteArrayOutputStream()) {
marshaller.marshal(action, outStream);
return outStream.toString();
}
Is something like this possible?
Thanks in advance.
You can do something like this for the above-provided XML:
METHOD-1
Action.class;
#XmlRootElement(name = "Action")
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class Action {
#XmlAttribute(name = "id")
protected String id;
#XmlElements({
#XmlElement(name = "Employee", type = Employee.class),
#XmlElement(name = "Department", type = Department .class),
})
private ActionItem type;
}
ActionItem.class;
#Data
#XmlAccessorType(XmlAccessType.NONE)
public class ActionItem {
#XmlElement(name = "Name")
protected String name;
}
Employee.class;
#Data
#XmlAccessorType(XmlAccessType.NONE)
public class Employee extends ActionItem {
#XmlAttribute(name = "id")
private String id;
}
Department.class;
#Data
#XmlAccessorType(XmlAccessType.NONE)
public class Department extends ActionItem {
#XmlAttribute
private String id;
#XmlElement(name = "ParentId")
private String parentID;
}
Main.class:
public class Main {
public static void main(String[] args) throws JAXBException, XMLStreamException {
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("action.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
final Unmarshaller unmarshaller = JAXBContext.newInstance(Action.class).createUnmarshaller();
final Action action = unmarshaller.unmarshal(xmlStreamReader, Action.class).getValue();
System.out.println(action.toString());
Marshaller marshaller = JAXBContext.newInstance(Action.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(action, System.out);
}
}
If you provided Employee XML then it produces the following result:
Action(id=5, type=Employee(id=10))
<Action id="5">
<Employee id="10">
<Name>John</Name>
</Employee>
</Action>
If you provide the Department XML then it produces the following result:
Action(id=5, type=Department(parentID=CDE))
<Action id="5">
<Department>
<Name>Economy Department</Name>
<ParentId>CDE</ParentId>
</Department>
</Action>
METHOD-2
Creating the interface and using it:
public interface ActionItem2 {
}
Action.class uses the created interface.
#XmlRootElement(name = "Action")
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class Action {
#XmlAttribute(name = "id")
protected String id;
#XmlElements({
#XmlElement(name = "Employee", type = Employee.class),
#XmlElement(name = "Department", type = Department .class),
})
private ActionItem2 type;
}
Employee.class which implements the created interface
#Data
#XmlAccessorType(XmlAccessType.NONE)
public class Employee implements ActionItem2 {
#XmlAttribute(name = "id")
private String id;
#XmlElement(name = "Name")
protected String name;
}
Department.class which implements the created interface
#Data
#XmlAccessorType(XmlAccessType.NONE)
public class Department implements ActionItem2 {
#XmlAttribute
private String id;
#XmlElement(name = "ParentId")
private String parentID;
#XmlElement(name = "Name")
protected String name;
}
Main.class(No changes)
public class Main {
public static void main(String[] args) throws JAXBException, XMLStreamException {
final InputStream inputStream = Unmarshalling.class.getClassLoader().getResourceAsStream("action.xml");
final XMLStreamReader xmlStreamReader = XMLInputFactory.newInstance().createXMLStreamReader(inputStream);
final Unmarshaller unmarshaller = JAXBContext.newInstance(Action.class).createUnmarshaller();
final Action action = unmarshaller.unmarshal(xmlStreamReader, Action.class).getValue();
System.out.println(action.toString());
Marshaller marshaller = JAXBContext.newInstance(Action.class).createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
marshaller.marshal(action, System.out);
}
}
The result would be the same.
Method - 3
If you don't want to modify your POJO then you can do something like this:
#XmlRootElement(name = "Action")
#Data
#XmlAccessorType(XmlAccessType.FIELD)
public class Action {
#XmlAttribute(name = "id")
protected String id;
#XmlElements({
#XmlElement(name = "Employee", type = Employee.class),
#XmlElement(name = "Department", type = Department .class),
})
private Object type;
}
Employee.class:
#Data
#XmlAccessorType(XmlAccessType.NONE)
public class Employee {
#XmlAttribute(name = "id")
private String id;
#XmlElement(name = "Name")
protected String name;
}
Department.class:
#Data
#XmlAccessorType(XmlAccessType.NONE)
public class Department {
#XmlAttribute
private String id;
#XmlElement(name = "ParentId")
private String parentID;
#XmlElement(name = "Name")
protected String name;
}
This will provide the same output.

AttributeOverride MappedSuperClass attribute with a different type

I have an Abstract class used as MappedSuperclass like :
#MappedSuperclass
#IdClass(LogId.class)
public abstract class Log {
private LocalDateTime moment;
private String pid;
// attributes omitted
private String type;
private String message;
#Id
public LocalDateTime getMoment() { return moment; }
#Id
public String getPid() { return pid; }
// getters/setters omitted
}
My Log class provides to every entity the same structure with standard attributes type and message. Each entity represents a table in my database which share every attributes (#Id and // omitted ones). Columns type and message might have different names & types.
My first attempt was :
#Entity
#Table(name = "flow_catcher")
public class FlowCatcher extends Log {
#Override
#Column(name = "message_type")
public String getType() {
return super.getType();
}
#Override
#Column(name = "count")
#Convert(converter = StringToIntegerDbConverter.class)
public String getMessage() {
return super.getMessage();
}
}
Following this post I used #AttributeOverride like :
#Entity
#Table(name = "flow_catcher")
#AttributeOverride(name = "type", column = #Column(name = "message_type"))
#AttributeOverride(name = "message", column = #Column(name = "count"))
public class FlowCatcher extends Log {
}
But it seems impossible to use any converter to get data count as a String and not an Integer.
Here is my error log:
Unable to build Hibernate SessionFactory; nested exception is
org.hibernate.tool.schema.spi.SchemaManagementException:
Schema-validation: wrong column type encountered in column [count] in table [flow_catcher];
found [int (Types#INTEGER)], but expecting [varchar(255) (Types#VARCHAR)]
Is there any way to get my column count as a String and not an Integer ?
PS: Hibernate hbm2ddl.auto is set on validate and database scheme can't change.
EDIT 1: I found a working solution but it uses another (unused) column in my database other_string_column which doesn't sound clean to me :
#Entity
#Table(name = "flow_catcher")
#AttributeOverride(name = "type", column = #Column(name = "message_type"))
#AttributeOverride(name = "message", column = #Column(name = "other_string_column"))
public class FlowCatcher extends Log{
private Integer count;
#Override
public String getMessage() {
return this.count.toString();
}
public Integer getCount() { return count; }
public void setCount(Integer count) { this.count = count; }
}
You can go with generic
#MappedSuperclass
#IdClass(LogId.class)
public class Log<T extends Comparable> implements Serializable {
#Id
private LocalDateTime moment;
#Id
private String pid;
//additional required fields here
// Do NOT SPECIFY MAPPING
private T message;
}
#Entity
#Table(name = "flow_catcher")
#AttributeOverride(name = "type", column = #Column(name = "message_type"))
#AttributeOverride(name = "message", column = #Column(name = "count", columnDefinition = "BIGINT(15)"))
public class FlowCatcher extends Log<Integer> {
private static final long serialVersionUID = -3629698185247120860L;
}
#Entity
#Table(name = "flow_catcher_another_sample")
#AttributeOverride(name = "type", column = #Column(name = "message_type"))
#AttributeOverride(name = "message", column = #Column(name = "message", columnDefinition = "VARCHAR(20)"))
public class FlowCatcherString extends Log<String> {
private static final long serialVersionUID = -3629698185247120860L;
}

Specification Predicate to Search Nested jsonb column Objects

I have stored jsonb object in postgresql like
{"sample": {"lastName": "Sahani", "firstName": "Sanjay"}, "address": "Address2", "bedrooms": 2, "postcode": "40 BS", "propertyType": "Type 2"}
My table name is valuation_report_json
and jsonb column name is params
I can get address using Specification but unable to get sample's firstName
My specification is
public class ValuationReportJSONSpecification implements Specification<ValuationReportJSON>{
private String locale;
private String fieldToSearch;
private String localeParameter;
public ValuationReportJSONSpecification(String locale, String fieldToSearch,String localeParameter) {
this.locale = locale;
this.fieldToSearch = fieldToSearch;
this.localeParameter=localeParameter;
}
#Override
public Predicate toPredicate(Root<ValuationReportJSON> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// TODO Auto-generated method stub
System.err.println("INSIDE");
// return cb.equal(cb.function("jsonb_extract_path_text", String.class, root.<String>get("params"), cb.literal(this.locale)), this.fieldToSearch);
return cb.equal(cb.function("jsonb_extract_path_text", String.class,root.<String>get("params"), cb.literal(this.locale),cb.literal(this.localeParameter)),this.fieldToSearch);
}
}
My models are
ValuationReportJSON :
#Data
#NoArgsConstructor
#Entity
#JsonIgnoreProperties(ignoreUnknown = true)
#Table(name = "valuation_report_json")
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
public class ValuationReportJSON implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Type(type = "jsonb")
#Column(name = "parameters", nullable = false,columnDefinition = "jsonb")
private Params params;
#OneToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "property_id", nullable = true)
private Property property;
#Column(name = "entry_id", nullable = true)
private Integer entryId;
#Column(name = "report_data", nullable = true, columnDefinition = "text")
private String reportData;
#Column(name = "entry_date", nullable = true)
#CreationTimestamp
private Timestamp entryDate;
#Column(name = "modified_date", nullable = true)
#UpdateTimestamp
private Timestamp modifiedDate;
//getter setter
}
Params
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Params implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
public String address;
private String postcode;
private SampleObj sample;
private int bedrooms;
//getter setter
}
SampleObj:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class SampleObj implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
private String firstName;
private String lastName;
//getter setter
}
I cannot use join in predicate as my objects are not having mapping or not an entity.
because i have seen same question but objects were entity :Specification/Predicate to Search Nested Objects

Saving nested JSON data into a MySQL database using Hibernate

I am strucked with this issue. I have created a POJO for nested JSON and I am getting data in MarketPrice object where marketPrices is an ArrayList which has two elements.
This is MarketPrice POJO class and actually I need to save it into the MarketPrice table. I.e, entire JSON object. But I have two entities. How can this be possible?
MarketPrice.java
#Entity
#Table(name = "MarketPrice")
public class MarketPrice {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "itemId")
private Long itemId;
#Column(name = "analysisDate")
private Date analysisDate;
#Column(name = "marketName")
private String marketName;
#Column(name = "category")
private String category;
#Column(name = "marketPlace")
private String marketPlace;
#Column(name = "state")
private String state;
#Column(name = "district")
private String district;
public ArrayList<Items> marketPrices;
Items.java
public class Items implements Serializable {
private static final long serialVersionUID = -2428562977284114465L;
#Id
#Column(name="id")
private int id;
#Column(name = "itemName")
private String itemName;
#Column(name = "unitofPrice")
private String unitofPrice;
#Column(name = "minimumPrice",columnDefinition = "Float(10,2)")
private Float minimumPrice;
#Column(name = "maximumPrice",columnDefinition = "Float(10,2)")
private Float maximumPrice;
This is my nested JSON data I'm getting from at the server side in the controller:
JSON data in marketPrices
{
"marketPrices": [{
"itemName": "Mango",
"unitofPrice": "Kg",
"minimumPrice": "10",
"maximumPrice": "20"
}, {
"itemName": "Grapes",
"unitofPrice": "Kg",
"minimumPrice": "30",
"maximumPrice": "40"
}],
"state": "xyz",
"district": 4,
"marketPlace": 5001,
"marketName": "pmc",
"category": "Fruits"
}
Controller.java
#RequestMapping(value = {"/saveAnalysis"} , method = RequestMethod.POST,consumes = "application/json")
#ResponseBody
public MarketPrice bulkSaveMarketAnalysis(#RequestBody
String marketPrices, HttpServletResponse response,
HttpServletRequest request) throws JsonProcessingException, IOException, JSONException{
MarketPrice marketPrice1 = new MarketPrice();
System.out.println("Json Data"+marketPrices);//here am getting valid nested json from UI
Gson gson = new Gson();
MarketPrice marketPrice = gson.fromJson(marketPrices, MarketPrice.class);//converting it into Entity type all values are present in it.
//Am strucked after this,How to save nested json into DB.
String marketDataResponse = analyserService.saveListOfMarketPrice(marketPrice);
marketPrice1.setStatusMessage("success");
return marketPrice1;
}
DAO.java
public String saveListOfMarketPrice(MarketPrice marketPrice) {
System.out.println("In Analyser DAO fro bulk saving");
final Session session = getSession();
session.beginTransaction();
marketPrice.setAnalysisDate(new Date());
for (Items item : marketPrice.marketPrices) {
marketPrice.currentItem = item;
marketPrice.setItemName(marketPrice.currentItem.getItemName());
marketPrice.setUnitofPrice(marketPrice.currentItem.getUnitofPrice());
marketPrice.setMinimumPrice(marketPrice.currentItem.getMinimumPrice());
marketPrice.setMaximumPrice(marketPrice.currentItem.getMaximumPrice());
session.save(marketPrice);
}
session.getTransaction().commit();
session.close();
return "success";
}
After making these changes to DAO it saved finally
Thank you.
As discussed in the comments, you can modify your code as below to make it work as expected.
MarketPrice.java
#Entity
#Table(name = "MarketPrice")
public class MarketPrice {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "itemId")
private Long itemId;
#Column(name = "analysisDate")
private Date analysisDate;
#Column(name = "marketName")
private String marketName;
#Column(name = "category")
private String category;
#Column(name = "marketPlace")
private String marketPlace;
#Column(name = "state")
private String state;
#Column(name = "district")
private String district;
#Transient
public Items currentItem;
#Column(name = "itemName")
public String getItemName() {
return this.currentItem.itemName;
}
#Column(name = "unitofPrice")
public String getUnitofPrice() {
return this.currentItem.unitofPrice;
}
#Column(name = "minimumPrice",columnDefinition = "Float(10,2)")
public Float getMinimumPrice() {
return this.currentItem.minimumPrice;
}
#Column(name = "maximumPrice",columnDefinition = "Float(10,2)")
public Float getMaximumPrice() {
return this.currentItem.maximumPrice;
}
#Transient
public ArrayList<Items> marketPrices;
Items.java
public class Items implements Serializable {
private static final long serialVersionUID = -2428562977284114465L;
#Id
#Column(name="id")
private int id;
public String itemName;
public String unitofPrice;
public Float minimumPrice;
public Float maximumPrice;
DAO.java
public String saveListOfMarketPrice(MarketPrice marketPrice) {
System.out.println("In Analyser DAO fro bulk saving");
final Session session = getSession();
session.beginTransaction();
for (Items item : marketPrice.marketPrices) {
marketPrice.currentItem = item;
session.save(marketPrice);
}
session.getTransaction().commit();
session.close();
return "success";
}
You should create one bigger entity for the whole Json ant it will have a list of you marketPrice objects #onetomany. Read about that annotation. And also parse your whole incoming object at once not only the list of marketPrices inside. You can read how to parse your whole object here: GSON parsing without a lot of classes
You need something like:
JsonObject rootObj = parser.parse(json).getAsJsonObject();
Then you should describe that structure in an entity:
#Entity
#Data
#Table(name = "your_table")
public class YourEntity{
//you should describe your parameters here too
//of the parsed json. it has other data in it not only the list of `MarketPrices`...
#OneToMany(mappedBy = "yourEntity",
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.LAZY, orphanRemoval = true)
private List<MarketPrice> prices;
.....

Categories