JPA #ElementCollection how can I query? - java

I am using Spring JPA and in order to ad a List of String to my Entity I am using #ElementCollection as below.
#ElementCollection
private Map<Integer, String> categories;
When I use this it generates a table called subscription_categories this contains the following columns subscription(varchar), catergories(varchar) and caterogies_key (int)
If I use my SQL tool on my desktop I can query this table fine with the following
select `subscription_categories`.`subscription` from `subscription_categories` where `subscription_categories`.`categories`='TESTING';
However, when I attempt to use this in Spring Data it fails with a "... not mapped" error
Here are a few attempts below:
#Query("select s.subscription from subscription_categories s where s.categories = ?1")
List<Subscription> findUsernameByCategory(String category);
#Query("select s.subscription from categories s where s.categories = ?1")
List<Subscription> findUsernameByCategory(String category);
Both return the same error.
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException:
categories is not mapped
My question is this:
How can I query the table created by the #ElementCollection?

You can't directly query from #ElementCollection. You should query base entity (I assume its name is Subscription).
#Query("select s from Subscription s where s.categories = ?1")
List<Subscription> findUsernameByCategory(String category);
If you want query by key, then use
#Query("select s from Subscription s where index(s.categories) = ?1")
List<Subscription> findUsernameByCategoryKey(Integer key);

I'd also side with #talex on this and argue that you need to base your query on the parent/container/base object of the #ElementCollection.
In my experience following query should suffice:
#Query("select category from Subscription subscription inner join subscription.categories category")
Side-note: Querying subscription_categories seems to be the wrong path, since this table is part of a different layer (the database layer in Sql/Jpql), while the query should be formed on the Hibernate layer (hql), which uses your entity/class-names as references.
I have used Upper-case class names, instead of lower-case table names.

Related

Hibernate #Formula which return collection

I'm using a legacy database. In my example, we retrieve a product which have some characteristics. In the db, we can find a product table, a characteristic table and a jointable for the manyToMany association.
The only field i need is the label of the characteristics. So, my Product entity will contains a list of characteristics as String. I would like to not create to many entities in order to not overload my sourcecode. Let's see the example :
#Entity
#Table(name = "product")
public class Product implements Serializable {
#Id
#Column(name = "id")
private Long id;
// all field of Product entity
#ElementCollection(targetClass = String.class)
#Formula(value = "(SELECT characteristic.label FROM a jointable JOIN b characteristic ON jointable.characteristic_id = characteristic.id WHERE jointable.product_id = id)")
private Set<String> characteristics = new HashSet<>();
// Getter / setter
}
To represent my characteristics, i tried to use the association of #Formula and #ElementCollection. As you can see, the names of tables (a and b in the query) does not match with my representation of these datas.
But, when I try to load a product, I get an error like "PRODUCT_CHARACTERISTICS table not found".
Here the generated SQL query executed by hibernate :
SELECT product0_.id AS id1_14_0_,
-- Other fields
characteri10_.product_id AS product_1_15_1__,
(SELECT characteristic.label
FROM a jointable JOIN b characteristic ON jointable.characteristic_id = characteristic.id
WHERE jointable.product_id = id) AS formula6_1__,
FROM product product0_
-- Other Joins
LEFT OUTER JOIN product_characteristics characteri10_ ON product0_.cdprd = characteri10_.product_cdprd
WHERE product0_.id = ?;
In the FROM part, we can refind the call of product_characteristics table (which not exist in the database).
So, my main question is the following : How can I get the list of characterics as entity attribute ? Can I reach this result with #Formula ?
Edit
In other words, i would like to load only one attribute from Many to Many mapping. I found an example here but it works only with the id (which can find in the jointable)
I assume that what you want to achieve here is reducing the amount of data that is fetched for a use case. You can leave your many-to-many mapping as it is, since you will need DTOs for this and I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(Product.class)
public interface ProductDto {
#IdMapping
Long getId();
String getName();
#Mapping("characteristics.label")
Set<String> getCharacteristicLabels();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
ProductDto a = entityViewManager.find(entityManager, ProductDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<ProductDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!

JPA Query to filter results based on properties of an Embeddable object, which is a value of #ElementCollection Map in an Entity Model Object

I have an Entity MyEntity which has an ElementCollection Map in it as shown below.
The Map<String, MyEmbeddableObject>, values are an Embeddable object MyEmbeddableObject and keys are 'String'.
The db tables and JPA mapping everything is working correctly in system currently. I just need a query to get the required info.
Assuming that MyEmbeddableObject has some 'String' properties in it, is there a way to write a JPA NamedQuery so that I can filter the results based on comparison of properties from MyEmbeddableObject ?
Also the query should return (the select clause) MyEmbeddableObject and not MyEntity. findMyEmbeddableObjectByAttr1 is what I have tried and doesn't work. It complains the JPA Query Grammar is wrong.
I could get it done using Native query, but ideally I want to be able to do it with JPA NamedQuery for all the disadvantages associated with Native query.
#Entity
#NamedQueries({
#NamedQuery(name = "findMyEmbeddableObjectByAttr1",
query = "Select n from MyEntity v, in (v.myMap) n WHERE n.attr1 = :someUserPassedValue")
})
public class MyEntity {
#ElementCollection
#MapKeyColumn(name="someKey")
private Map<String, MyEmbeddableObject> myMap;
}
#Embeddable
public class MyEmbeddableObject {
private String attr1;
private String attr2;
}
All my SO searches and Google searches showed how one can write query to filter by Map's Keys or by comparing the entire Map value object. Comparison of Map value is an easy option if values are primitive type like String, int. But in my case its an Complex object.
These SO questions are the closest I could get to answers :
Execute "MEMBER OF" query against 'ElementCollection' Map fields in JP-QL (JPA 2.0)
LIKE query on values of Map ElementCollection in entity in HQL
Any help, greatly appreciated! Thanks.

