How to refer mapped superclass fields in JPA query syntax? - java

I have a class with multiple mapped superclasses
#EqualsAndHashCode(callSuper = true)
#Entity(name = "Supported_cars_usage")
#Data
#NoArgsConstructor
public class SupportedCarUsage extends SupportedUsageBase {
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "supported_car_id")
private SupportedCar supportedCar;
}
#MappedSuperclass
#Data
#EqualsAndHashCode(callSuper = true)
#NoArgsConstructor
public class SupportedUsageBase extends BaseEntity {
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
User user;
boolean allowed;
}
I am trying to select for, search by and group by some nested field. For the beginning I found I can refer nested field at all.
I tried variations like
Query query = em.createQuery("select supportedCar, allowed from Supported_cars_usage");
Query query = em.createQuery("select supportedCar, super.allowed from Supported_cars_usage");
Query query = em.createQuery("select supportedCar, SupportedUsageBase.allowed from Supported_cars_usage");
but failed with various errors. Is these some syntax to refer fields inside mapped superclass?
Database itself is created normally.
Getters are present and created automatically with Lombok (see #Data annotation).
I wish not to use native queries.

You can write something like this:
List<Object[]> result = em.createQuery("select s.supportedCar, s.allowed from Supported_cars_usage s").getResultList();

Related

JPA - Eager ManyToOne on demand

I have entity Workflow which has #OneToMany relation with ValidationResults class. It's fetch Lazy but sometimes I would like to get all the Workflows and interate on them accessing the ValidationResults. In that moment I want jpa to get all the data eagerly not query each time I access ValidationResults. I use springDataJpa, How to do it, is there any way to do it with #Query ?
I try to achieve something like that but I don't know how
//here all the workflows has corresponding data eagerly
List<Workflow> workflows = workflowService.getAllWorkflowsWithValidationResultsEagerly();
//here validationResults ref is lazy, when I try to access it it does query
List<Workflow> workflows = workflowService.getAllWorkflowsUsually();
Here are my entities.
#Entity
#Table(name = "workflow")
public class Workflow {
..............
#OneToMany(fetch = FetchType.LAZY, mappedBy = "workflow", cascade = CascadeType.ALL)
private Set<ValidationResults> validationResultsSet = new HashSet<>();
public Set<ValidationResults> getValidationResultsSet(){return this.validationResultsSet;}
...............
}
And ValidationResult class
#Entity
#Table(name = "validation_results")
public class ValidationResults {
...
#ManyToOne
#JoinColumn(name = "workflow_id", insertable = false, updatable = false)
private Workflow workflow;
....
}
The spring boot-ish way of doing this is by using the #EntityGraph as described in the documentation.
You can use fetch join in order to do it on #Query https://www.logicbig.com/tutorials/java-ee-tutorial/jpa/fetch-join.html
#Query("SELECT DISTINCT e FROM Employee e INNER JOIN FETCH e.tasks t")
If you don't want to create another query, just call .size() of your list

Hibernate #Where annotation not working with inheritance

I am using Hibernate 5.1.2
I have run into an unexpected problem that I can't seem to work around. Here's the summary of my data model:
dfip_project_version is my superclass table, and dfip_appln_proj_version is my subclass table. dfip_application contains a list of dfip_appln_proj_versions.
I have mapped this as follows:
#Table(name = "DFIP_PROJECT_VERSION")
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class AbstractProjectVersion {
#Id #GeneratedValue
#Column(name = "PROJECT_VERSION_OID")
Long oid;
#Column(name = "PROJ_VSN_EFF_FROM_DTM")
Timestamp effFromDtm;
#Column(name = "PROJ_VSN_EFF_TO_DTM")
Timestamp effToDtm;
#Column(name = "PROJECT_VERSION_TYPE")
#Type(type = "project_version_type")
ProjectVersionType projectVersionType;
}
#Table(name = "DFIP_APPLN_PROJ_VERSION")
#Entity
class ApplicationProjectVersion extends AbstractProjectVersion {
#OneToOne
#JoinColumn(name = "APPLICATION_OID", nullable = false)
Application application;
public ApplicationProjectVersion() {
projectVersionType = ProjectVersionType.APPLICATION;
}
}
#Table(name = "DFIP_APPLICATION")
#Entity
class Application {
#Id #GeneratedValue
#Column(name = "APPLICATION_OID")
Long oid;
#OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#Where(clause = "PROJ_VSN_EFF_TO_DTM is null")
List<ApplicationProjectVersion> applicationVersions = [];
}
I am using the #Where annotation so that only the current ApplicationProjectVersion is retrieved with the Application.
The problem with this is that Hibernate assumes that the column I am referencing is in the dfip_appl_proj_version table, when it's actually on the super-class table (dfip_project_version).
Here's what I tried so far to work around this limitation:
Attempt 1
I tried putting the #Where annotation onto the AbstractProjectVersion super-class, like so:
#Table(name = "DFIP_PROJECT_VERSION")
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Where(clause = "PROJ_VSN_EFF_TO_DTM is null")
public abstract class AbstractProjectVersion {
...etc...
}
This did nothing, as the WHERE clause does not seem to be noticed when retrieving the Application.
Attempt 2
I made the applicationVersions list on Application LAZY, and tried to map latestVersion manually like this:
#Table(name = "DFIP_APPLICATION")
#Entity
class Application {
#Id #GeneratedValue
#Column(name = "APPLICATION_OID")
Long oid;
#OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.LAZY)
#Fetch(FetchMode.SELECT)
List<ApplicationProjectVersion> applicationVersions = [];
#ManyToOne
#JoinColumnsOrFormulas([
#JoinColumnOrFormula(formula = #JoinFormula(value = "(APPLICATION_OID)", referencedColumnName="APPLICATION_OID")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "(select apv.PROJECT_VERSION_OID from DFIP_PROJECT_VERSION pv, DFIP_APPLN_PROJ_VERSION apv where apv.PROJECT_VERSION_OID = pv.PROJECT_VERSION_OID and apv.APPLICATION_OID = APPLICATION_OID and pv.PROJ_VSN_EFF_TO_DTM is null)", referencedColumnName="PROJECT_VERSION_OID")),
])
ApplicationProjectVersion latestVersion;
}
This caused Hibernate to generate the following SQL (snippet):
from DFIP_APPLICATION this_
left outer join DFIP_APPLN_PROJ_VERSION applicatio2_
on (this_.APPLICATION_OID)=applicatio2_.APPLICATION_OID and
(select apv.PROJECT_VERSION_OID from DFIP_PROJECT_VERSION pv, DFIP_APPLN_PROJ_VERSION apv
where apv.PROJECT_VERSION_OID = pv.PROJECT_VERSION_OID and apv.APPLICATION_OID = this_.APPLICATION_OID
and pv.PROJ_VSN_EFF_TO_DTM is null)=applicatio2_.PROJECT_VERSION_OID
which resulted in ORA-01799: a column may not be outer-joined to a subquery.
If I can't specify a sub-query in my join formula, then I cannot join to the super-class manually...
Attempt 3
I noticed that usage of #JoinFormula makes Hibernate notice my #Where annotation on the super-class. So I tried the following:
#Table(name = "DFIP_PROJECT_VERSION")
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#Where(clause = "PROJ_VSN_EFF_TO_DTM is null")
public abstract class AbstractProjectVersion {
...etc...
}
#Table(name = "DFIP_APPLICATION")
#Entity
class Application {
#Id #GeneratedValue
#Column(name = "APPLICATION_OID")
Long oid;
#OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.LAZY)
#Fetch(FetchMode.SELECT)
List<ApplicationProjectVersion> applicationVersions = [];
#ManyToOne
#JoinFormula(value = "(APPLICATION_OID)", referencedColumnName="APPLICATION_OID")
ApplicationProjectVersion latestVersion;
}
This generated the following SQL (snippet):
from DFIP_APPLICATION this_
left outer join DFIP_APPLN_PROJ_VERSION applicatio2_
on (this_.APPLICATION_OID)=applicatio2_.APPLICATION_OID and ( applicatio2_1_.PROJ_VSN_EFF_TO_DTM is null)
left outer join DFIP_PROJECT_VERSION applicatio2_1_ on applicatio2_.PROJECT_VERSION_OID=applicatio2_1_.PROJECT_VERSION_OID
This is almost correct! Unfortunately it is not valid SQL, since applicatio2_1_ is used before it is declared on the next line :(.
Now I am out of ideas, so any help would be appreciated. Is there a way to specify a WHERE clause that will bring in only the current ProjectVersion, without getting rid of my inheritance structure?
Related Hibernate issue ticket
I have a solution to this problem. I must admit, it ended up being a little more cumbersome than what I hoped for, but it does work quite well. I waited a couple of months before posting, to make sure that there are no issues and so far, I have not experienced any problems.
My entities are still mapped exactly as described in the question, but instead of using the problematic #Where annotation, I had to use #Filter annotation instead:
public class Application {
#OneToMany(mappedBy="application", orphanRemoval = true, fetch = FetchType.EAGER)
#Cascade([SAVE_UPDATE, DELETE, MERGE])
#Fetch(FetchMode.SELECT)
// Normally we'd just use the #Where(clause = "PROJ_VSN_EFF_TO_DTM is null"), but that doesn't work with collections of
// entities that use inheritance, as we have here.
//
// Hibernate thinks that PROJ_VSN_EFF_TO_DTM is a column on DFIP_APPLN_PROJ_VERSION table, but it is actually on the "superclass"
// table (DFIP_PROJECT_VERSION).
//
// B/c of this, we have to do the same thing with a Filter, which is defined on AbstractProjectVersion.
// NOTE: This filter must be explicitly enabled, which is currently achieved by HibernateForceFiltersAspect
//
#Filter(name="currentProjectVersionOnly",
condition = "{pvAlias}.PROJ_VSN_EFF_TO_DTM is null",
deduceAliasInjectionPoints=false,
aliases=[ #SqlFragmentAlias(alias = "pvAlias", table = "DFIP_PROJECT_VERSION") ]
)
List<ApplicationProjectVersion> projectVersions = [];
}
Since we are using a Filter, we must also define it:
// NOTE: This filter needs to be explicitly turned on with session.enableFilter("currentProjectVersionOnly");
// This is currently achieved with HibernateForceFiltersAspect
#FilterDef(name="currentProjectVersionOnly")
#Table(name = "DFIP_PROJECT_VERSION")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class AbstractProjectVersion {
}
And of course, we must enable it, since Hibernate does not have a setting to automatically turn on all filters.
To do this I created a system-wide Aspect, whose job is to enable specified filters before every call to any DAO:
/**
* Enables provided Hibernate filters every time a Hibernate session is openned.
*
* Must be enabled and configured explicitly from Spring XML config (i.e. no auto-scan here)
*
* #author Val Blant
*/
#Aspect
public class HibernateForceFiltersAspect {
List<String> filtersToEnable = [];
#PostConstruct
public void checkConfig() throws Exception {
if ( filtersToEnable.isEmpty() ) {
throw new IllegalArgumentException("Missing required property 'filtersToEnable'");
}
}
/**
* This advice gets executed before all method calls into DAOs that extend from <code>HibernateDao</code>
*
* #param jp
*/
#Before("#target(org.springframework.stereotype.Repository) && execution(* ca.gc.agr.common.dao.hibernate.HibernateDao+.*(..))")
public void enableAllFilters(JoinPoint jp) {
Session session = ((HibernateDao)jp?.getTarget())?.getSession();
if ( session != null ) {
filtersToEnable.each { session.enableFilter(it) } // Enable all specified Hibernate filters
}
}
}
And the corresponding Spring configuration:
<!-- This aspect is used to force-enable specified Hibernate filters for all method calls on DAOs that extend HibernateDao -->
<bean class="ca.gc.agr.common.dao.hibernate.HibernateForceFiltersAspect">
<property name="filtersToEnable">
<list>
<value>currentProjectVersionOnly</value> <!-- Defined in AbstractProjectVersion -->
</list>
</property>
</bean>
And there you have it - polymorphic #Where clause :).
since you are looking for #Where with inheritance, I assume you are trying to apply some SQL logic globally, maybe hibernate interceptor or SQL inspector would be a better fit for this type of requirement

