Hibernate OGM with MongoDB could not resolve property - mongodb

This HQL query give me error:
String q4 = "SELECT i, COUNT(ie) FROM CorsoStudi cs \n"
+ "\t JOIN cs.inserogati ie JOIN ie.insegn i \n"
+ "\t WHERE cs.nome = 'Laurea in Informatica' \n"
+ "\t GROUP BY i";
The error is:
Exception in thread "main" java.lang.IllegalArgumentException: org.hibernate.QueryException: could not resolve property: insegn. of: component[_id,annierogazione,annoaccademico,crediti,discriminante,discriminantemodulo,hamoduli,id_facolta,insegn,inserogato_padre,modulo,nomemodulo,nomeunita,programma]
This is InsErogato:
#Embeddable
public class InsErogato {
private Integer _id;
private String annoaccademico;
#Embedded
private Insegn insegn;
#Embedded
private Discriminante discriminante;
private Integer modulo;
private String discriminantemodulo;
private String nomemodulo;
private Double crediti;
private String programma;
private Integer id_facolta;
private String hamoduli;
#Embedded
private InsErogatoPadre inserogato_padre;
private String nomeunita;
private Integer annierogazione;
// constructors, getters and setters and toString
}
and this is Insegn:
#Embeddable
public class Insegn {
private Integer _id;
private String nomeins;
private String codiceins;
// constructors, getters and setters and toString
}
Main:
// begin transaction
entityManager.getTransaction().begin();
List<Object[]> insegn = entityManager
.createQuery(q4, Object[].class)
.getResultList();
for(Object[] i : insegn) {
Insegn ins = (Insegn)i[0];
Long count = (Long)i[1];
System.out.println("nomeins: " + ins.getNomeins() + ", numero inserogati: " + count);
}
// commit transaction
entityManager.getTransaction().commit();
The MongoDB structure:
https://i.stack.imgur.com/qFusC.jpg
https://i.stack.imgur.com/k04HK.png
https://i.stack.imgur.com/H8nhS.png
https://i.stack.imgur.com/eYl2M.png
I tried to change the query but Hibernate doesn't find "insegn" (and also "discriminante") property in "inserogato", but he can find other simple attributes from it (like "annoaccademico" etc.).
The same query works on Hibernate ORM with PostgreSQL.
Maybe I have to add something in the annotations, or change the mongoDB structure (?).
I'm using Hibernate OGM 5.3.1.Final and MongoDB 3.6.3 JDBC Driver.

