org.springframework.integration.jpa.core.JpaExecutor Maven / Gradle / Ivy
Show all versions of spring-integration-jpa Show documentation
/*
* Copyright 2002-2015 the original author or 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
*
* 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 org.springframework.integration.jpa.core;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Query;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.expression.ExpressionUtils;
import org.springframework.integration.jpa.support.JpaParameter;
import org.springframework.integration.jpa.support.PersistMode;
import org.springframework.integration.jpa.support.parametersource.BeanPropertyParameterSourceFactory;
import org.springframework.integration.jpa.support.parametersource.ExpressionEvaluatingParameterSourceFactory;
import org.springframework.integration.jpa.support.parametersource.ParameterSource;
import org.springframework.integration.jpa.support.parametersource.ParameterSourceFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.util.Assert;
/**
* Executes Jpa Operations that produce payload objects from the result of the provided:
*
*
* - entityClass
* - JpQl Select Query
* - Sql Native Query
* - JpQl Named Query
* - Sql Native Named Query
*
*
* When objects are being retrieved, it also possibly to:
*
*
* - delete the retrieved object
*
*
* If neither entityClass nor any other query is specified then the entity-class
* is "guessed" from the {@link Message} payload.
*
* @author Gunnar Hillert
* @author Amol Nayak
* @author Artem Bilan
* @since 2.2
*
*/
public class JpaExecutor implements InitializingBean, BeanFactoryAware {
private final JpaOperations jpaOperations;
private volatile List jpaParameters;
private volatile Class entityClass;
private volatile String jpaQuery;
private volatile String nativeQuery;
private volatile String namedQuery;
private volatile Expression maxResultsExpression;
private volatile Expression firstResultExpression;
private volatile Expression idExpression;
private volatile PersistMode persistMode = PersistMode.MERGE;
private volatile ParameterSourceFactory parameterSourceFactory = null;
private volatile ParameterSource parameterSource;
private volatile boolean flush = false;
private volatile int flushSize = 0;
private volatile boolean clearOnFlush = false;
private volatile boolean deleteAfterPoll = false;
private volatile boolean deleteInBatch = false;
private volatile boolean expectSingleResult = false;
/**
* Indicates that whether only the payload of the passed in {@link Message}
* will be used as a source of parameters. The is 'true' by default because as a
* default a {@link BeanPropertyParameterSourceFactory} implementation is
* used for the sqlParameterSourceFactory property.
*/
private volatile Boolean usePayloadAsParameterSource = null;
private volatile BeanFactory beanFactory;
private volatile EvaluationContext evaluationContext;
/**
* Constructor taking an {@link EntityManagerFactory} from which the
* {@link EntityManager} can be obtained.
* @param entityManagerFactory Must not be null.
*/
public JpaExecutor(EntityManagerFactory entityManagerFactory) {
Assert.notNull(entityManagerFactory, "entityManagerFactory must not be null.");
DefaultJpaOperations defaultJpaOperations = new DefaultJpaOperations();
defaultJpaOperations.setEntityManagerFactory(entityManagerFactory);
defaultJpaOperations.afterPropertiesSet();
this.jpaOperations = defaultJpaOperations;
}
/**
* Constructor taking an {@link EntityManager} directly.
* @param entityManager Must not be null.
*/
public JpaExecutor(EntityManager entityManager) {
Assert.notNull(entityManager, "entityManager must not be null.");
DefaultJpaOperations defaultJpaOperations = new DefaultJpaOperations();
defaultJpaOperations.setEntityManager(entityManager);
defaultJpaOperations.afterPropertiesSet();
this.jpaOperations = defaultJpaOperations;
}
/**
* If custom behavior is required a custom implementation of {@link JpaOperations}
* can be passed in. The implementations themselves typically provide access
* to the {@link EntityManager}.
* See also {@link DefaultJpaOperations} and {@link AbstractJpaOperations}.
* @param jpaOperations Must not be null.
*/
public JpaExecutor(JpaOperations jpaOperations) {
Assert.notNull(jpaOperations, "jpaOperations must not be null.");
this.jpaOperations = jpaOperations;
}
public void setIntegrationEvaluationContext(EvaluationContext evaluationContext) {
this.evaluationContext = evaluationContext;
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
/**
* Verify and sets the parameters. E.g. initializes the to be used
* {@link ParameterSourceFactory}.
*/
@Override
public void afterPropertiesSet() {
if (this.jpaParameters != null) {
if (this.parameterSourceFactory == null) {
ExpressionEvaluatingParameterSourceFactory expressionSourceFactory =
new ExpressionEvaluatingParameterSourceFactory(this.beanFactory);
expressionSourceFactory.setParameters(this.jpaParameters);
this.parameterSourceFactory = expressionSourceFactory;
}
else {
if (!(this.parameterSourceFactory instanceof ExpressionEvaluatingParameterSourceFactory)) {
throw new IllegalStateException("You are providing 'JpaParameters'. "
+ "Was expecting the the provided jpaParameterSourceFactory "
+ "to be an instance of 'ExpressionEvaluatingJpaParameterSourceFactory', "
+ "however the provided one is of type '" + this.parameterSourceFactory.getClass().getName() + "'");
}
}
if (this.usePayloadAsParameterSource == null) {
this.usePayloadAsParameterSource = false;
}
}
else {
if (this.parameterSourceFactory == null) {
this.parameterSourceFactory = new BeanPropertyParameterSourceFactory();
}
if (this.usePayloadAsParameterSource == null) {
this.usePayloadAsParameterSource = true;
}
}
if (this.flushSize > 0) {
this.flush = true;
}
else if (this.flush) {
this.flushSize = 1;
}
if (this.evaluationContext == null) {
this.evaluationContext = ExpressionUtils.createStandardEvaluationContext(this.beanFactory);
}
}
/**
* Execute the actual Jpa Operation. Call this method, if you need access to
* process return values. This methods return a Map that contains either
* the number of affected entities or the affected entity itself.
*Keep in mind that the number of entities effected by the operation may
* not necessarily correlate with the number of rows effected in the database.
* @param message The message.
* @return Either the number of affected entities when using a JPQL query.
* When using a merge/persist the updated/inserted itself is returned.
*/
public Object executeOutboundJpaOperation(final Message message) {
final Object result;
ParameterSource parameterSource = null;
if (this.jpaQuery != null || this.nativeQuery != null || this.namedQuery != null) {
parameterSource = this.determineParameterSource(message);
}
if (this.jpaQuery != null) {
result = this.jpaOperations.executeUpdate(this.jpaQuery, parameterSource);
}
else if (this.nativeQuery != null) {
result = this.jpaOperations.executeUpdateWithNativeQuery(this.nativeQuery, parameterSource);
}
else if (this.namedQuery != null) {
result = this.jpaOperations.executeUpdateWithNamedQuery(this.namedQuery, parameterSource);
}
else {
if (PersistMode.PERSIST.equals(this.persistMode)) {
this.jpaOperations.persist(message.getPayload(), this.flushSize, this.clearOnFlush);
result = message.getPayload();
}
else if (PersistMode.MERGE.equals(this.persistMode)) {
result = this.jpaOperations.merge(message.getPayload(), this.flushSize, this.clearOnFlush);
}
else if (PersistMode.DELETE.equals(this.persistMode)) {
this.jpaOperations.delete(message.getPayload());
if (this.flush) {
this.jpaOperations.flush();
}
result = message.getPayload();
}
else {
throw new IllegalStateException(String.format("Unsupported PersistMode: '%s'", this.persistMode.name()));
}
}
return result;
}
/**
* Execute a (typically retrieving) JPA operation. The requestMessage
* can be used to provide additional query parameters using
* {@link JpaExecutor#parameterSourceFactory}. If the
* requestMessage parameter is null then
* {@link JpaExecutor#parameterSource} is being used for providing query parameters.
* @param requestMessage May be null.
* @return The payload object, which may be null.
*/
@SuppressWarnings("unchecked")
public Object poll(final Message requestMessage) {
final Object payload;
if (this.idExpression != null) {
Object id = this.idExpression.getValue(this.evaluationContext, requestMessage);
Class entityClass = this.entityClass;
if (entityClass == null) {
entityClass = requestMessage.getPayload().getClass();
}
payload = this.jpaOperations.find(entityClass, id);
}
else {
final List result;
int maxNumberOfResults = this.evaluateExpressionForNumericResult(requestMessage, this.maxResultsExpression);
if (requestMessage == null) {
result = this.doPoll(this.parameterSource, 0, maxNumberOfResults);
}
else {
int firstResult = 0;
if (firstResultExpression != null) {
firstResult = this.getFirstResult(requestMessage);
}
ParameterSource parameterSource = this.determineParameterSource(requestMessage);
result = this.doPoll(parameterSource, firstResult, maxNumberOfResults);
}
if (result.isEmpty()) {
payload = null;
}
else {
if (this.expectSingleResult) {
if (result.size() == 1) {
payload = result.iterator().next();
}
else {
throw new MessagingException(requestMessage,
"The Jpa operation returned more than 1 result object but expectSingleResult was 'true'.");
}
}
else {
payload = result;
}
}
}
if (payload != null && this.deleteAfterPoll) {
if (payload instanceof Iterable) {
if (this.deleteInBatch) {
this.jpaOperations.deleteInBatch((Iterable