Spring Data + JPA > New entries of "mappedBy" property are not fetched from DB after SQL INSERT

Hej.
I think I am experiencing some odd behaviour with Spring Data, JPA and the alike. I wrote a flyway migration script that inserts new data into a table cash_entry_note.
Which is related to an entity AbstractCashEntry the following way:
#Entity
#Table(name = AbstractCashEntry.TABLE_NAME)
#Inheritance(strategy = InheritanceType.JOINED)
#Data
#ToString(callSuper = true)
#EqualsAndHashCode(of = "key", callSuper = false)
#NoArgsConstructor
abstract public class AbstractCashEntry extends TenantBaseModel {
#OneToMany(mappedBy = "cashEntry", cascade = { CascadeType.ALL })
protected Set<CashEntryNote> notes = new HashSet<>();
which is the super class for CashEntry
#Entity
#Table(name = "cash_entry")
#Data
#ToString(callSuper = true, exclude = "group")
#EqualsAndHashCode(callSuper = true)
#NoArgsConstructor
public class CashEntry extends AbstractCashEntry {
and finally the class CashEntryNote itself
#Entity
#Table(name = "cash_entry_note")
#Data
#ToString(callSuper = true, exclude = "cashEntry")
#EqualsAndHashCode(callSuper = true)
#NoArgsConstructor
#AllArgsConstructor
public class CashEntryNote extends BaseModel {
#ManyToOne(optional = false)
private AbstractCashEntry cashEntry;
#ManyToOne(optional = false)
private Note note;
#ManyToOne(optional = false)
#DiffIgnore
private User reporter;
public LocalDateTime getDate() {
return createdAt;
}
}
now what I do is some plain and simple SQL inserts:
INSERT INTO [cash_entry_note] (created_at, modified_at, cash_entry, note, reporter) VALUES (GETDATE(), GETDATE(), #cash_key, #initial_note, 'username')
After that, I would expect the JpaRepository to return the CashEntry entity with the attached new notes, I just recently inserted. However, this is not the case. The property notes of a CashEntry is always an empty set or contains the notes that where already there prior to the SQL inserts. Even when trying to use FetchType.EAGER or restarting the server, the repository does not return the newly sql-inserted notes. What am I missing? Anybody got an idea?
I should also mention, that this project is using [Javers][1] http://javers.org/documentation/ to track changes in entites and provide them later on in an audit log.
Got it. The #cash_key variable somehow contained spaces or a tab or any invisible control characters - which however did not prevent it to work in a comparison in a where clause before the insert, so that was really hard to pinpoint.
SELECT #cash_key = min(cash_key) FROM cash_entry is to blame here

#Where clause does not work inside hibernate join query

I have 2 entities with #Where annotation. First one is Category;
#Where(clause = "DELETED = '0'")
public class Category extends AbstractEntity
and it has the following relation;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "category")
private Set<SubCategory> subCategories = Sets.newHashSet();
and second entity is SubCategory;
#Where(clause = "DELETED = '0'")
public class SubCategory extends AbstractEntity
and contains corresponding relation;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CATEGORY_ID")
private Category category;
Whenever I call the following Dao method;
#Query(value = "select distinct category from Category category join fetch category.subCategories subcategories")
public List<Category> findAllCategories();
I got the following sql query;
select
distinct category0_.id as id1_3_0_,
subcategor1_.id as id1_16_1_,
category0_.create_time as create2_3_0_,
category0_.create_user as create3_3_0_,
category0_.create_userip as create4_3_0_,
category0_.deleted as deleted5_3_0_,
category0_.update_time as update6_3_0_,
category0_.update_user as update7_3_0_,
category0_.update_userip as update8_3_0_,
category0_.version as version9_3_0_,
category0_.name as name10_3_0_,
subcategor1_.create_time as create2_16_1_,
subcategor1_.create_user as create3_16_1_,
subcategor1_.create_userip as create4_16_1_,
subcategor1_.deleted as deleted5_16_1_,
subcategor1_.update_time as update6_16_1_,
subcategor1_.update_user as update7_16_1_,
subcategor1_.update_userip as update8_16_1_,
subcategor1_.version as version9_16_1_,
subcategor1_.category_id as categor11_16_1_,
subcategor1_.name as name10_16_1_,
subcategor1_.category_id as categor11_3_0__,
subcategor1_.id as id1_16_0__
from
PUBLIC.t_category category0_
inner join
PUBLIC.t_sub_category subcategor1_
on category0_.id=subcategor1_.category_id
where
(
category0_.DELETED = '0'
)
Could you please tell me why the above query lacks
and subcategor1_.DELETED = '0'
inside its where block?
I have just solved a similar problem in my project.
It is possible to put #Where annotation not only on Entity, but on also on your child collection.
According to the javadoc:
Where clause to add to the element Entity or target entity of a collection
In your case, it would be like :
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "category")
#Where(clause = "DELETED = '0'")
private Set<SubCategory> subCategories = Sets.newHashSet();
Please find a similar issues resolved here
I believe thus solution is not as invasive compared to using Hibernate Filters.These filters are disabled by default and operate on Session level, thus enabling them each time new Session opens is extra work especially when your DAO works through abstractions like Spring Data
This is a quick reply;
#Where(clause = "DELETED = '0'")
public class SubCategory extends AbstractEntity
Where will effect when direct query for SubCategry.
To not get deleted sub categories use Hibernate Filters
as exampled on here

JPA multiple queries instead of one

I have two entities:
#Entity
#Table(name = "ACCOUNT")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class MyCloudAccount implements Serializable {
...
#OneToMany(mappedBy = "account", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<ServerInstance> servers = new HashSet<ServerInstance>();
...
}
#Entity
#Table(name = "SERVER_INSTANCE")
public class ServerInstance implements Serializable {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ACCOUNT_ID")
private MyCloudAccount account;
...
}
I am getting all accounts by this code:
StringBuilder sql = new StringBuilder();
sql.append("SELECT e FROM ");
sql.append(persistentClass.getName());
sql.append(" e");
return entityManager.createQuery(sql.toString()).getResultList();
And this produces one query for the account and N queries for the servers instead of one with outer join. How to force JPA to make the query in optimal way?
I find it more convenient to use Java Persistence Query Language
you can do:
#NamedQueries{
#NamedQuery(name="myQuery" query="SELECT a FROM MyCloudAccount JOIN FETCH a.servers")
}
public class MyCloudAccount{
...
}
then you can do
TypedQuery<MyCloudAccount> query = em.createNamedQuery("MyCloudAccount.myQuery", MyCloudAccount.class);
List<MyCloudAccount> results = query.getResultList();
EDIT
You are actually already using JPQL. The key thing to your problem is using the JOIN FECTH command.

Categories