Unexpected end of subtree with Criteria Query using isEmpty - spring

I've ran into a problem while developing a Spring Boot application with Criteria API.
I'm having a simple Employer entity, which contains a set of Job.ID (not entities, they're pulled out using repository when needed). Employer and Job are in many to many relationship. This mapping is only used on a purpose of finding Employee with no jobs.
public class Employer {
#ElementCollection
#CollectionTable(
name = "EMPLOYEE_JOBS"
joinColumns = #JoinColumn(name = "EMP_ID")
#Column(name = "JOB_ID")
private final Set<String> jobs = new HashSet<>(); //list of ids of jobs for an employee
}
Then I have a generic function, which returns a predicate (Specification) by a given attributePath and command for any IEntity implementation.
public <E extends IEntity> Specification<E> createPredicate(String attributePath, String command) {
return (r, q, b) -> {
Path<?> currentPath = r;
for(String attr : attributePath.split("\\.")) {
currentPath = currentPath.get(attr);
}
if(Collection.class.isAssignableFrom(currentPath.getJavaType())) {
//currentPath points to PluralAttribute
if(command.equalsIgnoreCase("empty")) {
return b.isEmpty((Expression<Collection<?>>)currentPath);
}
}
}
}
If want to get list of all employee, who currently have no job, I wish I could create the predicate as follows:
Specification<Employer> spec = createPredicate("jobs", "empty");
//or if I want only `Work`s whose were done by employer with no job at this moment
Specification<Work> spec = createPredicate("employerFinished.jobs", "empty");
This unfortunately does not works and throws following exception:
org.hibernate.hql.internal.ast.QuerySyntaxException:
unexpected end of subtree
[select generatedAlias0 from Employer as generatedAlias0
where generatedAlias0.jobs is empty]
Is there a workaround how to make this work?

This bug in Hibernate is known since September 2011, but sadly hasn't been fixed yet. (Update: this bug is fixed as of 5.4.11)
https://hibernate.atlassian.net/browse/HHH-6686
Luckily there is a very easy workaround, instead of:
"where generatedAlias0.jobs is empty"
you can use
"where size(generatedAlias0.jobs) = 0"
This way the query will work as expected.

Related

InvalidPathException while sorting with org.springframework.data.domain.Pageable

