JPA/HIBERNATE: How to pass value in #where clause - java

I would like to know if it is possible to configure dynamic #where clauses with JPA anotations. Let's say I have a relationship between two classes - company (parent) and invoces (child).
If have a Annotation declaration like this (pseudo code).
#ManyToOne
#JoinColumn(name = "ID_MEN_PRE", referencedColumnName = "ID_MEN"
#Where(clause="invoice_month=:month")
private Testinvoices invoice;
What I would like to do now is to pass "month" value in the where clause. The result should return only Testinvoices by the given Date.
Is there a way how to do it?

No, however Filters - see further below - can be parameterized.
However it looks to me like you are attempting to model a relationship when actually a query would be a better solution.
Or, create a view filtered by date at the database Level and map invoices to that view rather than a table.
17.1. Hibernate filters
Hibernate3 has the ability to pre-define filter criteria and attach
those filters at both a class level and a collection level. A filter
criteria allows you to define a restriction clause similar to the
existing "where" attribute available on the class and various
collection elements. These filter conditions, however, can be
parameterized. The application can then decide at runtime whether
certain filters should be enabled and what their parameter values
should be. Filters can be used like database views, but they are
parameterized inside the application.

This is not possible in JPA.
For same effect you can use criteria. Please refer to example below
Criteria criteria = session.createCriteria(Employees.class);
if (subsidiaryId != null) {
criteria.add(Restrictions.eq("subsidiaryId", subsidiaryId));
}
if (employeeId != null) {
criteria.add(Restrictions.eq("employeeId", employeeId));
}
if (lastName != null) {
criteria.add(
Restrictions.eq("lastName", lastName).ignoreCase()
);
}

It is not possible to passe a parameter during the runtime but it is possible to use it like this way
#ManyToOne
#JoinColumn(name = "ID_MEN_PRE", referencedColumnName = "ID_MEN"
#Where(clause="invoice_month=january")
private Testinvoices invoice;

Related

JPA inheritance #EntityGraph include optional associations of subclasses

Given the following domain model, I want to load all Answers including their Values and their respective sub-children and put it in an AnswerDTO to then convert to JSON. I have a working solution but it suffers from the N+1 problem that I want to get rid of by using an ad-hoc #EntityGraph. All associations are configured LAZY.
#Query("SELECT a FROM Answer a")
#EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();
Using an ad-hoc #EntityGraph on the Repository method I can ensure that the values are pre-fetched to prevent N+1 on the Answer->Value association. While my result is fine there is another N+1 problem, because of lazy loading the selected association of the MCValues.
Using this
#EntityGraph(attributePaths = {"value.selected"})
fails, because the selected field is of course only part of some of the Value entities:
Unable to locate Attribute with the the given name [selected] on this ManagedType [x.model.Value];
How can I tell JPA only try fetching the selected association in case the value is a MCValue? I need something like optionalAttributePaths.
You can only use an EntityGraph if the association attribute is part of the superclass and by that also part of all subclasses. Otherwise, the EntityGraph will always fail with the Exception that you currently get.
The best way to avoid your N+1 select issue is to split your query into 2 queries:
The 1st query fetches the MCValue entities using an EntityGraph to fetch the association mapped by the selected attribute. After that query, these entities are then stored in Hibernate's 1st level cache / the persistence context. Hibernate will use them when it processes the result of the 2nd query.
#Query("SELECT m FROM MCValue m") // add WHERE clause as needed ...
#EntityGraph(attributePaths = {"selected"})
public List<MCValue> findAll();
The 2nd query then fetches the Answer entity and uses an EntityGraph to also fetch the associated Value entities. For each Value entity, Hibernate will instantiate the specific subclass and check if the 1st level cache already contains an object for that class and primary key combination. If that's the case, Hibernate uses the object from the 1st level cache instead of the data returned by the query.
#Query("SELECT a FROM Answer a")
#EntityGraph(attributePaths = {"value"})
public List<Answer> findAll();
Because we already fetched all MCValue entities with the associated selected entities, we now get Answer entities with an initialized value association. And if the association contains an MCValue entity, its selected association will also be initialized.
I don't know what Spring-Data is doing there, but to do that, you usually have to use the TREAT operator to be able to access the sub-association but the implementation for that Operator is quite buggy.
Hibernate supports implicit subtype property access which is what you would need here, but apparently Spring-Data can't handle this properly. I can recommend that you take a look at Blaze-Persistence Entity-Views, a library that works on top of JPA which allows you map arbitrary structures against your entity model. You can map your DTO model in a type safe way, also the inheritance structure. Entity views for your use case could look like this
#EntityView(Answer.class)
interface AnswerDTO {
#IdMapping
Long getId();
ValueDTO getValue();
}
#EntityView(Value.class)
#EntityViewInheritance
interface ValueDTO {
#IdMapping
Long getId();
}
#EntityView(TextValue.class)
interface TextValueDTO extends ValueDTO {
String getText();
}
#EntityView(RatingValue.class)
interface RatingValueDTO extends ValueDTO {
int getRating();
}
#EntityView(MCValue.class)
interface TextValueDTO extends ValueDTO {
#Mapping("selected.id")
Set<Long> getOption();
}
With the spring data integration provided by Blaze-Persistence you can define a repository like this and directly use the result
#Transactional(readOnly = true)
interface AnswerRepository extends Repository<Answer, Long> {
List<AnswerDTO> findAll();
}
It will generate a HQL query that selects just what you mapped in the AnswerDTO which is something like the following.
SELECT
a.id,
v.id,
TYPE(v),
CASE WHEN TYPE(v) = TextValue THEN v.text END,
CASE WHEN TYPE(v) = RatingValue THEN v.rating END,
CASE WHEN TYPE(v) = MCValue THEN s.id END
FROM Answer a
LEFT JOIN a.value v
LEFT JOIN v.selected s
My latest project used GraphQL (a first for me) and we had a big issue with N+1 queries and trying to optimize the queries to only join for tables when they are required. I have found Cosium
/
spring-data-jpa-entity-graph irreplaceable. It extends JpaRepository and adds methods to pass in an entity graph to the query. You can then build dynamic entity graphs at runtime to add in left joins for only the data you need.
Our data flow looks something like this:
Receive GraphQL request
Parse GraphQL request and convert to list of entity graph nodes in the query
Create entity graph from the discovered nodes and pass into the repository for execution
To solve the problem of not including invalid nodes into the entity graph (for example __typename from graphql), I created a utility class which handles the entity graph generation. The calling class passes in the class name it is generating the graph for, which then validates each node in the graph against the metamodel maintained by the ORM. If the node is not in the model, it removes it from the list of graph nodes. (This check needs to be recursive and check each child as well)
Before finding this I had tried projections and every other alternative recommended in the Spring JPA / Hibernate docs, but nothing seemed to solve the problem elegantly or at least with a ton of extra code
Edited after your comment:
My apologize, I haven't undersood you issue in the first round, your issue occurs on startup of spring-data, not only when you try to call the findAll().
So, you can now navigate the full example can be pull from my github:
https://github.com/bdzzaid/stackoverflow-java/blob/master/jpa-hibernate/
You can easlily reproduce and fix your issue inside this project.
Effectivly, Spring data and hibernate are not capable to determinate the "selected" graph by default and you need to specify the way to collect the selected option.
So first, you have to declare the NamedEntityGraphs of the class Answer
As you can see, there is two NamedEntityGraph for the attribute value of the class Answer
The first for all Value without specific relationship to load
The second for the specific Multichoice value. If you remove this one, you reproduce the exception.
Second, you need to be in a transactional context answerRepository.findAll() if you want to fetch data in type LAZY
#Entity
#Table(name = "answer")
#NamedEntityGraphs({
#NamedEntityGraph(
name = "graph.Answer",
attributeNodes = #NamedAttributeNode(value = "value")
),
#NamedEntityGraph(
name = "graph.AnswerMultichoice",
attributeNodes = #NamedAttributeNode(value = "value"),
subgraphs = {
#NamedSubgraph(
name = "graph.AnswerMultichoice.selected",
attributeNodes = {
#NamedAttributeNode("selected")
}
)
}
)
}
)
public class Answer
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(updatable = false, nullable = false)
private int id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "value_id", referencedColumnName = "id")
private Value value;
// ..
}

Hibernate is making extra SQL statement with #ManyToOne and #Lazy fetching object

I would like someone to explain me why Hibernate is making one extra SQL statement in my straight forward case. Basically i have this object:
#Entity
class ConfigurationTechLog (
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long?,
val configurationId: Long,
val type: String,
val value: String?
) {
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "configurationId", insertable = false, updatable = false)
val configuration: Configuration? = null
}
So as you can see, nothing special there. And when i execute this query :
#Query(value = "SELECT c FROM ConfigurationTechLog c where c.id = 10")
fun findById10() : Set<ConfigurationTechLog>
In my console i see this:
Hibernate:
/* SELECT
c
FROM
ConfigurationTechLog c
where
c.id = 10 */ select
configurat0_.id as id1_2_,
configurat0_.configuration_id as configur2_2_,
configurat0_.type as type3_2_,
configurat0_.value as value4_2_
from
configuration_tech_log configurat0_
where
configurat0_.id=10
Hibernate:
select
configurat0_.id as id1_0_0_,
configurat0_.branch_code as branch_c2_0_0_,
configurat0_.country as country3_0_0_,
configurat0_.merchant_name as merchant4_0_0_,
configurat0_.merchant_number as merchant5_0_0_,
configurat0_.org as org6_0_0_,
configurat0_.outlet_id as outlet_i7_0_0_,
configurat0_.platform_merchant_account_name as platform8_0_0_,
configurat0_.store_type as store_ty9_0_0_,
configurat0_.terminal_count as termina10_0_0_
from
configuration configurat0_
where
configurat0_.id=?
Can someone please explain me, what is happening here ? From where this second query is coming from ?
I assume you are using Kotlin data class. The kotlin data class would generate toString, hashCode and equals methods utilizing all the member fields. So if you are using the returned values in your code in a way that results in calling of any of these method may cause this issue.
BTW, using Kotlin data claases is against the basic requirements for JPA Entity as data classes are final classes having final members.
In order to make an association lazy, Hibernate has to create a proxy instance instead of using the real object, i.e. it needs to create an instance of dynamically generated subclass of the association class.
Since in Kotlin all classes are final by default, Hibernate cannot subclass it so it has to create the real object and initialize the association right away. In order to verify this, try declaring the Configuration class as open.
To solve this without the need to explicitly declare all entities open, it is easier to do it via the kotlin-allopen compiler plugin.
This Link can be useful for understand what kind (common) problem is that N + 1 Problem
Let me give you an example:
I have three Courses and each of them have Students related.
I would like to perform a "SELECT * FROM Courses". This is the first query that i want (+ 1) but Hibernate in background, in order to get details about Students for each Course that select * given to us, will execute three more queries, one for each course (N, there are three Course coming from select *). In the end i will see 4 queries into Hibernate Logs
Considering the example before, probably this is what happen in your case: You execute the first query that you want, getting Configuration Id = 10 but after, Hibernate, will take the entity related to this Configuration, then a new query is executed to get this related entity.
This problem should be related in specific to Relationships (of course) and LAZY Fetch. This is not a problem that you have caused but is an Hibernate Performance Issue with LAZY Fetch, consider it a sort of bug or a default behaviour
To solve this kind of problem, i don't know if will be in your case but ... i know three ways:
EAGER Fetch Type (but not the most good option)
Query with JOIN FETCH between Courses and Students
Creating EntityGraph Object that rappresent the Course and SubGraph that rappresent Students and is added to EntityGraph
Looking at your question, it seems like an expected behavior.
Since you've set up configuration to fetch lazily with #ManyToOne(fetch = FetchType.LAZY), the first sql just queries the other variables. When you try to access the configuration object, hibernate queries the db again. That's what lazy fetching is. If you'd like Hibernate to use joins and fetch all values at once, try setting #ManyToOne(fetch = FetchType.EAGER).

EclipseLink native query and FetchType behaviour

I'm trying to understand EclipseLink behaviour in case if I use native query. So I have Entity like this:
class Entity {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name="other_entity_id")
private OtherEntity otherEntity;
#Column(name = "name")
private String name;
//gets ... sets ...
}
and corresponding table looks like:
**ENTITY**
INTEGER ID;
VARCHAR NAME;
OTHER_ENTITY_ID;
And then I run native query
Query query = getEntityManager().runNativeQuery("select * from ENTITY", Entity.class);
query.getResultList()
Within Entity I have declared OtherEntity otherEntity which is annotated with FetchType.LAZY, however my query selects (*) - all of the columns, including OTHER_ENTITY_ID. The question is - if I run native query that fetches all columns, will fields annotated with FetchType.LAZY populated as if they were FetchType.EAGER or not? I've never worked with EclipseLink before and tyring to decide is it worth using it or not so I would really appreciate any help
Thanks, Cheers
My first advice is to turn on EclipseLink's SQL logging, and execute the equivalent JPQL to load what you are looking for and see the SQL EclipseLink generates to accomplish that to get an understanding of what is required to build objects in your native queries based on your current mappings.
Relationships generally loaded with a secondary query using the values read in from the foreign keys, so eager or lazy fetching is not affected by the native query to read in "Entity" - the query requires the other_entity_id value regardless of the fetch type. When required based on eager/lazy loading, EclipseLink will issue the query required by the mapping.
You can change this though by marking that the relationship is to use joining. In this case, EclipseLink will expect not only the Entity values to be in the query, but the referenced OtherEntity values as well.

Protect entity from cascade delete in Hibernate

Simple question: does anyone have any ideas how to protect some entity from being deleted via CascadeType.ALL in hibernate in runtime (may be with throwing a runtime exception)?
Say, we have some entity:
#Entity
#Table(name = "FOO_ENTITY")
public class FooEntity {
...
}
And I want to protect it from accidental wrong mapping, like:
#Entity
#Table(name = "SOME_OTHER_FOO_ENTITY")
public class SomeOtherFooEntity {
...
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "FOO_ENTITY_ID")
private FooEntity fooEntity;
}
So, it should be possible to delete some entity of type FooEntity via session.delete(fooEntityObj), but it must be disabled ability to delete it via cascade removal (session.delete(someOtherFooEntityObj)).
ATTENTION: For those who read my question inattentive or think that I do not understand what am I asking:
1) I can not remove CascadeType.ALL. The question is: who programming avoid and protect from this?
2) Unit tests is not the way, I'm looking for runtime solution.
One of the ways this can be done is to programmatically inspect Hibernate mapping meta-data and to check whether any delete operation (ALL, REMOVE, orphanRemoval) cascades to the protected entity from any other entity; something like:
String protectedEntityName = FooEntity.class.getName();
SessionFactoryImpl sessionFactory = (SessionFactoryImpl) session.getSessionFactory();
for (EntityPersister entityPersister : sessionFactory.getEntityPersisters().values()) {
for (int i = 0; i < entityPersister.getPropertyTypes().length; i++) {
Type type = entityPersister.getPropertyTypes()[i];
EntityType entityType = null;
if (type.isCollectionType()) {
CollectionType collectionType = (CollectionType) type;
Type elementType = sessionFactory.getCollectionPersister(collectionType.getRole()).getElementType();
if (elementType.isEntityType()) {
entityType = (EntityType) elementType;
}
} else if (type.isEntityType()) {
entityType = (EntityType) type;
}
if (entityType != null && entityType.getName().equals(protectedEntityName)) {
if (entityPersister.getPropertyCascadeStyles()[i].doCascade(CascadingAction.DELETE)) {
// Exception can be thrown from here.
System.out.println("Found! Class: " + entityPersister.getEntityName() + "; property: " + entityPersister.getPropertyNames()[i]);
}
}
}
}
This validation can be performed on server startup or in an integration test.
The advantage of this approach is that you don't have to modify the defined behavior of Hibernate; it just acts as a reminder that you forgot not to cascade deletion to the FooEntity.
Regarding the tests, yes, I know that the OP explicitly said that tests are not an acceptable solution for this use case (and I personally agree with it in general). But these kinds of automatic tests may be useful because you write them and forget about them; you don't have to update the tests whenever you add a new mapping or modify an existing one (which defeats the purpose of the tests because you may forget or oversee to adopt the tests for each possible use case).
For starters I think you do understand what you're asking, you've just settled on a specific solution that many people, myself included, are questioning. It's not inattentiveness...it's trying to solve your actual problem.
If you really want to stop the CascadeType.ALL value on annotations from having its documented effect, instead of verifying that CascadeType.ALL is not used where it shouldn't be (and validating those expectations via unit tests), then extend the DefaultDeleteEventListener and override the deleteEntity method to always pass false to the super implementation for the isCascadeDeleteEnabled flag.
If you want a solution that has some semblance of standard, expected behavior, then define relationship that should do cascading deletes at the schema level, and establish best practices to only use the CascadeTypes that you care about in your code. Maybe that's PERSIST and MERGE, maybe you're using save and update functionality of session factory and so you need to use the Hibernate-specific #CascadeType annotation .
Can't you just remove cascade attribute from #ManyToOne annotation if you don't want to cascade changes to objects associated with the one that is really changed?
The most reliable way to catch any kind of programming error is to write unit tests.
If you practice Test Driven Development you will minimise the chances of "forgetting" to do it.