JPA Audit entity direct query

I would like to query audit entity in my JPA environment.
At first I do AuditQuery and it works well, but now I need more advance query witch I can't do with AuditQuery.
Now I need something like
this.em.createQuery("FROM ENTITY_AUD").getResultList();
but I get error :
QuerySyntaxException: ENTITY_AUD is not mapped [ENTITY_AUD]
I understand that this is due that I don't have entity with all properties, but I don't want to have entity because it is audit entity.
Is there a way around it? For me it would be ok to get List of Object.
You can always create native SQL query in JPA. Replace createQuery with createNativeQuery in your code:
List<Object[]> list =
this.em.createNativeQuery("SELECT * FROM ENTITY_AUD").getResultList();
Add a NamedQuery in your Audit class
#Entity
#Table(name = "ENTITY_AUD")
#NamedQueries({
#NamedQuery(name = "Audit.findAll", query = "SELECT a FROM Audit a")})
public class Audit implements Serializable {
...
}
then use the named query
List<Audit> auditList = entityManager.createNamedQuery("Audit.findAll").getResultList();
Read : Implementing a JPA Named Query

Dynamic Named Query in Entity class using JPQL example

I have a named query as below;
#NamedQuery(name = "MyEntityClass.findSomething", query = "SELECT item FROM MyTable mytbl")
Now I want to append dynamic sort clause to this query (based on UI parameters)
Can I get an example using JPQL for doing the same (like how to set a dynamic ORDER BY in the Entity class)
I have already tried using CriteriaQuery, but was looking for a JPQL implementation now.
NamedQueries are by definition NOT dynamic, it is not correct to change them programmatically.
So the way to go is to create a JPQL query (but not a named query) like this:
TypedQuery<MyEntity> query = em.createdQuery("SELECT item FROM MyEntity item ORDER BY "+sortingCol, MyEntity.class);
On the other hand, if you REALLY want to use the named query, you could do that the following way:
#NamedQuery(name = "MyEntityClass.findSomething", query = MyEntity.NAMED_QUERY)
#Entity
public class MyEntity {
public static final NAMED_QUERY= "SELECT item FROM MyTable mytbl";
//+your persistent fields/properties...
}
//and later in your code
TypedQuery<MyEntity> query = entityManager.createQuery(MyEntity.NAMED_QUERY + " ORDER BY " + sortingCol, MyEntity.class);
Complementing for JPA 2.1
As of JPA 2.1 it is possible to define named queries programmatically.
This can be achieved using entityManagerFactory.addNamedQuery(String name, Query).
Example:
Query q = this.em.createQuery("SELECT a FROM Book b JOIN b.authors a WHERE b.title LIKE :title GROUP BY a");
this.em.getEntityManagerFactory().addNamedQuery("selectAuthorOfBook", q);
// then use like any namedQuery
Reference here
This can be useful, for instance, if you have the orderby field defined as a application parameter. So, when the application starts up or on the first run of the query, you could define the NamedQuery with the defined OrderBy field.
On the other side, if your OrderBy can be changed anytime (or changes a lot), then you need dynamic queries instead of NamedQuery (static). It would not worth to (re)create a NamedQuery every time (by performance).
#NamedQuery
Persistence Provider converts the named queries from JPQL to SQL at deployment time.
Until now, there is no feature to create/update the query with #NamedQuery annotation at runtime.
On the other hand, you can use Reflection API, to change the annotation value at runtime. I think It is not solution, also it is not you wanted .
em.createQuery()
Persistence Provider converts the dynamic queries from JPQL to SQL every time it is invoked.
The main advantage of using dynamic queries is that the query can be created based on the user inputs.

EJB - How to search by non-indexed fields?

I'm trying to create "search engine" on my DB.
I have a table with Id, Name and Description.
When I have an Id I can get the record with this Id by find().
But if I want to get records by Name or Description how can I do that? Did I've to set the Name as index?
Thanks.
As a general rule, if you have to frequently query a table by one of its fields and the table contains many records, it might be a good idea to create an index for that field in the database.
About the other question: if you need to get records by name, by description or by some other field and you're using JPA, then use the JPQL query language. For example, assuming that the entity is of type MyEntity (with fields id, name, description) the following query will return a list of entities with name aName:
EntityManager em = ... // get the entity manager
Query q = em.createQuery("SELECT me FROM MyEntity me WHERE me.name = :name");
q.setParameter("name", aName); // aName is the name you're looking for
List<MyEntity> results = (List<MyEntity>) q.getResultList();
Read more about the Java Persistence API in the tutorial.

Categories