How can I read list of #Embeddable objects from MongoDB with Hibernate OGM after aggregation.
I have entity like this
#javax.persistence.Entity
public class MySession implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Type(type = "objectid")
private String id;
private Date start;
private Date end;
#ElementCollection
private List<MySessionEvent> events;
}
and #Embeddable object
#javax.persistence.Embeddable
public class MySessionEvent implements Serializable {
private Long time;
private String name;
}
I stuck with mapping Embeddable objects from native query
String queryString = "db.MySession.aggregate([" +
" { '$match': { 'events.name': { '$regex': 'Abrakadabra'} }}, " +
" { '$unwind': '$events' }, " +
" { '$replaceRoot': { 'newRoot': '$events'}} " +
"])";
List<MySessionEvent> objects = em.createNativeQuery(queryString, MySessionEvent.class).getResultList();
I get an error Caused by: org.hibernate.MappingException: Unknown entity
Copying your comment here because it adds some details:
I have data like this [ {id:'s1', events: [{name: 'one'},{name:
'two'}]}, {id:'s2', events: [{name: 'three'},{name: 'four'}]} ] and I
want result like this [{name: 'one'},{name: 'two'},{name:
'three'},{name: 'four'}]
The query you are running returns the following type of results if I run it on MongoDB natively (I populated it with some random data):
{ "name" : "Event 3", "time" : NumberLong(3) }
{ "name" : "Abrakadabra", "time" : NumberLong(5) }
This is not enough to rebuild an entity and that's the reason you are seeing the exception.
Considering that you only want the list of events, this should work:
List<Object[]> poems = em.createNativeQuery( queryString ).getResultList();
Hibernate OGM will convert the previous result in a list of arrays.
Each element of the List is an array where the firs value of the array is the name of the event and the second one is the time.
For supported cases like this I think HQL queries are better. You could rewrite the same example with the following:
String queryString =
"SELECT e.name " +
"FROM MySession s JOIN s.events e " +
"WHERE e.name LIKE 'Abrakadabra'";
List<Object[]> events = em.createQuery( queryString ).getResultList();
Note that I decided not to return the time because in your comment you didn't request it, but this will work as well:
String queryString =
"SELECT e.time, e.name " +
"FROM MySession s JOIN s.events e " +
"WHERE e.name LIKE 'Abrakadabra'";
List<Object[]> events = em.createQuery( queryString ).getResultList();
It is not recognizing the entity, make sure all your entities are in persistence.xml Embeddable objects too
<class>org.example.package.MySessionEvent</class>
Related
I have a JPA query written like this:
public interface MyDataRepository extends CrudRepository<MyData, String> {
#Query("select md " +
"from MyData md " +
"join fetch md.child c " +
"where c.date = :date")
List<MyData> getMyDataOfDate(#NotNull LocalDate date);
#Query("select md " +
"from MyData md " +
"join fetch md.child c " +
"where c.name = :name")
List<MyData> getMyDataOfName(#NotNull String name);
#Query("select md " +
"from MyData md " +
"join fetch md.child c " +
"where md.type = :type")
List<MyData> getMyDataOfType(#NotNull String type);
}
Class MyData and Child are defined as:
class MyData {
String id;
String type;
#ManyToOne
#JoinColumn(name = "CHILD_ID", referencedColumnName = "ID", nullable = false, updatable = false)
Child child;
}
class Child {
String id;
String name;
LocalDate date;
}
The problem is that whenever I call the getMyDataOfDate method or getMyDataOfName method, they always return ALL rows rather than the rows that matches the where condition, as if the where clause never exists.
However, the getMyDataOfType method works fine. The difference of this method is that the where condition is on a property of md, not c.
What did I do wrong?
JPA does not allow filtering on join fetches. Reasons being is that when you specify a join fetch, you are telling JPA to fetch the parent and all its children defined by that relationship in the managed entities it returns. If filtering were allowed, the list of children, the relationship in the parent, might not reflect what is actually in the database. Take the case of Parent with many children
"Select parents from Parent p fetch join p.children c where c.firstName = 'Bob'"
For such a query, when you get a list of parents and calling getChildren on them, do you expect to see all their children or a list that only contains children named Bob? If the later (which is the only way to do so), how should JPA handle changes to a parents children list, and know what to do with the not-fetched children?
This is why JPA doesn't allow filtering over fetch joins, and they restrict it across all relationships to be consistent. If you want the parents who have children with the firstName of 'Bob', it would look like:
"Select parents from Parent p join p.children c where c.firstName = 'Bob'"
Every parent returned will be a complete representation of its state in the database based on its mappings; so accessing parent.getChildren will return the current state of its children list and not something affected by the way it was fetched.
In my Spring boot application I have a query which should return a distinct List of Focus' (works perfectly in MySQL)
#Query(value = "SELECT DISTINCT * FROM Focus F " +
"JOIN FrameworkFoci FF on FF.focusID = F.focusID " +
"JOIN FocusGroups FG on FF.frameworkID = FG.frameworkID " +
"JOIN GroupMembers GM on FG.groupID = GM.groupID " +
"JOIN Users U on GM.userID = U.userID " +
"WHERE U.userID = :userID", nativeQuery = true)
List<Focus> findByUserID(#Param("userID") Long userID);
However this does not return distinct values, duplicates are contained in the resulting list. Another issue is that I can't return a whole entity using #Query annotation - changing my query to SELECT DISTINCT(F) FROM Focus F gives the error java.sql.SQLSyntaxErrorException: Unknown column 'F' in 'field list'.
Furthermore, I tried changing adjusting the query to the following
#Query(value = "SELECT DISTINCT * FROM FrameworkFoci FF " +
"JOIN FocusGroups FG on FF.frameworkID = FG.frameworkID " +
"JOIN GroupMembers GM on FG.groupID = GM.groupID " +
"JOIN Users U on GM.userID = U.userID " +
"WHERE U.userID = :userID", nativeQuery = true)
however this produced the error java.sql.SQLException: Column 'focusCategory' not found.
Why is the query not returning distinct values? And why can't I return a whole entity nor use the second query?
Focus Entity:
#Entity
public class Focus {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long focusID;
#Column(name = "focusCategory")
private String focusCategory;
private String focusName;
private String focusExplanation;
#OneToMany(mappedBy = "focus")
private Set<Rating> ratings;
#ManyToMany
#JoinTable(name = "FrameworkFoci",
joinColumns = #JoinColumn(
name = "focusID"),
inverseJoinColumns = #JoinColumn(
name = "frameworkID"))
private Set<Framework> frameworks;
//image
protected Focus(){}
public Focus(String focusName, String focusCategory, String focusExplanation) {
this.focusCategory = focusCategory;
this.focusName = focusName;
this.focusExplanation = focusExplanation;
}
public Focus(String focusCategory, String focusName, String focusExplanation, Set<Rating> ratings){
this.focusCategory = focusCategory;
this.focusName = focusName;
this.focusExplanation = focusExplanation;
this.ratings = ratings;
}
public Long getFocusId() {
return focusID;
}
public void setFocusId(Long focusID) {
this.focusID = focusID;
}
public String getFocusCategory() {
return focusCategory;
}
public void setFocusCategory(String focusCategory) {
this.focusCategory = focusCategory;
}
EDIT:
I've switched from SQL to JPQL with the following query:
#Query(value = "SELECT DISTINCT focus FROM Focus focus " +
"WHERE focus.frameworks.groups.groupMembers.user.userID =:userID ")
I now get an error org.hibernate.QueryException: illegal attempt to dereference collection [focus0_.focusID.frameworks] with element property reference [groups]
Framework entity:
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "frameworkID")
public class Framework {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long frameworkID;
private String frameworkName;
#ManyToMany
#JoinTable(name = "FrameworkFoci",
joinColumns = #JoinColumn(
name = "frameworkID"),
inverseJoinColumns = #JoinColumn(
name = "focusID"))
private Set<Focus> frameworkFoci = new HashSet<>();
#OneToMany(mappedBy = "framework", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
private Set<Group> groups;
public Framework(){}
The following query solves the issue
#Query(value = "SELECT DISTINCT focus FROM Focus focus " +
"JOIN focus.frameworks frameworks " +
"JOIN frameworks.groups groups " +
"JOIN groups.groupMembers groupMembers "+
"WHERE groupMembers.userID =:userID ")
List<Focus> findByUserID(#Param("userID") Long userID);
Frameworks and GroupMembers are collections and hence needed to be joined, otherwise illegal attempt to dereference collection [focus0_.focusID.frameworks] with element property reference [groups] was produced
you should write your query like this:
'
SELECT DISTINCT f FROM Focus F '
The problem stems from you using SQL by specifying nativeQuery = true. SQL doesn't know about entities, just tables.
Since you presumably have many FrameworkFoci rows (and rows in all the other tables) for each Focus row, each Focus row gets repeated for each matching row in FrameworkFoci. This kind of duplicates the Focus row but the resulting rows are still distinct, because they differ in the columns from the other tables.
And then each row gets turned into a Focus entity, probably with a single element in the framework set.
So therefore query doesn't so much return duplicate results as results split into multiple entities.
Fortunately the solution should be fairly simple: Use JPQL which should be perfectly possible, since you're using only simple joins.
The following should give you a start:
#Query(value = "SELECT DISTINCT * FROM Focus F " +
"WHERE F.framework.groupMembers.user.id=:userID")
List<Focus> findByUserID(#Param("userID") Long userID);
I'm facing the following problem...
I'm trying to have a java list of ProcessDTO object of both process and subprocess in this way:
[{"process": {
"code": "AB",
"name": "Proc1"
"subprocesses":
[{"code": "cd", "name": "subProc1"}],
[{"code": "ef", "name": "subProc2"}],
[{"code": "gh", "name": "subProc3"}]
}
}]
This is what I have till now:
SELECT distinct C.code as process_code, C.name as process_name, A.code as subprocess_code, A.name as subprocess_code FROM subprocess A
inner join zone B
on A.id = B.subprocess_id
inner join process C
on C.id = B.process_id
ORDER BY C.code;
But when I create the DTO class:
public class ProcessDto {
private Long id;
private String code;
private String name;
private List<Subprocess> subprocess;
}
And the query:
#RepositoryRestResource(itemResourceRel = "process", collectionResourceRel = "processes", path = "process")
public interface ProcessDataRestRepository extends JpaRepository<Process, Long> {
#Query("Select p.code, p.name, sp.code, sp.name " +
"from Process p " +
"inner join Zone z on p.id = z.process " +
"inner join Subprocess sp on sp.id = z.subprocess")
List<ProcessDto> findProcessesAndSubprocesses();
}
I get the following error:
org.springframework.core.convert.ConverterNotFoundException: No
converter found capable of converting from type
[org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap]
to type [dto.ProcessSubprocess]
I'm not sure if this is the correct approach.
A dirty way would be to do a findAll of the processes, store in an object, then iterate them and find all the subprocesses...
Is there any other way to bring everything from de BBDD and have it in an object?
Make an interface from that DTO so Spring engine can pick it up and create a result object automatically:
public interface ProcessDto {
Long getId();
String getCode();
String getName();
String getSubprocessName();
}
More on the subject here
I don't know if this is possible but I am trying to project data queried from JPA repository into a DTO
I have the following query:
#Query(value =
"SELECT crop.id, count(*) as total " +
"FROM xxx.crop_sub_plot " +
"join crop on crop.id = crop_sub_plot.crop_id " +
"join sub_plot on sub_plot.id = crop_sub_plot.sub_plot_id " +
"where sub_plot.enabled = true " +
"group by crop_id " +
"order by total DESC;", nativeQuery = true)
List<CropUsedView> findCropsInUseOrderByDesc();
and the DTO:
public class CropUsedView implements Serializable{
private BigInteger id;
private BigInteger total;
public CropUsedView() {
}
public CropUsedView(BigInteger id, BigInteger total) {
this.id = id;
this.total = total;
}
//getters && setters
I'm getting the error:
No converter found capable of converting from type [java.math.BigInteger] to type [net.xxx.crop.CropUsedView]
I don't really know if this is possible, any suggestion?
EDIT: this is how the data is returning when I run the query on MySql and is how I want to be converted to a DTO:
Your query is returning two values: the id and a count (both can be mapped in a long or BigDecimal). But Hibernate, as it's not mapped directly into an object, is just returning BigDecimal[].
To solve this, you should use a custom mapper: UserType (https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/usertype/UserType.html). This allows you to map whatever response into an object with a manual parsing.
I'm trying to run a JPA query to return only specific fields from my entity, rather than the entire entity (for performance reasons).
Within this entity is this:
#OneToMany(cascade = { CascadeType.ALL }, mappedBy = "helper", fetch = FetchType.EAGER)
#MapKeyColumn(name = "year")
public Map<Integer, DutyHistory> getDutyHistoryList() {
return dutyHistoryList;
}
I'd like, within my query, to return multiple values from this map e.g. fields from the DutyHistory object for the last 3 years.
My question is, what's the query syntax for this? I'm mapping the returned values to a POJO as below:
#Query(value = "SELECT new com.castlemon.helpers.dto.ReportHelper(h.helperId, h.firstName, h.secondName"
+ ", h.sex, h.service, h.dateOfBirth, h.schoolGroup, h.orientationRequired, h.notes as adminNotes "
+ ", h.primaryDuty.dutyName as primaryDuty, h.secondDuty, h.secondaryDuty.dutyName as secondaryDuty "
+ " WHERE h.travelling = 1")
public List<ReportHelper> getTravellingHelperDetails();
You should create another query with your "year" parameter
#Query(value = "SELECT new com.castlemon.helpers.dto.ReportHelper(h.helperId, h.firstName, h.secondName"
+ ", h.sex, h.service, h.dateOfBirth, h.schoolGroup, h.orientationRequired, h.notes as adminNotes "
+ ", h.primaryDuty.dutyName as primaryDuty, h.secondDuty, h.secondaryDuty.dutyName as secondaryDuty "
+ " WHERE h.travelling = 1 AND h.year >= :yearLimit")
public List<ReportHelper> getTravellingHelperDetailsUntilYear(String yearLimit);