I am trying to sort my table's content on the backend side, so I am sending org.springframework.data.domain.Pageable object to controller. It arrives correctly, but at the repository I am getting org.hibernate.hql.internal.ast.InvalidPathException. Somehow the field name I would use for sorting gets an org. package name infront of the filed name.
The Pageable object logged in the controller:
Page request [number: 0, size 10, sort: referenzNumber: DESC]
Exception in repository:
Invalid path: 'org.referenzNumber'","logger_name":"org.hibernate.hql.internal.ast.ErrorTracker","thread_name":"http-nio-8080-exec-2","level":"ERROR","level_value":40000,"stack_trace":"org.hibernate.hql.internal.ast.InvalidPathException: Invalid path: 'org.referenzNumber'\n\tat org.hibernate.hql.internal.ast.util.LiteralProcessor.lookupConstant(LiteralProcessor.java:111)
My controller endpoint:
#GetMapping(value = "/get-orders", params = { "page", "size" }, produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<PagedModel<KryptoOrder>> getOrders(
#ApiParam(name = "searchrequest", required = true) #Validated final OrderSearchRequest orderSearchRequest,
#PageableDefault(size = 500) final Pageable pageable, final BindingResult bindingResult,
final PagedResourcesAssembler<OrderVo> pagedResourcesAssembler) {
if (bindingResult.hasErrors()) {
return ResponseEntity.badRequest().build();
}
PagedModel<Order> orderPage = PagedModel.empty();
try {
var orderVoPage = orderPort.processOrderSearch(resourceMapper.toOrderSearchRequestVo(orderSearchRequest), pageable);
orderPage = pagedResourcesAssembler.toModel(orderVoPage, orderAssembler);
} catch (MissingRequiredField m) {
log.warn(RESPONSE_MISSING_REQUIRED_FIELD, m);
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(orderPage);
}
the repository:
#Repository
public interface OrderRepository extends JpaRepository<Order, UUID> {
static final String SEARCH_ORDER = "SELECT o" //
+ " FROM Order o " //
+ " WHERE (cast(:partnerernumber as org.hibernate.type.IntegerType) is null or o.tradeBasis.account.retailpartner.partnerbank.partnerernumber = :partnerernumber)"
+ " and (cast(:accountnumber as org.hibernate.type.BigDecimalType) is null or o.tradeBasis.account.accountnumber = :accountnumber)"
+ " and (cast(:orderReference as org.hibernate.type.LongType) is null or o.tradeBasis.referenceNumber = :orderReference)"
+ " and (cast(:orderReferenceExtern as org.hibernate.type.StringType) is null or o.tradeBasis.kundenreferenceExternesFrontend = :orderReferenceExtern)"
+ " and (cast(:dateFrom as org.hibernate.type.DateType) is null or o.tradeBasis.timestamp > :dateFrom) "
+ " and (cast(:dateTo as org.hibernate.type.DateType) is null or o.tradeBasis.timestamp < :dateTo) ";
#Query(SEARCH_ORDER)
Page<Order> searchOrder(#Param("partnerernumber") Integer partnerernumber,
#Param("accountnumber") BigDecimal accountnumber, #Param("orderReference") Long orderReference,
#Param("orderReferenceExtern") String orderReferenceExtern, #Param("dateFrom") LocalDateTime dateFrom,
#Param("dateTo") LocalDateTime dateTo, Pageable pageable);
}
Update:
I removed the parameters from the sql query, and put them back one by one to see where it goes sideways. It seems as soon as the dates are involved the wierd "org." appears too.
Update 2:
If I change cast(:dateTo as org.hibernate.type.DateType) to cast(:dateFrom as date) then it appends the filed name with date. instead of org..
Thanks in advance for the help
My guess is, Spring Data is confused by the query you are using and can't properly append the order by clause to it. I would recommend you to use a Specification instead for your various filters. That will not only improve the performance of your queries because the database can better optimize queries, but will also make use of the JPA Criteria API behind the scenes, which requires no work from Spring Data to apply an order by specification.
Since your entity Order is named as the order by clause of HQL/SQL, my guess is that Spring Data tries to do something stupid with the string to determine the alias of the root entity.

How to efficiently update using JpaRepository

I have a parent which stores a list of children. When i update the children(add/edit/remove), is there a way to automatically decide which child to remove or edit based on the foreign key? Or do i have to manually check through all the child to see which are new or modified?
Parent Class
#Entity
#EntityListeners(PermitEntityListener.class)
public class Permit extends Identifiable {
#OneToMany(fetch = FetchType.LAZY, cascade=CascadeType.ALL, mappedBy = "permit")
private List<Coordinate> coordinates;
}
Child Class
#Entity
public class Coordinate extends Identifiable {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "permit_id", referencedColumnName = "id")
private Permit permit;
private double lat;
private double lon;
}
Parent's Controller
#PutMapping("")
public #ResponseBody ResponseEntity<?> update(#RequestBody Permit permit) {
logger.debug("update() with body {} of id {}", permit, permit.getId());
if (!repository.findById(permit.getId()).isPresent()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
Permit returnedEntity = repository.save(permit);
repository.flush();
return ResponseEntity.ok(returnedEntity);
}
=EDIT=
Controller Create
#Override
#PostMapping("")
public #ResponseBody ResponseEntity<?> create(#RequestBody Permit permit) {
logger.debug("create() with body {}", permit);
if (permit == null || permit.getId() != null) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
List<Coordinate> coordinates = permit.getCoordinates();
if (coordinates != null) {
for (int x = 0; x < coordinates.size(); ++x) {
Coordinate coordinate = coordinates.get(x);
coordinate.setPermit(permit);
}
}
Permit returnedEntity = repository.save(permit);
repository.flush();
return ResponseEntity.ok(returnedEntity);
}
Controller Update
#PutMapping("")
public #ResponseBody ResponseEntity<?> update(#RequestBody Permit permit) {
logger.debug("update() with body {} of id {}", permit, permit.getId());
if (!repository.findById(permit.getId()).isPresent()) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null);
}
List<Coordinate> repoCoordinate = coordinateRepository.findByPermitId(permit.getId());
List<Long> coordinateIds = new ArrayList<Long>();
for (Coordinate coordinate : permit.getCoordinates()) {
coordinate.setPermit(permit);
//if existing coordinate, save the ID in coordinateIds
if (coordinate.getId() != null) {
coordinateIds.add(coordinate.getId());
}
}
//loop through coordinate in repository to find which coordinate to remove
for (Coordinate coordinate : repoCoordinate) {
if (!(coordinateIds.contains(coordinate.getId()))) {
coordinateRepository.deleteById(coordinate.getId());
}
}
Permit returnedEntity = repository.save(permit);
repository.flush();
return ResponseEntity.ok(returnedEntity);
}
I have tested this and it is working, is there no simplified way of doing this?
You were close to the solution. The only thing you're missing is orphanRemoval=true on your one to many mapping:
#Entity
#EntityListeners(PermitEntityListener.class)
public class Permit extends Identifiable {
#OneToMany(mappedBy = "permit", cascade=CascadeType.ALL, orphanRemoval=true)
private List<Coordinate> coordinates;
}
Flagging the mapping for orphan removal will tell the underlying ORM to delete any entities that no longer belong to any parent entity. Since you removed a child element from the list, it will be deleted when you save the parent element.
Creating new elements and updating old is based on the CascadeType. Since you have CascadeType.ALL all elements in the list without an ID will be saved to the database and assigned a new ID when you save the parent entity, and all elements that are already in the list and have an ID will be updated.
On a side note, you might need to update the setter method for List coordinates to look something like:
public void setCoordinates(List<Coordinates> coordinates) {
this.coordinates = coordinates;
this.coordinates.forEach(coordinate -> coordinates.setPermit(this));
}
Or simply use #JsonManagedReference and #JsonBackReference if you're working with JSON.
I have a parent which stores a list of children.
Lets write the DDL for it.
TABLE parent (
id integer pk
)
TABLE child(
id integer pk
parent_id integer FOREIGN KEY (parent.id)
)
When i update the children(add/edit/remove), is there a way to automatically decide which child to remove or edit based on the foreign key?
Assuming you have a new child #5 bound to the parent #2 and:
The FK in the DDL is correctly
The entitys knows the FK
You are using the same jpa-context
The transaction is executed correctly
Then every call to parent.getChilds() must(!) return all the entitys that are existing before your transaction has been executed and the same instance of the entity that you have just committed to the database.
Then, if you remove child #5 of parent #2 and the transaction executed successfully parent.getChilds() must return all entitys without child #5.
Special case:
If you remove parent #2 and you have cascade-delete in the DDL as well as in the Java-Code all childrens must be removed from the Database as well as the parent #2 in the Database you just removed. In this case the parent #2 is not bound anymore to the jpa-context and all the childrens of parent #2 are not bound anymore to the jpa-context.
=Edit=
You could use merge. This will work for constructs like this:
POST {
"coordinates": [{
"lat":"51.33",
"lon":"22.44"
},{
"lat":"50.22",
"lon":"22.33"
}]
}
It will create one row in table "permit" and two rows in table "coordinate", both coordinates are bound to the permit-row. The result will include the ids set.
But: You will have to do the validation work (check that id is null, check that coordinates not refering different permit, ...)!
The removal of coordinates must be done using the DELETE method:
DELETE /permit/972/coordinate/3826648305

Find the difference between two collections in Java 8?

I am trying to make a List of all of the books in one Collection that are not present in another. My problem is that I need to compare based on book ID, so I can't just test to see whether a book in the first is contained in the second, I have to determine whether any book in the second collection has the same ID as a book in the first.
I have the below code to compare two collections of books and filter the first collection:
List<Book> parentBooks = listOfBooks1.stream().filter(book->
!listOfBooks2.contains(book)).collect(Collectors.toList());
The code doesn't work correctly because I am comparing the objects themselves. I need to compare the objects based on the bookId instead of the whole book object. How should I change the code so it can do the comparison based on the bookId (book.getId())?
List<Book> books1 = ...;
List<Book> books2 = ...;
Set<Integer> ids = books2.stream()
.map(Book::getId)
.collect(Collectors.toSet());
List<Book> parentBooks = books1.stream()
.filter(book -> !ids.contains(book.getId()))
.collect(Collectors.toList());
The problem is complex, but it boils down to one thing, knows your data. Is it immutables, entities with an id, duplicate entries etc?
The code below works for immutables with only values (and with possible duplicates).
It first tries to remove all entries in the before list (from the copied after-list).
What is left will be the added elements. The ones from the before-list that can be removed from the after-list are the unchanged ones.
The rest are the removed ones
public class ListDiffer<T> {
private List<T> addedList = new ArrayList<>();
private List<T> unchangedList = new ArrayList<>();
private List<T> removedList = new ArrayList<>();
public ListDiffer(List<T> beforeList, List<T> afterList) {
addedList.addAll(afterList); // Will contain only new elements when all elements in the Before-list are removed.
beforeList.forEach(e -> {
boolean b = addedList.remove(e) ? unchangedList.add(e) : removedList.add(e);
});
}
public List<T> getAddedList() {
return addedList;
}
public List<T> getUnchangedList() {
return unchangedList;
}
public List<T> getRemovedList() {
return removedList;
}
}

What overhead ormLite has when querying or creating object with ForeignCollection?

I have a table with Players and an other table with Games. There is Player----(1..n)[Game] relationship between them (field definitions may not be entirely correct):
// Player
#DatabaseField(generatedId = true)
private int id;
#DatabaseField
public String name;
#ForeignCollectionField(
eager = true,
maxEagerLevel = 3)
public ForeignCollection<Game> games;
// Game
#DatabaseField
String title;
#DatabaseField
String playerName;
I would like to get and return a list of all games. When is the overhead letting ormLite do the select for the ForeignCollection? Or would it be better to do something like this:
final List<Game> allGames = daoGames.getAllGroupedByName();
final List<Player> allPlayers = gameDao.getAll();
final HashMap<String, List<Game>> games = new HashMap<String, List<Game>>();
for (Game currentGame : allGames) {
final String player = currentGame.playerName;
if (games.get(player) == null) {
games.put(player, new ArrayList<Game>());
}
final List<Game> gamesOfPlayer = games.get(player);
gamesOfPlayer.add(currentGame);
}
for (Player player : allPlayers) {
player.games = games.get(player.name);
}
My guess is ormLite would do a query for each and every player. Is it a big overhead compared to the one daoGames.getAllGroupedByName() (though the groupBy is not even needed)?
My guess is ormLite would do a query for each and every player. Is it a big overhead compared to the one daoGames.getAllGroupedByName() (though the groupBy is not even needed)?
This is correct. ORMLite does not do this very efficiently with a separate query for each Player result. Your mechanism to do the getAll() and match them up by hand would most likely be faster.
I'd use an ArrayList unless you really need LinkedList capabilities.

sort by nullable property in a n:1 relation

I have the following problem - given just a snippet of the whole domain environment, I'll try to explain it:
I have these two domain classes and want to get a list of studies and sort them by the name of their (current) physician. My problem is, that I donĀ“t know how to do the criteria query with gorm...
class Study {
Date studydate
String comment
static belongsTo = [currentPatient:Patient, originalPatient: Patient, originalPhysician: Physician, currentPhysician: Physician]
static mapping = {
columns{
currentPatient column:'id_patient_current'
originalPatient column:'id_patient'
originalPhysician column:'id_physician'
currentPhysician column:'id_physician_current'
}
}
}
class Physician {
String personname
static hasMany = [currentStudies: Study, originalStudies: Study]
static mappedBy = [currentStudies: 'currentPhysician',
originalStudies: 'originalPhysician']
}
and now the query:
def physician = Physician.get(params?.phId)
def studies = Study.withCriteria{
and{
maxResults(limit as Integer)
firstResult(offset as Integer)
currentPhysician{
if(physician){
eq('id', physician.id)
}
order('personname', 'asc')
}
}
}
the problem is that not every study must have a physician - it is possible, that the columns (in the database) - "id_physician" and "id_physician_current" - contain NULL values (the patient did not came from a physician).
via direct sql the query is no problem:
select st.id, ph.personname from study as st left join Physician as ph on ph.ID = st.ID_Physician_Current order by ph.PersonName
I am using grails 1.3.7 on a mssql database
I believe the following query accomplishes what you are asking for.
def studies = Study.withCriteria{
maxResults(limit as Integer)
firstResult(offset as Integer)
order('currentPhysician.personname', 'asc')
if(physician) {
or {
eq('currentPhysician', physician)
isNull('currentPhysician')
}
}
order('currentPhysician.personname', 'asc')
}
Maybe not the exact same sql as you expected but if you are worried about that you shouldn't be writing criteria.

Resources