ca.gc.aafc.dina.jpa.BaseDAO Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of dina-base-api Show documentation
Show all versions of dina-base-api Show documentation
Base DINA API package for Java built on SpringBoot and Crnk
package ca.gc.aafc.dina.jpa;
import io.crnk.core.engine.information.bean.BeanInformation;
import lombok.NonNull;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.hibernate.Session;
import org.hibernate.SimpleNaturalIdLoadAccess;
import org.hibernate.annotations.NaturalId;
import org.springframework.stereotype.Component;
import javax.persistence.EntityGraph;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceUnitUtil;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Base Data Access Object layer. This class should be the only one holding a
* reference to the {@link EntityManager}.
*
*/
@Component
public class BaseDAO {
public static final String LOAD_GRAPH_HINT_KEY = "javax.persistence.loadgraph";
public static final int DEFAULT_LIMIT = 100;
@PersistenceContext
private EntityManager entityManager;
/**
* This method can be used to inject the EntityManager into an external object.
*
* @param creator
*/
public T createWithEntityManager(Function creator) {
Objects.requireNonNull(creator);
return creator.apply(entityManager);
}
/**
* Used to call the provided PredicateSupplier with the EntityManager.
* @param where
* @param criteriaBuilder
* @param root
* @param
* @return
*/
public Predicate[] buildPredicateFromSupplier(PredicateSupplier where, CriteriaBuilder criteriaBuilder, Root root) {
return where.supply(criteriaBuilder, root, entityManager);
}
/**
* Utility function that can check if a lazy loaded attribute is actually
* loaded.
*
* @param entity
* @param fieldName
* @return
*/
public Boolean isLoaded(Object entity, String fieldName) {
Objects.requireNonNull(entity);
Objects.requireNonNull(fieldName);
PersistenceUnitUtil unitUtil = entityManager.getEntityManagerFactory().getPersistenceUnitUtil();
return unitUtil.isLoaded(entity, fieldName);
}
/**
* Creates an EntityGraph for the specified entity class with the given attribute nodes.
*
* This method constructs an EntityGraph for the provided entity class and populates it
* with the specified attribute nodes. The attribute nodes can represent either single-level
* attributes or nested attributes in a dot-separated format. If an attribute node contains a dot,
* it will be treated as a nested attribute, and a subgraph will be created.
*
* @param entityClass The class of the entity for which the EntityGraph is constructed.
* @param attributeNodes The attribute nodes to be included in the EntityGraph.
* The attribute nodes can be either single-level attributes or nested attributes
* represented in a dot-separated format (e.g., "attributeName" or "nestedEntity.attributeName").
* @return An EntityGraph for the specified entity class with the given attribute nodes.
*/
public EntityGraph createEntityGraph(Class entityClass, String... attributeNodes) {
EntityGraph graph = entityManager.createEntityGraph(entityClass);
for (String attribute : attributeNodes) {
if (attribute.contains(".")) {
String[] parts = StringUtils.split(attribute, ".", 2);
if (parts.length == 2) {
graph.addSubgraph(parts[0]).addAttributeNodes(parts[1]);
}
} else {
graph.addAttributeNodes(attribute);
}
}
return graph;
}
/**
* Find a POJO/scalar(class that is not necessary an entity) Projection from a query.
* @param typeClass class of the result
* @param sql sql query. Usually a jpql query.
* @param parameters optional parameters for the query
* @return the POJO/Scalar or null if not found
*/
public T findOneByQuery(Class typeClass, String sql,
List> parameters) {
TypedQuery tq = entityManager.createQuery(sql, typeClass);
if (parameters != null) {
for (Pair param : parameters) {
tq.setParameter(param.getKey(), param.getValue());
}
}
try {
return tq.getSingleResult();
} catch (NoResultException nrEx) {
return null;
}
}
/**
* Find a list of POJO/scalar(class that is not necessary an entity) Projection from a query.
* @param typeClass class of the result
* @param sql sql query. Usually a jpql query.
* @param parameters optional parameters for the query
* @return the list of POJO/Scalar or null if not found
*/
public List findAllByQuery(Class typeClass, String sql,
List> parameters) {
return findAllByQuery(typeClass, sql, parameters, -1, -1);
}
/**
* Find a list of POJO/scalar(class that is not necessary an entity) Projection from a query.
* @param typeClass class of the result
* @param sql sql query. Usually a jpql query.
* @param parameters optional parameters for the query
* @param limit optional parameters to limit the page size.
* @param offset optional parameters to set the page offset. If used make sure the query includes an ORDER by.
* @return the list of POJO/Scalar or null if nothing found
*/
public List findAllByQuery(Class typeClass, String sql,
List> parameters, int limit, int offset) {
TypedQuery tq = entityManager.createQuery(sql, typeClass);
if (parameters != null) {
for (Pair param : parameters) {
tq.setParameter(param.getKey(), param.getValue());
}
}
// greater than 10 000 is stream should be used
if (limit > 0 && limit < 10_000) {
tq.setMaxResults(limit);
}
if (offset > 0) {
tq.setFirstResult(offset);
}
try {
return tq.getResultList();
} catch (NoResultException nrEx) {
return null;
}
}
/**
* Find an entity by its primary key.
*
* @param id
* @param entityClass
* @return
*/
public T findOneByDatabaseId(Object id, Class entityClass) {
return entityManager.find(entityClass, id);
}
/**
* Find an entity by its {@link NaturalId}. The method assumes that the
* naturalId is unique.
*
* @param id
* @param entityClass
* @return
*/
public T findOneByNaturalId(Object id, Class entityClass) {
Session session = entityManager.unwrap(Session.class);
return session.bySimpleNaturalId(entityClass).load(id);
}
/**
* Find an entity by its {@link NaturalId}. The method assumes that the
* naturalId is unique.
*
* @param id
* @param entityClass
* @param hints
* @return
*/
public T findOneByNaturalId(Object id, Class entityClass, Map hints) {
// Hibernate 5 doesn't support hint on natural id, so we are creating a real query
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery criteria = criteriaBuilder.createQuery(entityClass);
Root root = criteria.from(entityClass);
criteria.where(criteriaBuilder.equal(root.get(getNaturalIdFieldName(entityClass)), id));
criteria.select(root);
TypedQuery query = entityManager.createQuery(criteria);
if (hints != null) {
hints.forEach(query::setHint);
}
try {
return query.getSingleResult();
} catch (NoResultException nrEx) {
return null;
}
}
/**
* Find an entity by a specific property. The method assumes that the property
* is unique.
*
* @param clazz
* @param property
* @param value
* @return the entity or null if not found
*/
public T findOneByProperty(Class clazz, String property, Object value) {
// Create a criteria to retrieve the specific property.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery criteria = criteriaBuilder.createQuery(clazz);
Root root = criteria.from(clazz);
criteria.where(criteriaBuilder.equal(root.get(property), value));
criteria.select(root);
TypedQuery query = entityManager.createQuery(criteria);
try {
return query.getSingleResult();
} catch (NoResultException nrEx) {
return null;
}
}
/**
* Find an entity by specific properties. The method assumes that the properties
* are part of a unique constraint (that the query will return nothing or 1 result but never more).
* @param clazz
* @param propertiesAndValue
* @param
* @return
*/
public T findOneByProperties(Class clazz, List> propertiesAndValue) {
// Create a criteria to retrieve the specific property.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery criteria = criteriaBuilder.createQuery(clazz);
Root root = criteria.from(clazz);
Predicate whereClause =
PredicateHelper.appendPropertiesEqual(null, criteriaBuilder, root, propertiesAndValue);
criteria.where(whereClause);
criteria.select(root);
TypedQuery query = entityManager.createQuery(criteria);
try {
return query.getSingleResult();
} catch (NoResultException nrEx) {
return null;
}
}
/**
* Find one or more entity by a specific property. The number of records returned is limited
* to {@link #DEFAULT_LIMIT}.
*
* @param clazz
* @param property
* @param value
* @return list of entities or empty list if nothing is found
*/
public List findByProperty(Class clazz, String property, Object value) {
// Create a criteria to retrieve the specific property.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery criteria = criteriaBuilder.createQuery(clazz);
Root root = criteria.from(clazz);
criteria.where(criteriaBuilder.equal(root.get(property), value));
criteria.select(root);
TypedQuery query = entityManager.createQuery(criteria);
return query.setMaxResults(DEFAULT_LIMIT).getResultList();
}
/**
* Check for the existence of a record based on a property and a value
*
* @param clazz
* @param property
* @param value
* @param
* @return
*/
public boolean existsByProperty(Class clazz, String property, Object value) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery(Integer.class);
Root from = cq.from(clazz);
cq.select(cb.literal(1))
.where(
cb.equal(
from.get(property),
value))
.from(clazz);
TypedQuery tq = entityManager.createQuery(cq);
return !tq.getResultList().isEmpty();
}
/**
* Check for the existence of a record by natural id.
*
* @param naturalId
* @param entityClass
* @return
*/
public boolean existsByNaturalId(
@NonNull Object naturalId,
@NonNull Class entityClass
) {
return existsByProperty(entityClass, getNaturalIdFieldName(entityClass), naturalId);
}
/**
* Returns a reference to an entity that should exist without actually loading it. Useful to set
* relationships without loading the entity.
*
* @param entityClass
* @param naturalId
* @return
*/
public T getReferenceByNaturalId(Class entityClass, Object naturalId) {
SimpleNaturalIdLoadAccess loadAccess = entityManager.unwrap(Session.class)
.bySimpleNaturalId(entityClass);
return loadAccess.getReference(naturalId);
}
/**
* Set a relationship by calling the provided {@link Consumer} with a reference Entity loaded by
* NaturalId. A reference to the entity allows to set a foreign key without loading the other entity.
*
* Usage:
*
* Using the object 'dep', set the relationship to DepartmentType using only its NaturalId (depTypeUUID).
* baseDAO.setRelationshipByNaturalIdReference(DepartmentType.class, depTypeUUID,
(x) -> dep.setDepartmentType(x));
*
* @param entityClass entity to link to that will be loaded with a reference entity
* @param naturalId value
* @param objConsumer
*/
public void setRelationshipByNaturalIdReference(Class entityClass, Object naturalId, Consumer objConsumer) {
objConsumer.accept(getReferenceByNaturalId(entityClass, naturalId));
}
/**
* Save the provided entity.
*
* @param entity
*/
public void create(Object entity) {
create(entity, false);
}
/**
* Save the provided entity.
*
* @param entity
* @param flush should the changes made in the current transaction be flushed immediately to the database
*/
public void create(Object entity, boolean flush) {
entityManager.persist(entity);
if (flush) {
entityManager.flush();
}
}
/**
* Merge the state of a given entity into the current persistence context.
*
* @param Type of the entity
* @param entity entity to update
* @return returns the managed instance the state was merged to.
*/
public E update(E entity) {
E result = entityManager.merge(entity);
// Flush here to throw any validation errors:
entityManager.flush();
return result;
}
/**
* Delete the provided entity.
*
* @param entity
*/
public void delete(Object entity) {
entityManager.remove(entity);
}
/**
* Given a class, this method will extract the name of the field annotated with {@link NaturalId}.
*
* @param entityClass
* @return
*/
public String getNaturalIdFieldName(Class> entityClass) {
BeanInformation beanInfo = BeanInformation.get(entityClass);
// Check for NaturalId:
for (String attrName : beanInfo.getAttributeNames()) {
if (beanInfo.getAttribute(attrName).getAnnotation(NaturalId.class).isPresent()) {
return attrName;
}
}
return null;
}
/**
* Refresh an entity from the database.
* This will revert any non-flushed changes made in the current transaction to the entity, and refresh its state to what is currently defined on the database.
* @param entity
*/
public void refresh(Object entity) {
entityManager.refresh(entity);
}
/**
* Force a flush to the database.
* This is usually not necessary unless it is important to flush changes in the current transaction
* at a very specific moment. Otherwise, the transaction will automatically flush at the end.
*/
public void flush() {
entityManager.flush();
}
/**
* Remove the given entity from the persistence context, causing a managed entity to become detached.
* This will revert any non-flushed changes made in the current transaction to the entity.
*
* Also used to remove the object from the first-level cache.
* @param entity
*/
public void detach(Object entity) {
entityManager.detach(entity);
}
/**
* Given a class, this method will return the name of the field annotated with {@link Id}.
*
* @param entityClass
* @return
*/
public String getDatabaseIdFieldName(Class> entityClass) {
return entityManager.getMetamodel()
.entity(entityClass)
.getId(Serializable.class)
.getName();
}
/**
* returns a {@link CriteriaBuilder} for the creation of {@link CriteriaQuery},
* {@link Predicate}, {@link Expression}, and compound selections.
*
* @return {@link CriteriaBuilder}
*/
public CriteriaBuilder getCriteriaBuilder() {
return entityManager.getCriteriaBuilder();
}
/**
* Returns a List of entities based off a given criteria.
*
* @param
* - Type of result list
* @param criteria
* - criteria to generate the typed query
* @param start
* - position of first result to retrieve
* @param maxResult
* - maximum number of results to return
* @return List of entities
*/
public List resultListFromCriteria(CriteriaQuery criteria, int start, int maxResult) {
return resultListFromCriteria(criteria, start, maxResult, null);
}
/**
* Returns a List of entities based off a given criteria and a Hibernate hint.
*
* @param
* - Type of result list
* @param criteria
* - criteria to generate the typed query
* @param start
* - position of first result to retrieve
* @param maxResult
* - maximum number of results to return
* @param hints
* - Hibernate hint to set on the query
* @return List of entities
*/
public List resultListFromCriteria(CriteriaQuery criteria, int start, int maxResult, Map hints) {
TypedQuery query = entityManager.createQuery(criteria);
if (hints != null) {
hints.forEach(query::setHint);
}
return query
.setFirstResult(start)
.setMaxResults(maxResult)
.getResultList();
}
/**
* Returns the resource count from a given predicate supplier.
*
* @param entity type
* @param entityClass - entity class to query cannot be null
* @param predicateSupplier - function to return the predicates cannot be null
* @return resource count
*/
public Long getResourceCount(
@NonNull Class entityClass,
@NonNull PredicateSupplier predicateSupplier
) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery countQuery = cb.createQuery(Long.class);
Root root = countQuery.from(entityClass);
countQuery.select(cb.count(root));
countQuery.where(predicateSupplier.supply(cb, root, entityManager));
return entityManager.createQuery(countQuery).getSingleResult();
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy