Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
io.micronaut.data.hibernate.operations.HibernateJpaOperations Maven / Gradle / Ivy
/*
* Copyright 2017-2020 original authors
*
* 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
*
* https://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 io.micronaut.data.hibernate.operations;
import io.micronaut.aop.InvocationContext;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.annotation.EachBean;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.convert.ConversionService;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.data.annotation.QueryHint;
import io.micronaut.data.annotation.sql.Procedure;
import io.micronaut.data.hibernate.conf.RequiresSyncHibernate;
import io.micronaut.data.jpa.annotation.EntityGraph;
import io.micronaut.data.jpa.operations.JpaRepositoryOperations;
import io.micronaut.data.model.Page;
import io.micronaut.data.model.Pageable;
import io.micronaut.data.model.runtime.BatchOperation;
import io.micronaut.data.model.runtime.DeleteBatchOperation;
import io.micronaut.data.model.runtime.DeleteOperation;
import io.micronaut.data.model.runtime.EntityInstanceOperation;
import io.micronaut.data.model.runtime.InsertBatchOperation;
import io.micronaut.data.model.runtime.InsertOperation;
import io.micronaut.data.model.runtime.PagedQuery;
import io.micronaut.data.model.runtime.PreparedQuery;
import io.micronaut.data.model.runtime.QueryParameterBinding;
import io.micronaut.data.model.runtime.RuntimeEntityRegistry;
import io.micronaut.data.model.runtime.RuntimePersistentEntity;
import io.micronaut.data.model.runtime.StoredQuery;
import io.micronaut.data.model.runtime.UpdateBatchOperation;
import io.micronaut.data.model.runtime.UpdateOperation;
import io.micronaut.data.operations.CriteriaRepositoryOperations;
import io.micronaut.data.operations.async.AsyncCapableRepository;
import io.micronaut.data.operations.reactive.ReactiveCapableRepository;
import io.micronaut.data.operations.reactive.ReactiveRepositoryOperations;
import io.micronaut.data.runtime.convert.DataConversionService;
import io.micronaut.data.runtime.operations.ExecutorAsyncOperations;
import io.micronaut.data.runtime.operations.ExecutorAsyncOperationsSupportingCriteria;
import io.micronaut.data.runtime.operations.ExecutorReactiveOperationsSupportingCriteria;
import io.micronaut.transaction.TransactionOperations;
import jakarta.inject.Named;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.ParameterMode;
import jakarta.persistence.Tuple;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaDelete;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.CriteriaUpdate;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.graph.RootGraph;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.query.CommonQueryContract;
import org.hibernate.query.MutationQuery;
import org.hibernate.query.Query;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* Implementation of the {@link JpaRepositoryOperations} interface for Hibernate.
*
* @author graemerocher
* @since 1.0
*/
@RequiresSyncHibernate
@EachBean(DataSource.class)
final class HibernateJpaOperations extends AbstractHibernateOperations>
implements JpaRepositoryOperations, AsyncCapableRepository, ReactiveCapableRepository, CriteriaRepositoryOperations {
private final SessionFactory sessionFactory;
private final TransactionOperations transactionOperations;
private ExecutorAsyncOperations asyncOperations;
private ExecutorService executorService;
/**
* Default constructor.
*
* @param sessionFactory The session factory
* @param transactionOperations The transaction operations
* @param executorService The executor service for I/O tasks to use
* @param runtimeEntityRegistry The runtime entity registry
* @param dataConversionService The data conversion service
*/
public HibernateJpaOperations(
@NonNull @Parameter SessionFactory sessionFactory,
@NonNull @Parameter TransactionOperations transactionOperations,
@Named("io") @Nullable ExecutorService executorService,
RuntimeEntityRegistry runtimeEntityRegistry,
DataConversionService dataConversionService) {
super(runtimeEntityRegistry, dataConversionService);
ArgumentUtils.requireNonNull("sessionFactory", sessionFactory);
this.sessionFactory = sessionFactory;
this.transactionOperations = transactionOperations;
this.executorService = executorService;
}
@Override
public RuntimePersistentEntity getEntity(Class type) {
return runtimeEntityRegistry.getEntity(type);
}
@Override
public ApplicationContext getApplicationContext() {
return super.getApplicationContext();
}
@Override
public ConversionService getConversionService() {
return super.getConversionService();
}
@Override
protected void setParameter(CommonQueryContract query, String parameterName, Object value) {
query.setParameter(parameterName, value);
}
@Override
protected void setParameter(CommonQueryContract query, String parameterName, Object value, Argument> argument) {
// How to provide type, if needed at all? Was needed prior to Hibernate 6
query.setParameter(parameterName, value);
}
@Override
protected void setParameterList(CommonQueryContract query, String parameterName, Collection value) {
if (value == null) {
value = Collections.emptyList();
}
// Passing collection as param like this as well, before Hibernate 6 there was other method to pass collection
query.setParameterList(parameterName, value);
}
@Override
protected void setParameterList(CommonQueryContract query, String parameterName, Collection value, Argument> argument) {
if (value == null) {
value = Collections.emptyList();
}
// Can we ignore type? Was needed before Hibernate 6
query.setParameterList(parameterName, value);
}
@Override
protected void setParameter(CommonQueryContract query, int parameterIndex, Object value) {
query.setParameter(parameterIndex, value);
}
@Override
protected void setParameter(CommonQueryContract query, int parameterIndex, Object value, Argument> argument) {
query.setParameter(parameterIndex, value);
}
@Override
protected void setParameterList(CommonQueryContract query, int parameterIndex, Collection value) {
if (value == null) {
value = Collections.emptyList();
}
query.setParameterList(parameterIndex, value);
}
@Override
protected void setParameterList(CommonQueryContract query, int parameterIndex, Collection value, Argument> argument) {
if (value == null) {
value = Collections.emptyList();
}
// Can we ignore type? Was needed before Hibernate 6
query.setParameterList(parameterIndex, value);
}
@Override
protected void setHint(Query> query, String hintName, Object value) {
query.setHint(hintName, value);
}
@Override
protected RootGraph getEntityGraph(Session session, Class entityType, String graphName) {
return (RootGraph) session.getEntityGraph(graphName);
}
@Override
protected RootGraph createEntityGraph(Session session, Class entityType) {
return session.createEntityGraph(entityType);
}
@Override
protected Query> createQuery(Session session, String query, Class> resultType) {
return session.createQuery(query, resultType);
}
@Override
protected Query> createNativeQuery(Session session, String query, Class> resultType) {
return session.createNativeQuery(query, resultType);
}
@Override
protected Query> createQuery(Session session, CriteriaQuery> criteriaQuery) {
return session.createQuery(criteriaQuery);
}
@Override
protected void setOffset(Query> query, int offset) {
query.setFirstResult(offset);
}
@Override
protected void setMaxResults(Query> query, int max) {
query.setMaxResults(max);
}
@NonNull
@Override
public Map getQueryHints(@NonNull StoredQuery, ?> storedQuery) {
return super.getQueryHints(storedQuery);
}
@Nullable
@Override
public T findOne(@NonNull Class type, @NonNull Object id) {
return executeRead(session -> session.byId(type).load(id));
}
@NonNull
@Override
public T load(@NonNull Class type, @NonNull Object id) {
return executeRead(session -> session.getReference(type, id));
}
@Override
public T merge(T entity) {
return executeWrite(session -> session.merge(entity));
}
@Nullable
@Override
public R findOne(@NonNull PreparedQuery preparedQuery) {
return executeRead(session -> {
// limit does not work with native queries and does not produce expected
// results with EntityGraph annotation and joins
boolean limitOne = !preparedQuery.isNative() && !hasEntityGraph(preparedQuery.getAnnotationMetadata());
FirstResultCollector collector = new FirstResultCollector<>(limitOne);
collectFindOne(session, preparedQuery, collector);
return collector.result;
});
}
@Override
public boolean exists(@NonNull PreparedQuery preparedQuery) {
return findOne(preparedQuery) != null;
}
@NonNull
@Override
public Iterable findAll(@NonNull PagedQuery pagedQuery) {
return executeRead(session -> findPaged(session, pagedQuery));
}
@NonNull
@Override
public Stream findStream(@NonNull PagedQuery pagedQuery) {
return executeRead(session -> {
StreamResultCollector collector = new StreamResultCollector<>();
collectPagedResults(sessionFactory.getCriteriaBuilder(), session, pagedQuery, collector);
return collector.result;
});
}
@Override
public Page findPage(@NonNull PagedQuery pagedQuery) {
return executeRead(session -> Page.of(
findPaged(session, pagedQuery),
pagedQuery.getPageable(),
countOf(session, pagedQuery, pagedQuery.getPageable())
));
}
@Override
public long count(PagedQuery pagedQuery) {
return executeRead(session -> countOf(session, pagedQuery, null));
}
private List findPaged(Session session, PagedQuery pagedQuery) {
ListResultCollector collector = new ListResultCollector<>();
collectPagedResults(sessionFactory.getCriteriaBuilder(), session, pagedQuery, collector);
return collector.result;
}
private Long countOf(Session session, PagedQuery pagedQuery, @Nullable Pageable pageable) {
SingleResultCollector collector = new SingleResultCollector<>();
collectCountOf(sessionFactory.getCriteriaBuilder(), session, pagedQuery.getRootEntity(), pageable, collector);
return collector.result;
}
@NonNull
@Override
public Iterable findAll(@NonNull PreparedQuery preparedQuery) {
return executeRead(session -> {
ListResultCollector resultCollector = new ListResultCollector<>();
collectFindAll(session, preparedQuery, resultCollector);
return resultCollector.result;
});
}
@Override
public T persist(@NonNull InsertOperation operation) {
StoredQuery storedQuery = operation.getStoredQuery();
return executeWrite(session -> {
if (storedQuery != null) {
return executeUpdate(operation, session, storedQuery);
}
T entity = operation.getEntity();
session.persist(entity);
flushIfNecessary(session, operation.getAnnotationMetadata());
return entity;
});
}
@NonNull
@Override
public T update(@NonNull UpdateOperation operation) {
StoredQuery storedQuery = operation.getStoredQuery();
return executeWrite(session -> {
if (storedQuery != null) {
return executeUpdate(operation, session, storedQuery);
}
T entity = operation.getEntity();
entity = session.merge(entity);
flushIfNecessary(session, operation.getAnnotationMetadata());
return entity;
});
}
private T executeUpdate(EntityInstanceOperation operation, Session session, StoredQuery storedQuery) {
executeUpdate(session, storedQuery, operation.getInvocationContext(), operation.getEntity());
if (flushIfNecessary(session, operation.getAnnotationMetadata())) {
session.remove(operation.getEntity());
}
return operation.getEntity();
}
@NonNull
@Override
public Iterable updateAll(@NonNull UpdateBatchOperation operation) {
StoredQuery storedQuery = operation.getStoredQuery();
return executeWrite(session -> {
if (storedQuery != null) {
return executeUpdate(operation, session, storedQuery);
}
List results = new ArrayList<>();
for (T entity : operation) {
T merge = session.merge(entity);
results.add(merge);
}
flushIfNecessary(session, operation.getAnnotationMetadata());
return results;
});
}
private BatchOperation executeUpdate(BatchOperation operation, Session session, StoredQuery storedQuery) {
for (T entity : operation) {
executeUpdate(session, storedQuery, operation.getInvocationContext(), entity);
}
if (flushIfNecessary(session, operation.getAnnotationMetadata())) {
for (T entity : operation) {
session.remove(entity);
}
}
return operation;
}
@NonNull
@Override
public Iterable persistAll(@NonNull InsertBatchOperation operation) {
StoredQuery storedQuery = operation.getStoredQuery();
return executeWrite(session -> {
if (storedQuery != null) {
return executeUpdate(operation, session, storedQuery);
}
for (T entity : operation) {
session.persist(entity);
}
flushIfNecessary(session, operation.getAnnotationMetadata());
return operation;
});
}
private boolean flushIfNecessary(EntityManager entityManager, AnnotationMetadata annotationMetadata) {
return flushIfNecessary(entityManager, annotationMetadata, false);
}
private boolean flushIfNecessary(EntityManager entityManager, AnnotationMetadata annotationMetadata, boolean clear) {
if (annotationMetadata.hasAnnotation(QueryHint.class)) {
FlushModeType flushModeType = getFlushModeType(annotationMetadata);
if (flushModeType == FlushModeType.AUTO) {
entityManager.flush();
if (clear) {
entityManager.clear();
}
return true;
}
}
return false;
}
@NonNull
@Override
public Optional executeUpdate(@NonNull PreparedQuery, Number> preparedQuery) {
return executeWrite(session -> {
String query = preparedQuery.getQuery();
MutationQuery q = preparedQuery.isNative() ? session.createNativeMutationQuery(query) : session.createMutationQuery(query);
bindParameters(q, preparedQuery, true);
int numAffected = q.executeUpdate();
flushIfNecessary(session, preparedQuery.getAnnotationMetadata(), true);
return Optional.of(numAffected);
});
}
@Override
public List execute(PreparedQuery, R> preparedQuery) {
return executeWrite(session -> {
boolean needsOutRegistered = false;
if (preparedQuery.isProcedure()) {
Optional named = preparedQuery.getAnnotationMetadata().stringValue(Procedure.class, "named");
ProcedureCall procedureQuery;
if (named.isPresent()) {
procedureQuery = session.createNamedStoredProcedureQuery(named.get());
} else {
String procedureName = preparedQuery.getAnnotationMetadata().stringValue(Procedure.class).orElseGet(preparedQuery::getName);
if (preparedQuery.getResultArgument().isVoid()) {
procedureQuery = session.createStoredProcedureQuery(procedureName);
} else {
procedureQuery = session.createStoredProcedureQuery(
procedureName,
preparedQuery.getResultArgument().getType()
);
needsOutRegistered = true;
}
int index = 1;
for (QueryParameterBinding queryBinding : preparedQuery.getQueryBindings()) {
int parameterIndex = queryBinding.getParameterIndex();
Argument> argument = preparedQuery.getArguments()[parameterIndex];
procedureQuery.registerStoredProcedureParameter(
index++,
argument.getType(),
ParameterMode.IN);
}
if (needsOutRegistered) {
procedureQuery.registerStoredProcedureParameter(
index,
preparedQuery.getResultArgument().getType(),
ParameterMode.OUT);
}
}
boolean bindNamed = procedureQuery.getRegisteredParameters().stream().anyMatch(p -> p.getName() != null);
bindParameters(procedureQuery, preparedQuery, bindNamed);
procedureQuery.execute();
if (preparedQuery.getResultArgument().isVoid()) {
flushIfNecessary(session, preparedQuery.getAnnotationMetadata(), true);
return List.of();
}
jakarta.persistence.Parameter procedureParameter = procedureQuery.getRegisteredParameters().stream().filter(p -> p.getMode() == ParameterMode.OUT)
.findFirst()
.orElseThrow(() -> new IllegalStateException("Cannot determine the output parameter!"));
Object result;
if (bindNamed) {
result = procedureQuery.getOutputParameterValue(procedureParameter.getName());
} else {
result = procedureQuery.getOutputParameterValue(preparedQuery.getQueryBindings().size() + 1);
}
return List.of((R) result);
} else {
if (preparedQuery.isNative()) {
Iterable> result = findAll(preparedQuery);
return (List) result;
}
throw new IllegalStateException("Only native query supports update RETURNING operations.");
}
});
}
@Override
public int delete(@NonNull DeleteOperation operation) {
StoredQuery storedQuery = operation.getStoredQuery();
return executeWrite(session -> {
if (storedQuery != null) {
int numAffected = executeUpdate(session, storedQuery, operation.getInvocationContext(), operation.getEntity());
if (flushIfNecessary(session, operation.getAnnotationMetadata())) {
session.remove(operation.getEntity());
}
return numAffected;
}
session.remove(operation.getEntity());
return 1;
});
}
@Override
public Optional deleteAll(@NonNull DeleteBatchOperation operation) {
StoredQuery storedQuery = operation.getStoredQuery();
Integer result = executeWrite(session -> {
if (storedQuery != null) {
int i = 0;
for (T entity : operation) {
i += executeUpdate(session, storedQuery, operation.getInvocationContext(), entity);
}
if (flushIfNecessary(session, operation.getAnnotationMetadata())) {
for (T entity : operation) {
session.remove(entity);
}
}
return i;
}
int i = 0;
for (T entity : operation) {
session.remove(entity);
i++;
}
return i;
});
return Optional.ofNullable(result);
}
private int executeUpdate(Session session, StoredQuery storedQuery, InvocationContext, ?> invocationContext, T entity) {
MutationQuery query = session.createMutationQuery(storedQuery.getQuery());
bindParameters(query, storedQuery, invocationContext, true, entity);
return query.executeUpdate();
}
@NonNull
@Override
public Stream findStream(@NonNull PreparedQuery preparedQuery) {
return executeRead(session -> {
StreamResultCollector resultCollector = new StreamResultCollector<>();
collectFindAll(session, preparedQuery, resultCollector);
return resultCollector.result;
});
}
private R executeRead(Function callback) {
return transactionOperations.executeRead(status -> callback.apply(getCurrentSession()));
}
private R executeWrite(Function callback) {
return transactionOperations.executeWrite(status -> callback.apply(getCurrentSession()));
}
private Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
@NonNull
private ExecutorService newLocalThreadPool() {
this.executorService = Executors.newCachedThreadPool();
return executorService;
}
@NonNull
@Override
public ExecutorAsyncOperations async() {
ExecutorAsyncOperations executorAsyncOperations = this.asyncOperations;
if (executorAsyncOperations == null) {
synchronized (this) { // double check
executorAsyncOperations = this.asyncOperations;
if (executorAsyncOperations == null) {
executorAsyncOperations = new ExecutorAsyncOperationsSupportingCriteria(
this,
this,
executorService != null ? executorService : newLocalThreadPool()
);
this.asyncOperations = executorAsyncOperations;
}
}
}
return executorAsyncOperations;
}
@NonNull
@Override
public ReactiveRepositoryOperations reactive() {
if (dataConversionService instanceof DataConversionService asDataConversionService) {
return new ExecutorReactiveOperationsSupportingCriteria((ExecutorAsyncOperationsSupportingCriteria) async(), asDataConversionService);
}
return new ExecutorReactiveOperationsSupportingCriteria((ExecutorAsyncOperationsSupportingCriteria) async(), null);
}
@NonNull
@Override
public EntityManager getCurrentEntityManager() {
return sessionFactory.getCurrentSession();
}
@NonNull
@Override
public EntityManagerFactory getEntityManagerFactory() {
return this.sessionFactory;
}
@Override
public void flush() {
executeWrite(session -> {
session.flush();
return null;
}
);
}
private boolean hasEntityGraph(AnnotationMetadata annotationMetadata) {
return annotationMetadata.hasAnnotation(EntityGraph.class);
}
@Override
public CriteriaBuilder getCriteriaBuilder() {
return sessionFactory.getCriteriaBuilder();
}
@Override
public R findOne(CriteriaQuery query) {
return executeRead(session -> session.createQuery(query).uniqueResult());
}
@Override
public List findAll(CriteriaQuery query) {
return executeRead(session -> session.createQuery(query).getResultList());
}
@Override
public List findAll(CriteriaQuery query, int offset, int limit) {
return executeRead(session -> {
Query sessionQuery = session.createQuery(query);
if (offset != -1) {
sessionQuery = sessionQuery.setFirstResult(offset);
}
if (limit != -1) {
sessionQuery = sessionQuery.setMaxResults(limit);
}
return sessionQuery.getResultList();
});
}
@Override
public Optional updateAll(CriteriaUpdate query) {
return Optional.ofNullable(executeWrite(session -> session.createMutationQuery(query).executeUpdate()));
}
@Override
public Optional deleteAll(CriteriaDelete query) {
return Optional.ofNullable(executeWrite(session -> session.createMutationQuery(query).executeUpdate()));
}
private final class ListResultCollector extends ResultCollector {
private List result;
@Override
protected void collectTuple(Query> query, Function fn) {
result = ((List) query.getResultList()).stream().map(fn).toList();
}
@Override
protected void collect(Query> query) {
result = (List) query.getResultList();
}
}
private final class StreamResultCollector extends ResultCollector {
private Stream result;
@Override
protected void collectTuple(Query> query, Function fn) {
result = ((Stream) query.getResultStream()).map(fn);
}
@Override
protected void collect(Query> query) {
result = (Stream) query.getResultStream();
}
}
private final class SingleResultCollector extends ResultCollector {
private R result;
@Override
protected void collectTuple(Query> query, Function fn) {
Tuple tuple = (Tuple) query.getSingleResult();
if (tuple != null) {
this.result = fn.apply(tuple);
}
}
@Override
protected void collect(Query> query) {
result = (R) query.getSingleResult();
}
}
private final class FirstResultCollector extends ResultCollector {
private final boolean limitOne;
private R result;
private FirstResultCollector(boolean limitOne) {
this.limitOne = limitOne;
}
@Override
protected void collectTuple(Query> query, Function fn) {
Tuple tuple = getFirst(query);
if (tuple != null) {
this.result = fn.apply(tuple);
}
}
@Override
protected void collect(Query> query) {
result = getFirst(query);
}
private T getFirst(Query> q) {
if (limitOne) {
q.setMaxResults(1);
}
Iterator iterator = (Iterator) q.getResultList().iterator();
if (iterator.hasNext()) {
return iterator.next();
}
return null;
}
}
}