Join and fetch just some properties not all the collection with JPA and Hibernate

Consedireing this query :
#Query("select c from Contact c "
+ " join fetch c.abonnements ab ")
Set<Contact > findServiceNotifContactFtech();
How to get just two or one property from the collection abonnements in the join clause because with fetch c.abonnements hibernate fetch me all properties and i got 30 properties in the type of abonnements
If you only want to select Abonnement properties and map them to DTO (e.g. MyDTO), you just have to turn the query upside-down:
#Query("select new my.package.MyDTO(c, ab.prop1, ab.prop2) from Abonnements a join a.contact c")
Set<MyDTO> findMyDTO();
Where MyDTO is like:
public MyDTO {
private final Contact contact;
private final String prop1;
private final String prop2;
public MyDTO(Contact contact, String prop1, String prop2) {
this.contact= contact;
this.prop1 = prop1;
this.prop2 = prop2;
}
//getters
}
The only problem is that you will get more Contact entries for each Abonnnment child entry.
If you feel this really worthwhile, then see below but note the comment about it rarely being worthwhile.
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html
19.1.7. Using lazy property fetching
Hibernate3 supports the lazy fetching of individual properties. This
optimization technique is also known as fetch groups. Please note
that this is mostly a marketing feature; optimizing row reads is much
more important than optimization of column reads. However, only
loading some properties of a class could be useful in extreme cases.
For example, when legacy tables have hundreds of columns and the data
model cannot be improved.
To enable lazy property loading, set the lazy attribute on your
particular property mappings:
Lazy property loading requires buildtime bytecode instrumentation. If
your persistent classes are not enhanced, Hibernate will ignore lazy
property settings and return to immediate fetching.
A different way of avoiding unnecessary column reads, at least for
read-only transactions, is to use the projection features of HQL or
Criteria queries. This avoids the need for buildtime bytecode
processing and is certainly a preferred solution.
You can force the usual eager fetching of properties using fetch all
properties in HQL.
Essentially then, as noted in the documentation, Lazy Fetch of properties can be enabled but will be ignored at run-time unless you have 'enhanced' your classes at build time using the Ant Task referenced in the documentation. If your classes are not instrumented then property level lazy loading will be silently ignored. This is line with the JPA specification which does not mandate that lazy loading of individual properties should be supported:
http://download.oracle.com/otn-pub/jcp/persistence-2.0-fr-eval-oth-JSpec/persistence-2_0-final-spec.pdf?AuthParam=1423670601_31b0b4beeddadd49047c08d63a9ad933
See 11.1.16
The LAZY strategy is a hint to the persistence provider runtime
that data should be fetched lazily when it is first accessed. The
implementation is permitted to eagerly fetch data for which the LAZY
strategy hint has been specified
Since you want your projection to fit the properties you need in your DTOs, I'd suggest you give blaze-persistence entity-views a chance. You're DTOs would then look like this:
#EntityView(Contact.class)
public interface MyDTO {
#IdMapping
Integer getId();
// You can have any JPQL expression here
#Mapping("firstname")
String getFirstname();
// property name is the default mapping
String getLastname();
Set<AbonnementDTO> getAbonnements();
}
#EntityView(Abonnement.class)
public interface AbonnementDTO {
#IdMapping
Integer getId();
String getProp1();
}
The use site looks like this
#Inject
EntityManager em;
#Inject
CriteriaBuilderFactory cbf;
#Inject
EntityViewManager evm;
public void test() {
CriteriaBuilder<Contact> cb = cbf.create(em, Contact.class);
List<ContactDTO> result = evm.applySetting(EntityViewSetting.create(ContactDTO.class), cb)
.getResultList();
}
Use SELECT o.property1, o.property2 FROM EntityA o WHERE ...., the result will be a List<Object[2]>, where the array contents will contain the values.
You are expecting select firstname,lastname from contacts mean then
#Query("select c.firstname,c.lastname from Contact c "
+ " join fetch c.abonnements ab ")
Set<Contact > findServiceNotifContactFtech();

Categories