Fetch concurrent tables with Criteria API - java

I've got 3 entity classes:
(The BaseAutoIncrementModel contains the declaration of the id for each entity)
Table 1: Dossier
#Entity
#Table(name = "DOSSIER", schema = "ADOP")
public class Dossier extends BaseAutoIncrementModel<Integer> implements BaseModelCode<Integer> {
...
}
Table 2: AlerteDossier
#Entity
#Table(name = "ALERTE_DOSSIER", schema = "ADOP")
public class AlerteDossier extends BaseAutoIncrementModel<Integer> implements BaseModelCode<Integer> {
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "FK_DOSSIER")
private Dossier dossier;
...
}
Table 3: AlerteEnvoi
#Entity
#Table(name = "ALERTE_ENVOI", schema = "ADOP")
public class AlerteEnvoi extends BaseAutoIncrementModel<Integer> implements BaseModelCode<Integer> {
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "FK_ALERTE_DOSSIER")
private AlerteDossier alerteDossier;
...
}
What I have atm:
CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<AlerteEnvoi> query = builder.createQuery(AlerteEnvoi.class);
Root<AlerteEnvoi> root = query.from(AlerteEnvoi.class);
query.select(root);
List<Predicate> predicateList = new ArrayList<>();
...
As you can see in AlerteEnvoi the AlerteDossier table is eagerly loaded, though in AlerteDossier the Dossier table is lazily loaded.
I need to create, using the Criteria Api, a select statement for AlerteEnvoi where Dossier would also be loaded within the AlerteDossier entity.
I know how I'd need to fetch the AlerteDossier within the AlerteEnvoi if AlerteDossier would be lazily-loaded (root.fetch("alerteDossier", JoinType.LEFT), I've got no clue how to fetch a sub-entity of a sub-entity though. Anyone can help me with this?

I haven't tested it out yet, but heard from a collegue something like this should be do-able:
Fetch<AlerteEnvoi, AlerteDossier> fetchAlerteDossier = root.fetch("alerteDossier", JoinType.LEFT);
fetchAlerteDossier.fetch("dossier", JoinType.LEFT);
I'll be putting this answer as accepted once I've tested it out.

Related

Hibernate and Criteria Api generates wrong Join condition

I got following tables. Lets ignore the fact that the relation is done wrong here. I cannot change that.
Each company can have multiple employes and each employe belongs to only one company.
Table: Company
ID
EMPLOYE_ID
10
100
Table: Employe
ID
NAME
100 (Same as EMPLOYE_ID)
John
Now i want to create a relation #OneToMany between Company -> Employe . My entities look as follow
class Company {
#Id
#Column(name = "id", unique = true, nullable = false)
private String id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "EMPLOYE_ID", referencedColumnName = "ID")
private Set<Employe> employees;
}
No matter if i try to create a uniderectional, or biderection relationship by adding also #ManyToOne on my Employe class, when using Criteria api to select all Company entities and their Employes i always end up with a wrong generated SQL query at the point where it joines the tables. The above relation for example creates following:
FROM company company0
INNER JOIN employe employe0 ON company0.id = employe0.employe_id
I tried several approaches, but i end up almost with the same error. It tries either to access a column which does not exist on the table, or joins wrong columns (e.g. id = id). Or by the following exception
Caused by: org.hibernate.MappingException: Repeated column in mapping
for entity: com.Employe column: id (should be mapped with
insert="false" update="false")"}}
What is a simple approach to create a bidrectional relation with the above table structure?
Note: I finally ended up changing the DB schema. Still, it would be interesting if someone could provide an answer for such a case, even if it is based on a not well formed
The central problem is that the described table structures do not allow a 1:n relationship from Company to Employee. According to the table design (especially the design of PKs) above, a company can only have one employee.
However, if the DB design cannot be changed, the following approach using the JoinColumnOrFormula annotation may lead to partial success.
The #JoinColumnOrFormula annotation is used to customize the join between a child Foreign Key and a parent row Primary Key when we need to take into consideration a column value as well as a #JoinFormula.
See https://docs.jboss.org/hibernate/stable/orm/userguide/html_single/Hibernate_User_Guide.html#associations-JoinColumnOrFormula for details.
More concretely with these Entities
#Entity
#Table(name="t_company")
public class Company {
#Id
#Column(name="id")
private Integer id;
#Column(name="employee_id")
private Integer employeeId;
#OneToMany(mappedBy = "company")
private List<Employee> employees;
// ..
}
#Entity
#Table(name = "t_employee")
public class Employee {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumnOrFormula( column =
#JoinColumn(
name = "id",
referencedColumnName = "employee_id",
insertable = false,
updatable = false
)
)
private Company company;
// ..
}
and this custom repository
#Repository
public class EmployeeRepository {
#Autowired
EntityManager entityManager;
List<Employee> findAll() {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> root = cq.from(Employee.class);
Join<Employee, Company> joinCompany = root.join("company");
TypedQuery<Employee> query = entityManager.createQuery(cq);
return query.getResultList();
}
}
you get the following query:
select
employee0_.id as id1_1_,
employee0_.name as name2_1_
from t_employee employee0_
inner join t_company company1_ on employee0_.id=company1_.employee

How to refer mapped superclass fields in JPA query syntax?

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();

Hibernate L2 cache issues with EntityGraph and LEFT JOIN FETCH queries

I'm using hibernate 5.3.14 with hazelcast 3.11.5 as L2 cache provider and spring boot 2.1.11.
I have 3 entities defined with relations:
one order has many order items
one order has many custom fields
L2 cache is enabled for entities, associations and queries.
#Entity
#Table(name = "orders")
#org.hibernate.annotations.Cache(usage =CacheConcurrencyStrategy.READ_WRITE)
public class Order extends AbstractBaseEntity implements Orderable {
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#LazyCollection(LazyCollectionOption.TRUE)
#Fetch(FetchMode.SELECT)
private List<OrderItem> orderItems;
#MappedSuperclass
public abstract class AbstractBaseEntity
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL)
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#JoinColumn(name = "parent_rid")
#LazyCollection(LazyCollectionOption.TRUE)
private List<CustomField> customFields = new ArrayList<>();
#Entity
#Table(name = "custom_fields")
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class CustomField implements Serializable {
#Entity
#Table(name = "order_items")
#org.hibernate.annotations.Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class OrderItem extends AbstractBaseEntity implements Orderable {
I have one repository:
#Repository
public interface OrderRepository extends JpaRepository<Order, String> {
#EntityGraph(attributePaths = "customFields")
Optional<Order> findById(String rid);
#QueryHints(value = {#QueryHint(name = "org.hibernate.cacheable", value = "true")})
#EntityGraph(attributePaths = "customFields")
#Query("select o from Order left join fetch o.orderItems where o.status = 'ACTIVE' ")
List<Order> findAllActiveWithOrderItems();
There are 3 problems:
repo method findById doesn't load from the cache the main entity, order, with the relation, customFields, indicated by entity graph loaded
cached query results for repo method findAllActiveWithOrderItems does not seem to have the relations, orderItems, loaded by the FETCH JOIN
cached query results
for repo method findAllActiveWithOrderItems does not seem to have the relations loaded by the the EntityGraph, customFields
Are there any known hibernate tickets or workarounds to fix those?
That's a known issue and I think Hibernate 6.0 will fix it, but I don't remember if there ever was a ticket for this.

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

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