The same query works on Hibernate ORM with PostgreSQL
But PostgreSQL is a SQL database, MongoDB is a NoSQL database. Hibernate OGM is a means to add Hibernate ORM to NoSQL databases. The '(H)SQL' for OGM is limited (see below).
You don't say what your application is deployed on. I use WildFly 12.0.0.Final. I've had OGM with MongoDB working on version 11 & 12.
entityManager.getTransaction().begin();
I use the (WildFly) container to handle transactions. Annotate my EJB.
#TransactionManagement(TransactionManagementType.CONTAINER)
I don't believe you can use (H)SQL per se with Hiberate OGM but:
Use JPQL - only for simple queries for now
Use the NoSQL native query mapping the result as managed entities
Use Hibernate Search queries - primarily full-text queries
It says in the documentation:
In particular and of notice, what is not supported is:
cross entity joins
JPQL functions in particular aggregation functions like count
JPQL update and delete queries
One of my queries:
Query query = mongoDBEntityManager.createQuery("FROM FoodsCosmeticsMedicines f WHERE f.ean = :ean")
.setParameter("ean", ean);
The entity (the #Expose are for the JSON)
#Entity(name = "FoodsCosmeticsMedicines")
#Indexed
#Table(name = "foodsCosmeticsMedicines")
public class FoodsCosmeticsMedicines implements Serializable {
// Arrays of Objects
#Expose(deserialize = true, serialize = true)
#Embedded
ProductCharacteristics productCharacteristics;
#Expose(deserialize = true, serialize = true)
#Embedded
CalcNutrition calcNutrition;
#Expose(deserialize = true, serialize = true)
#Embedded
Nutrients nutrients;
#Expose(deserialize = true, serialize = true)
#Embedded
Enumbers enumbers;
#Expose(deserialize = true, serialize = true)
#Embedded
ChemicalsMineralsVitamins chemicalsMineralsVitamins;
#Expose(deserialize = true, serialize = true)
#Embedded
Lifestyle lifestyle;
.....
}
Why are you using JOIN?
With MySQL I use Hibernate as my ORM I would use annotations to map relationships with entities such as:
#OneToOne(cascade = CascadeType.ALL, mappedBy = "product", fetch = FetchType.LAZY)
private UriEntity uri;
entityManager.getTransaction().commit();
This is irrelevant as all you've done is read. Maybe you excluded the persist?
I'm sure you've read the documentation. I found all the answers to the problems I encountered here:
Hibernate OGM 5.3.1.Final: Reference Guide

Currently, queries with group by are not supported. You will need to run a native MongoDB query for this particular use case. See the reference documentation for more details about it.
I think the join on an embedded entity instead of a collection is confusing the parser even if it's a valid query. You should see a clearer exception with the following one (equivalent to the one you are trying):
SELECT ie.i, COUNT(ie)
FROM CorsoStudi cs
JOIN cs.inserogati ie
WHERE cs.nome = 'Laurea in Informatica'
GROUP BY ie.i;
It should throw:
java.lang.UnsupportedOperationException: The GROUP BY clause is not supported
Aggregate functions are not supported as well.

Related

How to compose query using the #Query annotation and HQL

Can somebody help how to compose a HQL query with JPA #Query on the following line?
"get me all books which has this author and are published after year X"
Consider a simple entity without relationship - more concern on the JPA #Query and HQL combination:
#Entity
public class Book{
#Id
public int id;
public String title,
public String authorName;
public String yearOfPublish;
.....
}
The query would be:
select b from Book b where b.authorName = :authorName and yearOfPublish > :year
And as you are using Spring Data JPA you don't even need to write the Query! Simply write a method:
List<Book> findAllByAuthorNameAndYearOfPublishGreaterThan(String authorName, String yearOfPublish);
This will create the query for you.
Read for about Query methods in the official documentation: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods

How can I make some change on spring data underlying query

I am using spring data in my project with Postgres as the Database. I have an entity that has a JSON field and I want to keep it as a String in the java context, but a jsonb in the database. Whenever I want to persist an instance of my entity the problem arises since hibernate is treating the JSON property as a Basic type and the underlying query does not change the String type to jsonb type.
#Entity
#Table(name = "a", catalog = "")
#TypeDef(name = "jsonb", typeClass = String.class)
public class A {
#Type(type = "jsonb")
#Column(name = "json-data", columnDefinition = "jsonb")
private String myJsonData;
}
I find the solution for this problem here, but it needs to change the parameters of the underlying PreparedStatement query. So, I need to change the query and add ::jsonb to make Postgres cast the parameter to jsonb. Is there any Annotation related to #Type to make it work?
You can use #ColumnTransformer for this purpose.

Spring JPA/Hibernate Repository findAll is doing N+1 requests instead of a JOIN by default in Kotlin

I am working in a Spring JPA/Hibernate application with Kotlin and I want to find all elements in an entity.
That entity has a foreign key with a #ManyToOne relationship. I want to get all elements with their associated values with a JOIN query avoiding the N+1 problem.
One thing is that the foreign keys are not related to the primary keys, but to another unique field in the entities (UUID).
I was able to make that query with a JOIN creating a custom Query with a JOIN FETCH, but my point is to avoid creating those queries and make those JOINS in all findAlls by default.
Is that possible or do I have to make a query in JPQL manually to force the JOIN FETCH?
Here is the example code:
#Entity
data class A {
#Id
val id: Long,
#Column
val uuid: UUID,
#Column
val name: String
}
#Entity
data class B {
#Id
val id: Long,
...
#Fetch(FetchMode.JOIN)
#ManyToOne
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid", insertable = false, updatable = false)
val a: A
}
#Repository
interface Repo<B> : CrudRepository<B, Long>
...
repo.findAll() // <-- This triggers N+1 queries instead of making a JOIN
...
Another option for you is using EntityGraph. It allows defining a template by grouping the related persistence fields which we want to retrieve and lets us choose the graph type at runtime.
This is an example code that is made by modifying your code.
#Entity
data class A (
#Id
val id: Long,
#Column
val uuid: UUID,
#Column
val name: String
) : Serializable
#NamedEntityGraph(
name = "b_with_all_associations",
includeAllAttributes = true
)
#Entity
data class B (
#Id
val id: Long,
#ManyToOne
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid")
val a: A
)
#Repository
interface ARepo: CrudRepository<A, Long>
#Repository
interface BRepo: CrudRepository<B, Long> {
#EntityGraph(value = "b_with_all_associations", type = EntityGraph.EntityGraphType.FETCH)
override fun findAll(): List<B>
}
#Service
class Main(
private val aRepo: ARepo,
private val bRepo: BRepo
) : CommandLineRunner {
override fun run(vararg args: String?) {
(1..3L).forEach {
val a = aRepo.save(A(id = it, uuid = UUID.randomUUID(), name = "Name-$it"))
bRepo.save(B(id = it + 100, a = a))
}
println("===============================================")
println("===============================================")
println("===============================================")
println("===============================================")
bRepo.findAll()
}
}
On B entity, an entity graph named "b_with_all_associations" is defined, and it is applied to the findAll method of the repository of B entity with LOAD type.
These things will prevent your N+1 problem by fetching with join.
Here is the SQL log for the bRepo.findAll().
select
b0_.id as id1_1_0_,
a1_.id as id1_0_1_,
b0_.a_uuid as a_uuid2_1_0_,
a1_.name as name2_0_1_,
a1_.uuid as uuid3_0_1_
from
b b0_
left outer join
a a1_
on b0_.a_uuid=a1_.uuid
ps1. due to this issue, I don't recommend using many to one relationship with non-pk. It forces us to use java.io.Serializable to 'One' entity.
ps2. EntityGraph can be a good answer to your question when you want to solve the N+1 problem with Join. But I would recommend the better solution: try to solve it with Lazy loading.
ps3. It's not a good idea that using non-pk associations for Hibernate. I truly agree on this comment. I think it's a bug that is not solved yet. It breaks the lazy loading mechanism of hibernate.
As far as I know, the fetch mode only applies to EntityManager.find related queries or when doing lazy loading but never when executing HQL queries, which is what is happening behind the scenes. If you want this to be join fetched, you will have to use an entity graph, which is IMO also better as you can define it per use-site, rather than globally.
I don't know how to configure exactly what you are asking, but the following suggestion might be worth considering...
Change
#Fetch(FetchMode.JOIN)
#ManyToOne
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid", insertable = false, updatable = false)
val a: A
to
#ManyToOne(fetch = javax.persistence.FetchType.LAZY)
#JoinColumn(name = "a_uuid", referencedColumnName = "uuid", insertable = false, updatable = false)
val a: A
And then on your entity A, add the annotation to the class
#BatchSize(size = 1000)
Or whatever batch-size you feel to be appropriate.
This will generally give you the results in 2 queries if you have less than 1000 results. It will load a proxy for A rather than joining to A, but then the first time that A is accessed, it will populate the proxies for BATCH_SIZE number of entities.
It reduces the number of queries from
N + 1
to
1 + round_up(N / BATCH_SIZE)
The findAll implementation will always load b first and then resolve it's dependencies checking the annotations. If you want to avoid the N+1 problem you can add the #Query annotation with JPQL query:
...
#Query("select b from TableB b left join fetch b.a")
repo.findAll()
...

How can I query to find mongo entities whose list of sub-entities contain a field matching a string?

I have a collection of entities that look like this:
public class ClientEntity {
#Id
private String id;
#Indexed(unique = true)
private String clientId;
private String name;
#DBRef
private List<ClientMachineEntity> machines;
...
}
...where ClientMachineEntity looks like:
public class ClientMachineEntity {
#Id
private String id;
#Indexed(unique = true)
private String clientMachineId;
private String hostName;
...
}
I have a working search that finds ClientEntities by matching against "clientId" and "name":
public List<ClientEntity> searchByIdAndName(String id, String name) {
Criteria idCriteria = Criteria.where("clientId").regex(id, "i");
Criteria nameCriteria = Criteria.where("name").regex(name, "i");
Query query = new Query(new Criteria().orOperator(idCriteria, nameCriteria));
...
}
So my question is, how can I expand this search so that it also matches against "clientMachineId" in the list of sub-entities? I tried adding the following criteria:
Criteria machineCriteria = Criteria.where("machines.clientMachineId").regex(id, "i");
...but that doesn't work, presumably because machines is a LIST of entities, not just a single sub-entity.
UPDATE: It seems like what I'm looking for is the .elemMatch() functionality, but when I try that:
Criteria machineCriteria = Criteria.where("machines").elemMatch(Criteria.where("clientMachineId").regex(id, "i"));
...I get the following error:
org.springframework.data.mapping.model.MappingException: No mapping metadata found for class com.mongodb.BasicDBObject
You can't query by fields in subentities linked with DBRef. If ClientMachineEntity would be embedded in ClientMachine - then you could use dot notation or $elemMatch depending on needs.
In your particular example - couldn't field ClientMachineEntity.clientMachineId be saved as _id and used as a primary key? Then you could get the results you need - take a look at: How to query mongodb with DBRef
My suggestion for development with Spring Data MongoDB is - first learn how to (and if it's possible) do it in plain Javascript with MongoDB console, then learn how to do the same with Spring Data MongoDB.

Why won't my EJB Entity work with a non-relational table, when using createNativeQuery?

I'm trying to use entities with a MySQL ndbcluster table. This table type doesn't allow foreign keys, but up until now it hasn't been a problem with my entities.
However, I have run into a bit of a problem, when I try to load an entity using the EntityManager's createNativeQuery method. I need to use this method due to my inability to do this: How to make a CriteriaBuilder join with a custom "on" condition?
My MySQL table looks like this:
CREATE TABLE `category` (
`id` SMALLINT(6) NOT NULL AUTO_INCREMENT,
`category_id` SMALLINT(6) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `category_id` (`category_id`)
)
COLLATE='utf8_general_ci'
ENGINE=ndbcluster
ROW_FORMAT=DEFAULT
If I change the table engine to innodb, and add foreign keys, the createNativeQuery method works fine.
My entity class looks like this:
#Entity
#Table(name = "category")
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Short id;
#OneToMany(mappedBy = "categoryId")
private List<Category> categoryList;
#JoinColumn(name = "category_id", referencedColumnName = "id")
#ManyToOne
private Category categoryId;
public Category() {
}
// getters and setters
}
Even without foreign keys, this entity works fine when I use the CriteriaBuilder for a query, but unfortunately not everything is possible with the CriteriaBuilder.
I get an error when I call getResultList on a Query object created with createNativeQuery. I don't know if this is a bug, or if something should be added to my entity class to make this work.
The error says:
Exception [EclipseLink-6044] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.QueryException
Exception Description: The primary key read from the row [ArrayRecord(
=> 2519
=> 2463
=> Tools)] during the execution of the query was detected to be null. Primary keys must not contain null.
Query: ReadAllQuery(referenceClass=Category sql="select * from `category`")
at org.eclipse.persistence.exceptions.QueryException.nullPrimaryKeyInBuildingObject(QueryException.java:895)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:584)
at org.eclipse.persistence.internal.descriptors.ObjectBuilder.buildObject(ObjectBuilder.java:560)
at org.eclipse.persistence.queries.ObjectLevelReadQuery.buildObject(ObjectLevelReadQuery.java:717)
at org.eclipse.persistence.queries.ReadAllQuery.registerResultInUnitOfWork(ReadAllQuery.java:769)
...
My table contains 1 row, where id=1 and category_id=null, so there are no primary keys with a null-value, despite what the error says. If I remove that row or set category_id=1, I don't get an error.
Need help, please.
Managed to make it work by switching from EclipseLink (JPA 2.0) to OpenJPA (JPA 2.0). Seems like there is a bug somewhere in EclipseLink 2.3.2 and/or GlassFish 3.1.2.2.
I've used EclipseLink (JPA 2.0) in another project of mine, using a slightly different version Netbeans + GlassFish 3.1.1, where I used createNativeQuery on an entity class for a non-relational myisam table. This never caused any problem. It really must be a bug.
But problem solved. Bye, bye EclipseLink, hello OpenJPA.
The issue is case sensitivity. In MySQL your column "id" will be defined in the database as "ID" unless you quote it. If you switch your mappings to upper case it should fix the issue (i.e. "ID").
You could also quote the column name ("'id'")
or set the persistence unit property,
"eclipselink.jpa.uppercase-column-names"="true"