com.jaxio.jpa.querybyexample.GenericRepository Maven / Gradle / Ivy
Show all versions of jpa-querybyexample Show documentation
/*
* Copyright 2015 JAXIO http://www.jaxio.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jaxio.jpa.querybyexample;
import org.hibernate.search.annotations.Field;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.persistence.*;
import javax.persistence.criteria.*;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Lists.newArrayList;
/**
* JPA 2 {@link GenericRepository} implementation
*/
public abstract class GenericRepository, PK extends Serializable> {
@Inject
protected ByExampleUtil byExampleUtil;
@Inject
protected ByPatternUtil byPatternUtil;
@Inject
protected ByRangeUtil byRangeUtil;
@Inject
protected ByNamedQueryUtil byNamedQueryUtil;
@Inject
protected ByPropertySelectorUtil byPropertySelectorUtil;
@Inject
protected OrderByUtil orderByUtil;
@Inject
protected MetamodelUtil metamodelUtil;
@Inject
private JpaUtil jpaUtil;
@Inject
protected ByFullTextUtil byFullTextUtil;
protected List> indexedAttributes;
@PersistenceContext
protected EntityManager entityManager;
protected Class type;
protected Logger log;
protected String cacheRegion;
/*
* This constructor needs the real type of the generic type E so it can be given to the {@link javax.persistence.EntityManager}.
*/
public GenericRepository(Class type) {
this.type = type;
this.log = LoggerFactory.getLogger(getClass());
this.cacheRegion = type.getCanonicalName();
}
@PostConstruct
protected void init() {
this.indexedAttributes = buildIndexedAttributes(type);
}
public Class getType() {
return type;
}
/**
* Create a new instance of the repository type.
*
* @return a new instance with no property set.
*/
public abstract E getNew();
/**
* Creates a new instance and initializes it with some default values.
*
* @return a new instance initialized with default values.
*/
public E getNewWithDefaults() {
return getNew();
}
/**
* Gets from the repository the E entity instance.
*
* DAO for the local database will typically use the primary key or unique fields of the given entity, while DAO for external repository may use a unique
* field present in the entity as they probably have no knowledge of the primary key. Hence, passing the entity as an argument instead of the primary key
* allows you to switch the DAO more easily.
*
* @param entity an E instance having a primary key set
* @return the corresponding E persistent instance or null if none could be found.
*/
@Transactional(readOnly = true)
public E get(E entity) {
return entity == null ? null : getById(entity.getId());
}
@Transactional(readOnly = true)
public E getById(PK pk) {
if (pk == null) {
return null;
}
E entityFound = entityManager.find(type, pk);
if (entityFound == null) {
log.warn("get returned null with id={}", pk);
}
return entityFound;
}
/**
* Refresh the given entity with up to date data. Does nothing if the given entity is a new entity (not yet managed).
*
* @param entity the entity to refresh.
*/
@Transactional(readOnly = true)
public void refresh(E entity) {
if (entityManager.contains(entity)) {
entityManager.refresh(entity);
}
}
/*
* Find and load all instances.
*/
@Transactional(readOnly = true)
public List find() {
return find(getNew(), new SearchParameters());
}
/**
* Find and load a list of E instance.
*
* @param entity a sample entity whose non-null properties may be used as search hints
* @return the entities matching the search.
*/
@Transactional(readOnly = true)
public List find(E entity) {
return find(entity, new SearchParameters());
}
/**
* Find and load a list of E instance.
*
* @param searchParameters carries additional search information
* @return the entities matching the search.
*/
@Transactional(readOnly = true)
public List find(SearchParameters searchParameters) {
return find(getNew(), searchParameters);
}
/**
* Find and load a list of E instance.
*
* @param entity a sample entity whose non-null properties may be used as search hints
* @param sp carries additional search information
* @return the entities matching the search.
*/
@Transactional(readOnly = true)
public List find(E entity, SearchParameters sp) {
if (sp.hasNamedQuery()) {
return byNamedQueryUtil.findByNamedQuery(sp);
}
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery criteriaQuery = builder.createQuery(type);
if (sp.getDistinct()) {
criteriaQuery.distinct(true);
}
Root root = criteriaQuery.from(type);
// predicate
Predicate predicate = getPredicate(criteriaQuery, root, builder, entity, sp);
if (predicate != null) {
criteriaQuery = criteriaQuery.where(predicate);
}
// fetches
fetches(sp, root);
// order by
criteriaQuery.orderBy(orderByUtil.buildJpaOrders(sp.getOrders(), root, builder, sp));
TypedQuery typedQuery = entityManager.createQuery(criteriaQuery);
applyCacheHints(typedQuery, sp);
jpaUtil.applyPagination(typedQuery, sp);
List entities = typedQuery.getResultList();
log.debug("Returned {} elements", entities.size());
return entities;
}
/*
* Find a list of E property.
*
* @param propertyType type of the property
* @param entity a sample entity whose non-null properties may be used as search hints
* @param sp carries additional search information
* @param path the path to the property
* @return the entities property matching the search.
*/
@Transactional(readOnly = true)
public List findProperty(Class propertyType, E entity, SearchParameters sp, String path) {
return findProperty(propertyType, entity, sp, metamodelUtil.toAttributes(path, type));
}
/*
* Find a list of E property.
*
* @param propertyType type of the property
* @param entity a sample entity whose non-null properties may be used as search hints
* @param sp carries additional search information
* @param attributes the list of attributes to the property
* @return the entities property matching the search.
*/
@Transactional(readOnly = true)
public List findProperty(Class propertyType, E entity, SearchParameters sp, Attribute, ?>... attributes) {
return findProperty(propertyType, entity, sp, newArrayList(attributes));
}
/*
* Find a list of E property.
*
* @param propertyType type of the property
* @param entity a sample entity whose non-null properties may be used as search hints
* @param sp carries additional search information
* @param attributes the list of attributes to the property
* @return the entities property matching the search.
*/
@Transactional(readOnly = true)
public List findProperty(Class propertyType, E entity, SearchParameters sp, List> attributes) {
if (sp.hasNamedQuery()) {
return byNamedQueryUtil.findByNamedQuery(sp);
}
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery criteriaQuery = builder.createQuery(propertyType);
if (sp.getDistinct()) {
criteriaQuery.distinct(true);
}
Root root = criteriaQuery.from(type);
Path path = jpaUtil.getPath(root, attributes);
criteriaQuery.select(path);
// predicate
Predicate predicate = getPredicate(criteriaQuery, root, builder, entity, sp);
if (predicate != null) {
criteriaQuery = criteriaQuery.where(predicate);
}
// fetches
fetches(sp, root);
// order by
// we do not want to follow order by specified in search parameters
criteriaQuery.orderBy(builder.asc(path));
TypedQuery typedQuery = entityManager.createQuery(criteriaQuery);
applyCacheHints(typedQuery, sp);
jpaUtil.applyPagination(typedQuery, sp);
List entities = typedQuery.getResultList();
log.debug("Returned {} elements", entities.size());
return entities;
}
/**
* Count the number of E instances.
*
* @param sp carries additional search information
* @return the number of entities matching the search.
*/
@Transactional(readOnly = true)
public int findCount(SearchParameters sp) {
return findCount(getNew(), sp);
}
/**
* Count the number of E instances.
*
* @param entity a sample entity whose non-null properties may be used as search hint
* @return the number of entities matching the search.
*/
@Transactional(readOnly = true)
public int findCount(E entity) {
return findCount(entity, new SearchParameters());
}
/**
* Count the number of E instances.
*
* @param entity a sample entity whose non-null properties may be used as search hint
* @param sp carries additional search information
* @return the number of entities matching the search.
*/
@Transactional(readOnly = true)
public int findCount(E entity, SearchParameters sp) {
checkNotNull(entity, "The entity cannot be null");
checkNotNull(sp, "The searchParameters cannot be null");
if (sp.hasNamedQuery()) {
return byNamedQueryUtil.numberByNamedQuery(sp).intValue();
}
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery criteriaQuery = builder.createQuery(Long.class);
Root root = criteriaQuery.from(type);
if (sp.getDistinct()) {
criteriaQuery = criteriaQuery.select(builder.countDistinct(root));
} else {
criteriaQuery = criteriaQuery.select(builder.count(root));
}
// predicate
Predicate predicate = getPredicate(criteriaQuery, root, builder, entity, sp);
if (predicate != null) {
criteriaQuery = criteriaQuery.where(predicate);
}
// construct order by to fetch or joins if needed
orderByUtil.buildJpaOrders(sp.getOrders(), root, builder, sp);
TypedQuery typedQuery = entityManager.createQuery(criteriaQuery);
applyCacheHints(typedQuery, sp);
return typedQuery.getSingleResult().intValue();
}
/**
* Count the number of E instances.
*
* @param entity a sample entity whose non-null properties may be used as search hint
* @param sp carries additional search information
* @param path the path to the property
* @return the number of entities matching the search.
*/
@Transactional(readOnly = true)
public int findPropertyCount(E entity, SearchParameters sp, String path) {
return findPropertyCount(entity, sp, metamodelUtil.toAttributes(path, type));
}
/**
* Count the number of E instances.
*
* @param entity a sample entity whose non-null properties may be used as search hint
* @param sp carries additional search information
* @param attributes the list of attributes to the property
* @return the number of entities matching the search.
*/
@Transactional(readOnly = true)
public int findPropertyCount(E entity, SearchParameters sp, Attribute, ?>... attributes) {
return findPropertyCount(entity, sp, newArrayList(attributes));
}
/**
* Count the number of E instances.
*
* @param entity a sample entity whose non-null properties may be used as search hint
* @param sp carries additional search information
* @param attributes the list of attributes to the property
* @return the number of entities matching the search.
*/
@Transactional(readOnly = true)
public int findPropertyCount(E entity, SearchParameters sp, List> attributes) {
if (sp.hasNamedQuery()) {
return byNamedQueryUtil.numberByNamedQuery(sp).intValue();
}
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery criteriaQuery = builder.createQuery(Long.class);
Root root = criteriaQuery.from(type);
Path> path = jpaUtil.getPath(root, attributes);
if (sp.getDistinct()) {
criteriaQuery = criteriaQuery.select(builder.countDistinct(path));
} else {
criteriaQuery = criteriaQuery.select(builder.count(path));
}
// predicate
Predicate predicate = getPredicate(criteriaQuery, root, builder, entity, sp);
if (predicate != null) {
criteriaQuery = criteriaQuery.where(predicate);
}
// construct order by to fetch or joins if needed
orderByUtil.buildJpaOrders(sp.getOrders(), root, builder, sp);
TypedQuery typedQuery = entityManager.createQuery(criteriaQuery);
applyCacheHints(typedQuery, sp);
return typedQuery.getSingleResult().intValue();
}
@Transactional(readOnly = true)
public E findUnique(SearchParameters sp) {
return findUnique(getNew(), sp);
}
@Transactional(readOnly = true)
public E findUnique(E e) {
return findUnique(e, new SearchParameters());
}
@Transactional(readOnly = true)
public E findUnique(E entity, SearchParameters sp) {
E result = findUniqueOrNone(entity, sp);
if (result != null) {
return result;
}
throw new NoResultException("Developper: You expected 1 result but found none !");
}
@Transactional(readOnly = true)
public E findUniqueOrNone(SearchParameters sp) {
return findUniqueOrNone(getNew(), sp);
}
@Transactional(readOnly = true)
public E findUniqueOrNone(E entity) {
return findUniqueOrNone(entity, new SearchParameters());
}
/*
* We request at most 2, if there's more than one then we throw a {@link javax.persistence.NonUniqueResultException}
*
* @throws javax.persistence.NonUniqueResultException
*/
@Transactional(readOnly = true)
public E findUniqueOrNone(E entity, SearchParameters sp) {
// this code is an optimization to prevent using a count
sp.setFirst(0);
sp.setMaxResults(2);
List results = find(entity, sp);
if (results == null || results.isEmpty()) {
return null;
} else if (results.size() > 1) {
throw new NonUniqueResultException("Developper: You expected 1 result but we found more ! sample: " + entity);
} else {
return results.iterator().next();
}
}
@Transactional(readOnly = true)
public E findUniqueOrNew(SearchParameters sp) {
return findUniqueOrNew(getNew(), sp);
}
@Transactional(readOnly = true)
public E findUniqueOrNew(E e) {
return findUniqueOrNew(e, new SearchParameters());
}
@Transactional(readOnly = true)
public E findUniqueOrNew(E entity, SearchParameters sp) {
E result = findUniqueOrNone(entity, sp);
if (result != null) {
return result;
} else {
return getNewWithDefaults();
}
}
protected Predicate getPredicate(CriteriaQuery> criteriaQuery, Root root, CriteriaBuilder builder, E entity, SearchParameters sp) {
return jpaUtil.andPredicate(builder, //
bySearchPredicate(root, builder, entity, sp), //
byMandatoryPredicate(criteriaQuery, root, builder, entity, sp));
}
protected Predicate bySearchPredicate(Root root, CriteriaBuilder builder, E entity, SearchParameters sp) {
return jpaUtil.concatPredicate(sp, builder, //
byFullText(root, builder, sp, entity, indexedAttributes), //
byRanges(root, builder, sp, type), //
byPropertySelectors(root, builder, sp), //
byExample(root, builder, sp, entity), //
byPattern(root, builder, sp, type));
}
protected > Predicate byFullText(Root root, CriteriaBuilder builder, SearchParameters sp, T entity,
List> indexedAttributes) {
return byFullTextUtil.byFullText(root, builder, sp, entity, indexedAttributes);
}
protected Predicate byExample(Root root, CriteriaBuilder builder, SearchParameters sp, E entity) {
return byExampleUtil.byExampleOnEntity(root, entity, builder, sp);
}
protected Predicate byPropertySelectors(Root root, CriteriaBuilder builder, SearchParameters sp) {
return byPropertySelectorUtil.byPropertySelectors(root, builder, sp);
}
protected Predicate byRanges(Root root, CriteriaBuilder builder, SearchParameters sp, Class type) {
return byRangeUtil.byRanges(root, builder, sp, type);
}
protected Predicate byPattern(Root root, CriteriaBuilder builder, SearchParameters sp, Class type) {
return byPatternUtil.byPattern(root, builder, sp, type);
}
/*
* You may override this method to add a Predicate to the default find method.
*/
protected Predicate byMandatoryPredicate(CriteriaQuery> criteriaQuery, Root root, CriteriaBuilder builder, E entity, SearchParameters sp) {
return null;
}
/**
* Save or update the given entity E to the repository. Assume that the entity is already present in the persistence context. No merge is done.
*
* @param entity the entity to be saved or updated.
*/
@Transactional
public void save(E entity) {
checkNotNull(entity, "The entity to save cannot be null");
// creation with auto generated id
if (!entity.isIdSet()) {
entityManager.persist(entity);
return;
}
// creation with manually assigned key
if (jpaUtil.isEntityIdManuallyAssigned(type) && !entityManager.contains(entity)) {
entityManager.persist(entity);
return;
}
// other cases are update
// the simple fact to invoke this method, from a service method annotated with @Transactional,
// does the job (assuming the give entity is present in the persistence context)
}
/*
* Persist the given entity.
*/
@Transactional
public void persist(E entity) {
entityManager.persist(entity);
}
/*
* Merge the state of the given entity into the current persistence context.
*/
@Transactional
public E merge(E entity) {
return entityManager.merge(entity);
}
/*
* Delete the given entity E from the repository.
*
* @param entity the entity to be deleted.
*/
@Transactional
public void delete(E entity) {
if (entityManager.contains(entity)) {
entityManager.remove(entity);
} else {
// could be a delete on a transient instance
E entityRef = entityManager.getReference(type, entity.getId());
if (entityRef != null) {
entityManager.remove(entityRef);
} else {
log.warn("Attempt to delete an instance that is not present in the database: {}", entity);
}
}
}
protected List> buildIndexedAttributes(Class type) {
List> ret = newArrayList();
for (Method m : type.getMethods()) {
if (m.getAnnotation(Field.class) != null) {
ret.add(metamodelUtil.toAttribute(jpaUtil.methodToProperty(m), type));
}
}
return ret;
}
public boolean isIndexed(String property) {
return !property.contains(".") && indexedAttributes.contains(metamodelUtil.toAttribute(property, type));
}
// -----------------
// Util
// -----------------
/*
* Helper to determine if the passed given property is null. Used mainly on binary lazy loaded property.
*
* @param id the entity id
* @param property the property to check
*/
@Transactional(readOnly = true)
public boolean isPropertyNull(PK id, SingularAttribute property) {
checkNotNull(id, "The id cannot be null");
checkNotNull(property, "The property cannot be null");
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery criteriaQuery = builder.createQuery(Long.class);
Root root = criteriaQuery.from(type);
criteriaQuery = criteriaQuery.select(builder.count(root));
// predicate
Predicate idPredicate = builder.equal(root.get("id"), id);
Predicate isNullPredicate = builder.isNull(root.get(property));
criteriaQuery = criteriaQuery.where(jpaUtil.andPredicate(builder, idPredicate, isNullPredicate));
TypedQuery typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.getSingleResult().intValue() == 1;
}
/*
* Return the optimistic version value, if any.
*/
@SuppressWarnings("unchecked")
@Transactional(readOnly = true)
public Comparable