JPA dont want to fetch OneToMany Object (One Level deep) - java

I am using Spring Data Rest. It works great except that I have a REST call that I need to just return the top object and not have the OneToMany object returned.
How do I do this?
For example:
So I have a Rest interface with "/States" and "StatesWithHotels". There is a OneToMany from the State JPA object to the Hotel JPA object. The "/States" should just return just the States and the "StatesWithHotels" should return State object with the corresponding Hotel objects. The "StatesWithHotels" works perfect in which it returns States and Hotels. But the "States" I just need to return just States, but it is returning both.

So this is what I came up with.
The top level object iterated through and set the secondary object to null
for example:
#RequestMapping(value = "/getStates", method = RequestMethod.GET,
headers = { "Accept=application/xml" }, produces = "application/xml")
public #ResponseBody StateList getStates() {
List<StateList> tmpStateList = countryRepo.findByStates();
for(State tmpState : tmpStateList ){
tmpState.setHotels(null);
}
StateList tmpResult = new StateList(tmpStateList );
return tmpResult;

You need to set the fetch mode for that collection to lazy. This will make sure that the collection isn't retrieved until it's accessed.
#OneToMany(fetch=FetchType.LAZY)
If you want it not to be serialised when it's returned, you might also need to annotate it with
#JsonIgnore

Related

How to convert a object from one JpaRepository into another JpaRepository

I have a problem with one functionality in my spring app. I have 2 tables in the same database, both contains the same type of data (id,title,description and date). And I can get the data from one table but don't know how to insert into 2nd table.
In my #Service layer i can get the data from table A. But dont know how to convert into another class object (both classes contain the samne data)
Injected JpaRepositories
private TasksRepository theTasksRepository;
private TasksRepositoryArchive theTasksRepositoryArchive;
And there's code to get the object from table A (TasksRepository - JpaRepository)
public Tasks findById(int theId) {
//Check if value is null or not null
Optional<Tasks> result = theTasksRepository.findById(theId);
Tasks theTask = null;
if (result.isPresent())
{
//if value is not null
theTask = result.get();
}
else
{
//if value is null
throw new RuntimeException("Task with given ID couldn't be found " +theId );
}
return theTask;
}
1) Define 2 entities, one for each table. To copy data, create an instance of the 2nd type and, copy properties, save. To copy properties there are many ways: you cann call each getter and setter manually, you can use some libraries like Dozer or MapStruct. Don't forget to set ID to null.
2) If you want to have an archive of changes, use libraries that help to implement it. For instance, consider using Enverse.

Passing json object to an endpoint developed with spring

I have an endpoint I created using spring.io. My GetMapping declaration can be seen below
#ApiOperation(
value = "Returns a pageable list of CustomerInvoiceProducts for an array of CustomerInvoices.",
notes = "Must be authenticated.")
#EmptyNotFound
#GetMapping({
"customers/{customerId}/getProductsForInvoices/{invoiceIds}"
})
public Page<CustomerInvoiceProduct> getProductsForInvoices(
#PathVariable(required = false) Long customerId,
#PathVariable String[] invoiceIds,
Pageable pageInfo) {
//Do something fun here
for (string i: invoiceIds){
//invoiceIds is always empty
}
}
Here is how I am calling the url from postman and passing the data.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
{
"invoiceIds": [
"123456",
"234566",
"343939"
]
}
My string array for invoiceIds is always empty in the for loop Nothing gets passed to the array. What am I doing wrong?
The mapping you are using is this:
customers/{customerId}/getProductsForInvoices/{invoiceIds}
Both customerId and invoiceIds are Path variables here.
http://localhost:8030/api/v1/customers/4499/getProductsForInvoices/invoiceIds/
The call you are making contains customerId but no invoiceIds. Either you can pass the list in place of invoiceIds as String and read it as a String and then create a List by breaking up the List - which will be a bad practice.
Other way is to change your path variable - invoiceId to RequestBody.
Generally, Path Variables are used for single id or say navigating through some structured data. When you want to deal in a group of ids, the recommended practice would be to pass them as RequestBody in a Post method call rather than a Get method call.
Sample code snippet for REST API (post calls):
Here, say you are trying to pass Employee object to the POST call, the REST API will look like something below
#PostMapping("/employees")
Employee newEmployee(#RequestBody Employee newEmployee) {
//.. perform some operation on newEmployee
}
This link will give you a better understanding of using RequestBody and PathVariables -
https://javarevisited.blogspot.com/2017/10/differences-between-requestparam-and-pathvariable-annotations-spring-mvc.html
https://spring.io/guides/tutorials/rest/

pass complex data from angularjs controller to Spring MVC controller

I have a form with following sections ( following example is for understanding purpose)
GeneralInformation - it 's an object with Cityname (String) and population(int)
Location Information: It's an object with locationCode (int) and Nearest HospitalName(String)
Companies: It's an object with company details. There is list of companies dynamically added
with Company as object. Basically List
Hospitals: it's like List
// generalInfo - populated from form
//locationInfo - populated from form
//companiesArr[] // this is dynamicallypopulated (each row each object) companies array
// hospitalsArr[] // // this is dynamicallypopulated (each row each object) Hospitals array
//Angular code starts..
controller('addGeneralController', function($scope, close,Service) {
$scope.companiesArr = [];
$scope.comapnyName='';
$scope.companyType='';
$scope.hospitalsArr = [];
$scope.hospitalName='';
$scope.locationCode='';
$scope.generalInfo = {};
$scope.locationInfo = {};
$scope.companies = {};
$scope.hospitals = {};
$scope.dataInfo = {};//this is to carry entire objects and arrays
//Following method calls after populating data from form and submit.
//companiesArr,hospitalsArr are populated from front end and passing as submission parameters
$scope.saveGeneral = functio(generalInfo,locationInfo,companiesArr,hospitalsArr){
$scope.companies = companiesArr;
$scope.hospitals = hospitalsArr;
//Create an empty array
//$scope.dataInfo = [];
$scope.dataInfo.push({'generalInfo' : generalInfo, 'locationInfo' : locationInfo,'companies' : $scope.companies,'hospitals' : $scope.hospitals});
$http.post("/addGeneralData",$scope.dataInfo);
});
//Angular code ends..
It's not reaching to the following Spring MVC method:
#RequestMapping(value = "/addGeneralData", method = RequestMethod.POST)
public #ResponseBody String addGeneralData(#RequestBody List<Data> dataInfo){
// not reaching here.With simple parametrs it's reaching here, so no other mapping issue apart from this complex data
// Data - is an object with generalInfo as object,locationInfo as object, //companies List ,hospitals List as it's attributes.
Data data = dataInfo.get(0);
GeneralInfo generalInfo = data.getgeneralInfo();
LocationInfo locationInfo = data.getLocationInfo();
List<Company> companies = data.getCompanies();
List<Hospital> hospitals = data.getHospitals();
}
Basically I want to know how can I transfer this complex data from angular controller to Spring MVC controller?
Please share the request sent from the browser to comment more
It certainly looks like you are sending DataInfo Object but receiving
List dataInfo in your controller. there is a mismatch.
Change the Signature of the handler method
to public #ResponseBody String addGeneralData(#RequestBody DataInfo dataInfo)
Thanks for your response.It's worked when I changed to array instead of list. I have changed all lists inside the Data objects also to array. In addition to that make sure that all data passing from input is as per the type mentioned in the concrete object. For example any data mentioned as int , make sure it's passing int only. If it is complex form and before input validation we are integrating front end with backend, make sure all data we passed exactly as the type mentioned in the mapping object.Is it good practice to use array as parameter in MVC controller?
Do you have any exception ?
It is very likely that you get Serialization Exception due to List interface passed as parameter to controller. Spring just can not initialize new instance of List. Try to use array instead of List. For example
#RequestMapping(value = "/addGeneralData", method = RequestMethod.POST)
public #ResponseBody String addGeneralData(#RequestBody Data[] dataInfo){
Data data = dataInfo[0];
GeneralInfo generalInfo = data.getgeneralInfo();
LocationInfo locationInfo = data.getLocationInfo();
Company[] companies = data.getCompanies();
Hospital[] hospitals = data.getHospitals();
}
Be sure that you use concrete implementations, not interfaces in your Data object.
Hope it helps

How to implement lazy fetching a list combined with a conditious get for one element of that list?

We're setting up a DB with an object model for safes that have bill readers inside to put the bills in. This safe is stored in the Unit class and that unit has a list of placeholders for, let's say, 3 components. Components can change over time, but placeholders will stay connected to the same safe. So this unit has 3 placeholders with identifiers like 'left reader', 'right reader' and 'printer'. The actual component inside such a placeholder has the product type (for instance Mei reader ... or JCM reader ...) and a serial number.
Now, as the unit itself will be requested from the server side quite often as it is shown in one or more (pages of) portals (to see it's own serial number, location, how much money is inside, which users are allowed to log on to this safe, etc.) and components need not be known most of the time we want to lazily load them. The unit itself needs to know it's actual components (configuration) with serial numbers for components to be able to know / tell the back end when a component is exchanged during a servicing by a mechanic.
There is an intermediate table (placeholdercomponents) with placeholder FK, component FK and a datetime placed and a datetime removed.
Right now there is a Unit class that contains, besides a lot else, this:
#OneToMany(mappedBy = "unit", fetch = FetchType.EAGER)
private List<Placeholder> placeholders = new ArrayList<>();
#XmlElementWrapper(name = "Placeholders")
#XmlElement(name = "Placeholder")
public final List<Placeholder> getPlaceholders() {
return placeholders;
}
public final void setPlaceholders(List<Placeholder> pPlaceholders) {
placeholders = pPlaceholders;
}
The placeholder class has this:
#OneToMany(mappedBy = "placeholder", fetch = FetchType.LAZY)
private List<PlaceholderComponent> placeholderComponents;
#XmlTransient
public List<PlaceholderComponent> getPlaceholderComponents() {
if (placeholderComponents == null)
placeholderComponents = new ArrayList<PlaceholderComponent>();
return placeholderComponents;
}
public void setOrganizationUnits(List<PlaceholderComponent> pPlaceholderComponents) {
placeholderComponents = pPlaceholderComponents;
}
#XmlElement(name = "Component")
public Component getCurrentComponent() {
if (placeholderComponents == null) {
return null;
} else {
PlaceholderComponent placeComp = placeholderComponents.stream()
.filter(pc -> pc.getDateTimeRemoved() == null)
.findFirst()
.orElse(null);
if (placeComp == null) {
return null;
} else {
return placeComp.getComponent();
}
}
}
And in the service there should be two possible calls getUnit & getUnitWithComponents. Now, if I remove the #XmlElement(name = "Component") annotation above getCurrentComponent() and do nothing with the placeholderComponents inside the getUnit call it works as expected, but getUnitWithComponents doesn't work (it works, but doesn't return the components inside the placeholders). When I leave the annotation getUnitWithComponents works as expected, but getUnit gives an error about not being able to lazily load the placeholderComponents as, apparently, JAXB wants to build the Component element even though the whole list isn't called in the service and therefore not loaded.
I can think of 3 possible 'solutions' working around this:
Remove the getCurrentComponent and make a separate call for retrieving the current component in a placeholder which would send a single component XML back
Make two different objects for Placeholder while both are for the same DB-table, using the one for getUnit (where the Component annotation isn't present) and the other for getUnitWithComponents (where the Component annotation is present).
Another option would be splitting into DAO's and DTO's as there might be more of those situations coming, but that splitting would take quite some time by now.
So the main question is: is there a way to get the components inside the unit XML for some calls and leave them out for most of the calls without a workaround? And if not, what solution would you chose? 1, 2, 3 or another one?

Unidirectional #OneToMany association fails equality test in JPA

I have set up a unidirectional OneToMany relationship like the example in section 2.10.5.1 of the JPA 2.1 spec:
#Entity
public class Client implements Serializable {
...
#OneToMany
private List<ServiceOrder> activeServiceOrders;
public void setActiveServiceOrders( List<ServiceOrder> activeServiceOrders ) {
this.activeServiceOrders = activeServiceOrders;
}
public List<ServiceOrder> getActiveServiceOrders() {
return activeServiceOrders;
}
}
The ServiceOrder class implements hashCode and equals using its auto-generated long id. They were implemented by Eclipse.
public class ServiceOrder implements Serializable {
#TableGenerator( name = "generator_serviceOrder", table = "SEQUENCE_TABLE", pkColumnName = "SEQ_NAME", valueColumnName = "LAST_VALUE_GEN", pkColumnValue = "SERVICE_ORDER_SEQ", allocationSize = 1, initialValue = 0 )
#Id
#GeneratedValue( strategy = GenerationType.TABLE, generator = "generator_serviceOrder" )
private long id;
...
#Override
public boolean equals( Object obj ) {
if ( this == obj )
return true;
if ( obj == null )
return false;
if ( getClass() != obj.getClass() )
return false;
ServiceOrder other = (ServiceOrder ) obj;
if ( id != other.id )
return false;
return true;
}
...
}
Tables are all auto-generated as expected. Then, when I want to establish the relationship I do:
...
Client client = entityManager.find(...);
ServiceOrder so = entityManager.find(...);
client.getActiveServiceOrders().add( so );
...
Everything is fine until now, transaction commits successfully. Problem starts when I try to remove the relationship (in another transaction, another moment):
...
Client sameClient = entityManager.find(...);
ServiceOrder sameSo = entityManager.find(...);
log.info(sameClient.getActiveServiceOrders().size()); // "1", OK
log.info(sameClient.getActiveServiceOrders().contains(so)); // "false". Why?
sameClient.getActiveServiceOrders().remove(so); // does nothing, returns false
...
I debugged and discovered that the following is failing in ServiceOrder.equals():
...
if ( getClass() != obj.getClass() ) // different probably because JPA (Hibernate) proxies one of the objects
return false; // returns
...
I found two temporary solutions:
Remove ServiceOrder equals() and hashCode();
or
Make the relationship bidirectional (and of course update both sides every add/remove);
I don't understand this behavior. Why the difference in treatment if the relationship is uni or bi-directional? Also, if I get these entities in the context of the same transaction, how would fail the first equals test:
if ( this == obj )
return true;
I'm using JPA 2.1 (Wildfly 8.1.0).
Best Regards and thank you in advance.
Renan
You should override equals and hashCode but you should never use the ID for hash code unless you make the hashCode immutable and use the ID only when it's not null for equality.
Otherwise, prior to saving an Entity with the ID being null which is to be assigned during the flush time when you add a Transient entity to a collection, the moment it gets persisted and the ID is generated the equals/hashCode contract is going to broken.
Hibernate best practices suggest using a business key for object equality/hashCode.
So quoting the reference documentation:
The general contract is: if you want to store an object in a List, Map
or a Set then it is a requirement that equals and hashCode are
implemented so they obey the standard contract as specified in the
documentation.
To avoid this problem we recommend using the "semi"-unique attributes
of your persistent class to implement equals() (and hashCode()).
Basically you should think of your database identifier as not having
business meaning at all (remember, surrogate identifier attributes and
automatically generated values are recommended anyway). The database
identifier property should only be an object identifier, and basically
should be used by Hibernate only. Of course, you may also use the
database identifier as a convenient read-only handle, e.g. to build
links in web applications.
Instead of using the database identifier for the equality
comparison, you should use a set of properties for equals() that
identify your individual objects. For example, if you have an "Item"
class and it has a "name" String and "created" Date, I can use both to
implement a good equals() method. No need to use the persistent
identifier, the so-called "business key" is much better. It's a
natural key, but this time there is nothing wrong with using it!
Don't override the equals and hashCode. Hibernate has its own implementation to find out the objects, and that's why you don't get the expected result.
This article explains more:
https://community.jboss.org/wiki/EqualsandHashCode?_sscc=t

Categories