Fix ambiguity in the JpaExecutor (#2640)
* Fix ambiguity in the `JpaExecutor` Since we can't mutate a provided `ParameterSourceFactory` with the provided `jpaParameters`, we have to reject such a combined configuration in favor of the advice to configure params on the provided `ParameterSourceFactory` * Throw `IllegalStateException` when `ParameterSourceFactory` and `jpaParameters` are configured together on the `JpaExecutor` * Some refactoring for `JpaExecutor` to have a consistent code style * Fix tests according new logic * Document such an ambiguity configuration to avoid confusion **Cherry-pick to 5.0.x** * * Use `assertThatThrownBy()` in the `JpaExecutorTests` * Fix "No BeanFactory" warning in the `JpaExecutorTests` * * Resolve potential NPE and add `@NonNullApi` with `@Nullable` on the `poll()`
This commit is contained in:
committed by
Gary Russell
parent
f6e9e6649a
commit
d981171972
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -36,6 +36,7 @@ import org.springframework.integration.jpa.support.parametersource.BeanPropertyP
|
||||
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.lang.Nullable;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -59,11 +60,12 @@ import org.springframework.util.CollectionUtils;
|
||||
* </ul>
|
||||
*
|
||||
* If neither entityClass nor any other query is specified then the entity-class
|
||||
* is "guessed" from the {@link Message} payload.
|
||||
* is "guessed" from the {@link Message} payload.idExpression
|
||||
*
|
||||
* @author Gunnar Hillert
|
||||
* @author Amol Nayak
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.2
|
||||
*
|
||||
*/
|
||||
@@ -71,39 +73,39 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
|
||||
|
||||
private final JpaOperations jpaOperations;
|
||||
|
||||
private volatile List<JpaParameter> jpaParameters;
|
||||
private List<JpaParameter> jpaParameters;
|
||||
|
||||
private volatile Class<?> entityClass;
|
||||
private Class<?> entityClass;
|
||||
|
||||
private volatile String jpaQuery;
|
||||
private String jpaQuery;
|
||||
|
||||
private volatile String nativeQuery;
|
||||
private String nativeQuery;
|
||||
|
||||
private volatile String namedQuery;
|
||||
private String namedQuery;
|
||||
|
||||
private volatile Expression maxResultsExpression;
|
||||
private Expression maxResultsExpression;
|
||||
|
||||
private volatile Expression firstResultExpression;
|
||||
private Expression firstResultExpression;
|
||||
|
||||
private volatile Expression idExpression;
|
||||
private Expression idExpression;
|
||||
|
||||
private volatile PersistMode persistMode = PersistMode.MERGE;
|
||||
private PersistMode persistMode = PersistMode.MERGE;
|
||||
|
||||
private volatile ParameterSourceFactory parameterSourceFactory = null;
|
||||
private ParameterSourceFactory parameterSourceFactory = null;
|
||||
|
||||
private volatile ParameterSource parameterSource;
|
||||
private ParameterSource parameterSource;
|
||||
|
||||
private volatile boolean flush = false;
|
||||
private boolean flush = false;
|
||||
|
||||
private volatile int flushSize = 0;
|
||||
private int flushSize = 0;
|
||||
|
||||
private volatile boolean clearOnFlush = false;
|
||||
private boolean clearOnFlush = false;
|
||||
|
||||
private volatile boolean deleteAfterPoll = false;
|
||||
private boolean deleteAfterPoll = false;
|
||||
|
||||
private volatile boolean deleteInBatch = false;
|
||||
private boolean deleteInBatch = false;
|
||||
|
||||
private volatile boolean expectSingleResult = false;
|
||||
private boolean expectSingleResult = false;
|
||||
|
||||
/**
|
||||
* Indicates that whether only the payload of the passed in {@link Message}
|
||||
@@ -111,11 +113,11 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
|
||||
* default a {@link BeanPropertyParameterSourceFactory} implementation is
|
||||
* used for the sqlParameterSourceFactory property.
|
||||
*/
|
||||
private volatile Boolean usePayloadAsParameterSource = null;
|
||||
private Boolean usePayloadAsParameterSource = null;
|
||||
|
||||
private volatile BeanFactory beanFactory;
|
||||
private BeanFactory beanFactory;
|
||||
|
||||
private volatile EvaluationContext evaluationContext;
|
||||
private EvaluationContext evaluationContext;
|
||||
|
||||
/**
|
||||
* Constructor taking an {@link EntityManagerFactory} from which the
|
||||
@@ -161,274 +163,6 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
|
||||
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 (!CollectionUtils.isEmpty(this.jpaParameters)) {
|
||||
|
||||
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.
|
||||
*<p>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 <i>requestMessage</i>
|
||||
* can be used to provide additional query parameters using
|
||||
* {@link JpaExecutor#parameterSourceFactory}. If the
|
||||
* <i>requestMessage</i> 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 (this.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<Object>) payload);
|
||||
}
|
||||
else {
|
||||
for (Object entity : (Iterable<?>) payload) {
|
||||
this.jpaOperations.delete(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.jpaOperations.delete(payload);
|
||||
}
|
||||
|
||||
if (this.flush) {
|
||||
this.jpaOperations.flush();
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
private int getFirstResult(final Message<?> requestMessage) {
|
||||
return this.evaluateExpressionForNumericResult(requestMessage, this.firstResultExpression);
|
||||
}
|
||||
|
||||
private int evaluateExpressionForNumericResult(final Message<?> requestMessage, Expression expression) {
|
||||
int evaluatedResult = 0;
|
||||
if (expression != null) {
|
||||
Object evaluationResult = expression.getValue(this.evaluationContext, requestMessage);
|
||||
if (evaluationResult != null) {
|
||||
if (evaluationResult instanceof Number) {
|
||||
evaluatedResult = ((Number) evaluationResult).intValue();
|
||||
}
|
||||
else if (evaluationResult instanceof String) {
|
||||
try {
|
||||
evaluatedResult = Integer.parseInt((String) evaluationResult);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Value " + evaluationResult + " passed as cannot be " +
|
||||
"parsed to a number, expected to be numeric");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Expected the value to be a Number" +
|
||||
" got " + evaluationResult.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
return evaluatedResult;
|
||||
}
|
||||
|
||||
private ParameterSource determineParameterSource(final Message<?> requestMessage) {
|
||||
ParameterSource parameterSource;
|
||||
if (this.usePayloadAsParameterSource) {
|
||||
parameterSource = this.parameterSourceFactory.createParameterSource(requestMessage.getPayload());
|
||||
}
|
||||
else {
|
||||
parameterSource = this.parameterSourceFactory.createParameterSource(requestMessage);
|
||||
}
|
||||
return parameterSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the JPA operation. Delegates to {@link JpaExecutor#poll(Message)}.
|
||||
* @return The object or null.
|
||||
*/
|
||||
public Object poll() {
|
||||
return this.poll(null);
|
||||
}
|
||||
|
||||
protected List<?> doPoll(ParameterSource jpaQLParameterSource, int firstResult, int maxNumberOfResults) {
|
||||
List<?> payload = null;
|
||||
if (this.jpaQuery != null) {
|
||||
payload = this.jpaOperations.getResultListForQuery(this.jpaQuery, jpaQLParameterSource,
|
||||
firstResult, maxNumberOfResults);
|
||||
}
|
||||
else if (this.nativeQuery != null) {
|
||||
payload = this.jpaOperations.getResultListForNativeQuery(this.nativeQuery, this.entityClass, jpaQLParameterSource,
|
||||
firstResult, maxNumberOfResults);
|
||||
}
|
||||
else if (this.namedQuery != null) {
|
||||
payload = this.jpaOperations.getResultListForNamedQuery(this.namedQuery, jpaQLParameterSource,
|
||||
firstResult, maxNumberOfResults);
|
||||
}
|
||||
else if (this.entityClass != null) {
|
||||
payload = this.jpaOperations.getResultListForClass(this.entityClass, firstResult, maxNumberOfResults);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("For the polling operation, one of "
|
||||
+ "the following properties must be specified: "
|
||||
+ "query, namedQuery or entityClass.");
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the class type which is being used for retrieving entities from the
|
||||
* database.
|
||||
@@ -517,7 +251,8 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
|
||||
|
||||
/**
|
||||
* If set to {@code true} the {@link javax.persistence.EntityManager#clear()} will be called,
|
||||
* and only if the {@link javax.persistence.EntityManager#flush()} was called after performing persistence operations.
|
||||
* and only if the {@link javax.persistence.EntityManager#flush()} was called after performing persistence
|
||||
* operations.
|
||||
* @param clearOnFlush defaults to 'false'.
|
||||
* @see #setFlush(boolean)
|
||||
* @see #setFlushSize(int)
|
||||
@@ -628,4 +363,270 @@ public class JpaExecutor implements InitializingBean, BeanFactoryAware {
|
||||
this.setMaxResultsExpression(new LiteralExpression("" + maxNumberOfResults));
|
||||
}
|
||||
|
||||
@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 (!CollectionUtils.isEmpty(this.jpaParameters)) {
|
||||
if (this.parameterSourceFactory == null) {
|
||||
ExpressionEvaluatingParameterSourceFactory expressionSourceFactory =
|
||||
new ExpressionEvaluatingParameterSourceFactory(this.beanFactory);
|
||||
expressionSourceFactory.setParameters(this.jpaParameters);
|
||||
this.parameterSourceFactory = expressionSourceFactory;
|
||||
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("The 'jpaParameters' and 'parameterSourceFactory' " +
|
||||
"are mutually exclusive. Consider to configure parameters on the provided " +
|
||||
"'parameterSourceFactory': " + this.parameterSourceFactory);
|
||||
}
|
||||
|
||||
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.
|
||||
*<p>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 {
|
||||
switch (this.persistMode) {
|
||||
case PERSIST:
|
||||
this.jpaOperations.persist(message.getPayload(), this.flushSize, this.clearOnFlush);
|
||||
result = message.getPayload();
|
||||
break;
|
||||
case MERGE:
|
||||
result = this.jpaOperations.merge(message.getPayload(), this.flushSize, this.clearOnFlush);
|
||||
break;
|
||||
case DELETE:
|
||||
this.jpaOperations.delete(message.getPayload());
|
||||
if (this.flush) {
|
||||
this.jpaOperations.flush();
|
||||
}
|
||||
result = message.getPayload();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported PersistMode: " + this.persistMode.name());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the JPA operation. Delegates to {@link JpaExecutor#poll(Message)}.
|
||||
* @return The object or null.
|
||||
*/
|
||||
public Object poll() {
|
||||
return poll(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a (typically retrieving) JPA operation. The <i>requestMessage</i>
|
||||
* can be used to provide additional query parameters using
|
||||
* {@link JpaExecutor#parameterSourceFactory}. If the
|
||||
* <i>requestMessage</i> 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.
|
||||
*/
|
||||
public Object poll(@Nullable final Message<?> requestMessage) {
|
||||
final Object payload;
|
||||
|
||||
if (this.idExpression != null) {
|
||||
Object id = this.idExpression.getValue(this.evaluationContext, requestMessage); // NOSONAR It can be null
|
||||
Assert.state(id != null, "The 'idExpression' cannot evaluate to null.");
|
||||
Class<?> entityClass = this.entityClass;
|
||||
if (entityClass == null && requestMessage != null) {
|
||||
entityClass = requestMessage.getPayload().getClass();
|
||||
}
|
||||
payload = this.jpaOperations.find(entityClass, id);
|
||||
}
|
||||
else {
|
||||
final List<?> result;
|
||||
int maxNumberOfResults = evaluateExpressionForNumericResult(requestMessage, this.maxResultsExpression);
|
||||
if (requestMessage == null) {
|
||||
result = doPoll(this.parameterSource, 0, maxNumberOfResults);
|
||||
}
|
||||
else {
|
||||
int firstResult = 0;
|
||||
if (this.firstResultExpression != null) {
|
||||
firstResult = getFirstResult(requestMessage);
|
||||
}
|
||||
ParameterSource parameterSource = determineParameterSource(requestMessage);
|
||||
result = doPoll(parameterSource, firstResult, maxNumberOfResults);
|
||||
}
|
||||
|
||||
if (result.isEmpty()) {
|
||||
payload = null;
|
||||
}
|
||||
else {
|
||||
if (this.expectSingleResult) {
|
||||
if (result.size() == 1) {
|
||||
payload = result.iterator().next();
|
||||
}
|
||||
else if (requestMessage != null) {
|
||||
throw new MessagingException(requestMessage,
|
||||
"The Jpa operation returned more than 1 result for expectSingleResult mode.");
|
||||
}
|
||||
else {
|
||||
throw new MessagingException(
|
||||
"The Jpa operation returned more than 1 result for expectSingleResult mode.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
payload = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (payload != null && this.deleteAfterPoll) {
|
||||
if (payload instanceof Iterable) {
|
||||
if (this.deleteInBatch) {
|
||||
this.jpaOperations.deleteInBatch((Iterable<?>) payload);
|
||||
}
|
||||
else {
|
||||
for (Object entity : (Iterable<?>) payload) {
|
||||
this.jpaOperations.delete(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.jpaOperations.delete(payload);
|
||||
}
|
||||
|
||||
if (this.flush) {
|
||||
this.jpaOperations.flush();
|
||||
}
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
protected List<?> doPoll(ParameterSource jpaQLParameterSource, int firstResult, int maxNumberOfResults) {
|
||||
List<?> payload;
|
||||
if (this.jpaQuery != null) {
|
||||
payload =
|
||||
this.jpaOperations.getResultListForQuery(this.jpaQuery, jpaQLParameterSource,
|
||||
firstResult, maxNumberOfResults);
|
||||
}
|
||||
else if (this.nativeQuery != null) {
|
||||
payload =
|
||||
this.jpaOperations.getResultListForNativeQuery(this.nativeQuery, this.entityClass,
|
||||
jpaQLParameterSource, firstResult, maxNumberOfResults);
|
||||
}
|
||||
else if (this.namedQuery != null) {
|
||||
payload =
|
||||
this.jpaOperations.getResultListForNamedQuery(this.namedQuery, jpaQLParameterSource,
|
||||
firstResult, maxNumberOfResults);
|
||||
}
|
||||
else if (this.entityClass != null) {
|
||||
payload = this.jpaOperations.getResultListForClass(this.entityClass, firstResult, maxNumberOfResults);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("For the polling operation, one of "
|
||||
+ "the following properties must be specified: "
|
||||
+ "query, namedQuery or entityClass.");
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
private int getFirstResult(final Message<?> requestMessage) {
|
||||
return evaluateExpressionForNumericResult(requestMessage, this.firstResultExpression);
|
||||
}
|
||||
|
||||
private int evaluateExpressionForNumericResult(@Nullable final Message<?> requestMessage,
|
||||
@Nullable Expression expression) {
|
||||
|
||||
int evaluatedResult = 0;
|
||||
if (expression != null) {
|
||||
Object evaluationResult = expression.getValue(this.evaluationContext, requestMessage); // NOSONAR can be null
|
||||
if (evaluationResult != null) {
|
||||
if (evaluationResult instanceof Number) {
|
||||
evaluatedResult = ((Number) evaluationResult).intValue();
|
||||
}
|
||||
else if (evaluationResult instanceof String) {
|
||||
try {
|
||||
evaluatedResult = Integer.parseInt((String) evaluationResult);
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException(
|
||||
"Value " + evaluationResult + " passed as cannot be " +
|
||||
"parsed to a number, expected to be numeric");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Expected the value to be a Number got "
|
||||
+ evaluationResult.getClass().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
return evaluatedResult;
|
||||
}
|
||||
|
||||
private ParameterSource determineParameterSource(final Message<?> requestMessage) {
|
||||
ParameterSource parameterSource;
|
||||
if (this.usePayloadAsParameterSource) {
|
||||
parameterSource = this.parameterSourceFactory.createParameterSource(requestMessage.getPayload());
|
||||
}
|
||||
else {
|
||||
parameterSource = this.parameterSourceFactory.createParameterSource(requestMessage);
|
||||
}
|
||||
return parameterSource;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/**
|
||||
* Provides core classes of the JPA module.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
package org.springframework.integration.jpa.core;
|
||||
|
||||
@@ -90,12 +90,13 @@
|
||||
<xsd:annotation>
|
||||
<xsd:documentation>
|
||||
<![CDATA[
|
||||
Provides a mechanism to provide
|
||||
Provides a mechanism to configure
|
||||
parameters for the queries that are either based
|
||||
on the Java Persistence Query Language (JPQL) or
|
||||
native SQL queries.
|
||||
|
||||
Parameters can also be provided for Named Queries.
|
||||
Mutually exclusive with the 'parameter-source-factory' attribute.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
@@ -129,12 +130,12 @@
|
||||
<xsd:union memberTypes="xsd:boolean xsd:string" />
|
||||
</xsd:simpleType>
|
||||
</xsd:attribute>
|
||||
<xsd:attribute name="parameter-source-factory"
|
||||
type="xsd:string">
|
||||
<xsd:attribute name="parameter-source-factory" type="xsd:string">
|
||||
<xsd:annotation>
|
||||
<xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
Reference to a ParameterSourceFactory.
|
||||
Mutually exclusive with the 'parameter' sub-element(s).
|
||||
</xsd:documentation>
|
||||
<tool:annotation kind="ref">
|
||||
<tool:expected-type type="org.springframework.integration.jpa.support.parametersource.ParameterSourceFactory" />
|
||||
@@ -188,8 +189,8 @@
|
||||
parameters for the queries that are either based
|
||||
on the Java Persistence Query Language (JPQL) or
|
||||
native SQL queries.
|
||||
|
||||
Parameters can also be provided for Named Queries.
|
||||
Mutually exclusive with the 'parameter-source-factory' attribute.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
@@ -222,8 +223,8 @@
|
||||
parameters for the queries that are either based
|
||||
on the Java Persistence Query Language (JPQL) or
|
||||
native SQL queries.
|
||||
|
||||
Parameters can also be provided for Named Queries.
|
||||
Mutually exclusive with the 'parameter-source-factory' attribute.
|
||||
]]>
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
@@ -395,8 +396,8 @@
|
||||
</xsd:appinfo>
|
||||
<xsd:documentation>
|
||||
The parameter source factory that would be used for evaluating the
|
||||
parameters of the response JPA QL that would be evaluated
|
||||
JPA outbound gateway
|
||||
parameters of the response JPA QL that would be evaluated JPA outbound gateway.
|
||||
Mutually exclusive with the 'parameter' sub-element(s).
|
||||
</xsd:documentation>
|
||||
</xsd:annotation>
|
||||
</xsd:attribute>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.integration.jpa.core;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
@@ -36,12 +37,12 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.jpa.support.JpaParameter;
|
||||
import org.springframework.integration.jpa.support.parametersource.ExpressionEvaluatingParameterSourceFactory;
|
||||
import org.springframework.integration.jpa.support.parametersource.ParameterSourceFactory;
|
||||
import org.springframework.integration.jpa.test.entity.StudentDomain;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
/**
|
||||
@@ -52,8 +53,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
*
|
||||
* @since 2.2
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@RunWith(SpringRunner.class)
|
||||
public class JpaExecutorTests {
|
||||
|
||||
@Autowired
|
||||
@@ -70,6 +70,7 @@ public class JpaExecutorTests {
|
||||
@Test
|
||||
public void testExecutePollWithNoEntityClassSpecified() {
|
||||
JpaExecutor jpaExecutor = new JpaExecutor(mock(EntityManager.class));
|
||||
jpaExecutor.setBeanFactory(mock(BeanFactory.class));
|
||||
jpaExecutor.afterPropertiesSet();
|
||||
try {
|
||||
jpaExecutor.poll();
|
||||
@@ -191,6 +192,7 @@ public class JpaExecutorTests {
|
||||
executor.setJpaQuery(query);
|
||||
executor.setExpectSingleResult(true);
|
||||
executor.setUsePayloadAsParameterSource(false);
|
||||
executor.setBeanFactory(mock(BeanFactory.class));
|
||||
executor.afterPropertiesSet();
|
||||
return executor;
|
||||
}
|
||||
@@ -205,6 +207,7 @@ public class JpaExecutorTests {
|
||||
executor.setJpaQuery(query);
|
||||
executor.setExpectSingleResult(true);
|
||||
executor.setUsePayloadAsParameterSource(true);
|
||||
executor.setBeanFactory(mock(BeanFactory.class));
|
||||
executor.afterPropertiesSet();
|
||||
return executor;
|
||||
}
|
||||
@@ -274,4 +277,16 @@ public class JpaExecutorTests {
|
||||
fail("Expected the test case to throw an exception");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterSourceFactoryAndJpaParameters() {
|
||||
JpaExecutor executor = new JpaExecutor(this.entityManager);
|
||||
ParameterSourceFactory parameterSourceFactory = new ExpressionEvaluatingParameterSourceFactory();
|
||||
executor.setParameterSourceFactory(parameterSourceFactory);
|
||||
executor.setJpaParameters(Collections.singletonList(new JpaParameter("firstName", null, "#this")));
|
||||
|
||||
assertThatThrownBy(executor::afterPropertiesSet)
|
||||
.isExactlyInstanceOf(IllegalStateException.class)
|
||||
.hasMessageStartingWith("The 'jpaParameters' and 'parameterSourceFactory' are mutually exclusive.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ public class JpaTests {
|
||||
|
||||
@Test
|
||||
public void testRetrievingGatewayFlow() {
|
||||
this.retrievingGatewayFlowInput.send(MessageBuilder.withPayload(1002L).build());
|
||||
this.retrievingGatewayFlowInput.send(MessageBuilder.withPayload("foo").setHeader("payloadId", 1002L).build());
|
||||
Message<?> receive = this.retrieveResults.receive(10_000);
|
||||
assertNotNull(receive);
|
||||
assertThat(receive.getPayload(), instanceOf(StudentDomain.class));
|
||||
@@ -252,7 +252,7 @@ public class JpaTests {
|
||||
.handle(Jpa.retrievingGateway(entityManagerFactory)
|
||||
.jpaQuery("from Student s where s.id = :id")
|
||||
.expectSingleResult(true)
|
||||
.parameterExpression("id", "payload"))
|
||||
.parameterExpression("id", "headers[payloadId]"))
|
||||
.channel(c -> c.queue("retrieveResults"));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2018 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.
|
||||
@@ -16,10 +16,16 @@
|
||||
|
||||
package org.springframework.integration.jpa.outbound;
|
||||
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
@@ -38,6 +44,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
* @author Gunnar Hillert
|
||||
* @author Artem Bilan
|
||||
* @author Amol Nayak
|
||||
*
|
||||
* @since 2.2
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@@ -60,21 +67,21 @@ public class JpaOutboundGatewayTests {
|
||||
@Test
|
||||
public void getStudent() {
|
||||
final StudentDomain student = studentService.getStudent(1001L);
|
||||
Assert.assertNotNull(student);
|
||||
assertNotNull(student);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAllStudentsStartingFromGivenRecord() {
|
||||
List<?> students = studentService.getAllStudentsFromGivenRecord(1);
|
||||
Assert.assertNotNull(students);
|
||||
Assert.assertEquals(2, students.size());
|
||||
assertNotNull(students);
|
||||
assertEquals(2, students.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAllStudentsWithMaxNumberOfRecords() {
|
||||
List<?> students = studentService.getStudents(1);
|
||||
Assert.assertNotNull(students);
|
||||
Assert.assertEquals(1, students.size());
|
||||
assertNotNull(students);
|
||||
assertEquals(1, students.size());
|
||||
}
|
||||
|
||||
|
||||
@@ -86,27 +93,24 @@ public class JpaOutboundGatewayTests {
|
||||
|
||||
try {
|
||||
studentService.deleteStudent(student);
|
||||
fail("IllegalArgumentException is expected");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
return;
|
||||
assertThat(e.getMessage(), startsWith("Removing a detached instance"));
|
||||
}
|
||||
|
||||
Assert.fail("Was expecting a MessageHandlingException to be thrown.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getStudentWithException() {
|
||||
try {
|
||||
studentService.getStudentWithException(1001L);
|
||||
fail("MessageHandlingException is expected");
|
||||
}
|
||||
catch (MessagingException e) {
|
||||
Assert.assertEquals("The Jpa operation returned more than 1 result object but expectSingleResult was 'true'.",
|
||||
assertEquals("The Jpa operation returned more than 1 result for expectSingleResult mode.",
|
||||
e.getMessage());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.fail("Was expecting a MessageHandlingException to be thrown.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -114,15 +118,15 @@ public class JpaOutboundGatewayTests {
|
||||
|
||||
StudentDomain student = studentService.getStudentWithParameters("First Two");
|
||||
|
||||
Assert.assertEquals("First Two", student.getFirstName());
|
||||
Assert.assertEquals("Last Two", student.getLastName());
|
||||
assertEquals("First Two", student.getFirstName());
|
||||
assertEquals("Last Two", student.getLastName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAllStudents() {
|
||||
List<StudentDomain> students = studentService.getAllStudents();
|
||||
Assert.assertNotNull(students);
|
||||
Assert.assertTrue(students.size() == 3);
|
||||
assertNotNull(students);
|
||||
assertEquals(3, students.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -130,11 +134,11 @@ public class JpaOutboundGatewayTests {
|
||||
public void persistStudent() {
|
||||
|
||||
final StudentDomain studentToPersist = JpaTestUtils.getTestStudent();
|
||||
Assert.assertNull(studentToPersist.getRollNumber());
|
||||
assertNull(studentToPersist.getRollNumber());
|
||||
|
||||
final StudentDomain persistedStudent = studentService.persistStudent(studentToPersist);
|
||||
Assert.assertNotNull(persistedStudent);
|
||||
Assert.assertNotNull(persistedStudent.getRollNumber());
|
||||
assertNotNull(persistedStudent);
|
||||
assertNotNull(persistedStudent.getRollNumber());
|
||||
|
||||
}
|
||||
|
||||
@@ -143,18 +147,18 @@ public class JpaOutboundGatewayTests {
|
||||
public void persistStudentUsingMerge() {
|
||||
|
||||
final StudentDomain studentToPersist = JpaTestUtils.getTestStudent();
|
||||
Assert.assertNull(studentToPersist.getRollNumber());
|
||||
assertNull(studentToPersist.getRollNumber());
|
||||
|
||||
final StudentDomain persistedStudent = studentService.persistStudentUsingMerge(studentToPersist);
|
||||
Assert.assertNotNull(persistedStudent);
|
||||
Assert.assertNotNull(persistedStudent.getRollNumber());
|
||||
assertNotNull(persistedStudent);
|
||||
assertNotNull(persistedStudent.getRollNumber());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetrievingGatewayInsideChain() {
|
||||
final StudentDomain student = studentService.getStudent2(1001L);
|
||||
Assert.assertNotNull(student);
|
||||
assertNotNull(student);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -162,18 +166,18 @@ public class JpaOutboundGatewayTests {
|
||||
public void testUpdatingGatewayInsideChain() {
|
||||
|
||||
final StudentDomain studentToPersist = JpaTestUtils.getTestStudent();
|
||||
Assert.assertNull(studentToPersist.getRollNumber());
|
||||
assertNull(studentToPersist.getRollNumber());
|
||||
|
||||
final StudentDomain persistedStudent = studentService.persistStudent2(studentToPersist);
|
||||
Assert.assertNotNull(persistedStudent);
|
||||
Assert.assertNotNull(persistedStudent.getRollNumber());
|
||||
assertNotNull(persistedStudent);
|
||||
assertNotNull(persistedStudent.getRollNumber());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJpaRepositoryAsService() {
|
||||
List<StudentDomain> students = this.studentService.getStudentsUsingJpaRepository("F");
|
||||
Assert.assertEquals(2, students.size());
|
||||
assertEquals(2, students.size());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -368,7 +368,7 @@ If `false`, the `List` of entities is sent as the payload of the message.
|
||||
The value defaults to `false`.
|
||||
Optional.
|
||||
<9> An implementation of `org.springframework.integration.jpa.core.JpaOperations` used to perform the JPA operations.
|
||||
We recommend not providig an implementation of your own but using the default `org.springframework.integration.jpa.core.DefaultJpaOperations` implementation.
|
||||
We recommend not providing an implementation of your own but using the default `org.springframework.integration.jpa.core.DefaultJpaOperations` implementation.
|
||||
You can use any of the `entity-manager`, `entity-manager-factory`, or `jpa-operations` attributes.
|
||||
Optional.
|
||||
<10> The JPA QL to be executed by this adapter.
|
||||
@@ -705,7 +705,7 @@ It defaults to `Ordered.LOWEST_PRECEDENCE`.
|
||||
Optional.
|
||||
<11> An instance of `o.s.i.jpa.support.parametersource.ParameterSourceFactory` used to get an instance of `o.s.i.jpa.support.parametersource.ParameterSource`, which is used to resolve the values of the parameters in the query.
|
||||
Ignored if you perform operations by using a JPA entity.
|
||||
If you use a parameter element, the factory must be of type `ExpressionEvaluatingParameterSourceFactory`, located in the `o.s.i.jpa.support.parametersource` package.
|
||||
The `parameter` sub-elements are mutually exclusive with the `parameter-source-factory` attribute and must be configured on the provided `ParameterSourceFactory`.
|
||||
Optional.
|
||||
<12> Accepts one of the following: `PERSIST`, `MERGE`, or `DELETE`.
|
||||
Indicates the operation that the adapter needs to perform.
|
||||
@@ -872,8 +872,8 @@ Consequently, the following parameters is available:
|
||||
`parameter-source-factory`::
|
||||
An instance of `o.s.i.jpa.support.parametersource.ParameterSourceFactory` used to get an instance of `o.s.i.jpa.support.parametersource.ParameterSource`.
|
||||
The `ParameterSource` is used to resolve the values of the parameters provided in the query.
|
||||
If you perform operations by using a JPA entity, the `parameter-source-factory attribute` is ignored.
|
||||
If you use a `parameter` element, the factory must be of type `ExpressionEvaluatingParameterSourceFactory` (located in package `o.s.i.jpa.support.parametersource`).
|
||||
If you perform operations by using a JPA entity, the `parameter-source-factory` attribute is ignored.
|
||||
The `parameter` sub-elements are mutually exclusive with the `parameter-source-factory` and they have to be configured on the provided `ParameterSourceFactory`.
|
||||
Optional.
|
||||
|
||||
`use-payload-as-parameter-source`::
|
||||
@@ -1047,7 +1047,7 @@ The following example shows all the attributes that you can set on a retrieving
|
||||
</int-jpa:retrieving-outbound-gateway>
|
||||
----
|
||||
|
||||
<1> (Since Spring Integration 4.0.) The SpEL expression that determines the `primaryKey` value for `EntityManager.find(Class entityClass, Object primaryKey)` method against the `requestMessage` as the root object of evaluation context.
|
||||
<1> (Since Spring Integration 4.0) The SpEL expression that determines the `primaryKey` value for `EntityManager.find(Class entityClass, Object primaryKey)` method against the `requestMessage` as the root object of evaluation context.
|
||||
The `entityClass` argument is determined from the `entity-class` attribute, if present.
|
||||
Otherwise, it is determined from the `payload` class.
|
||||
All other attributes are disallowed if you use `id-expression`.
|
||||
@@ -1075,7 +1075,8 @@ Version 3.0 introduced this attribute.
|
||||
Optional.
|
||||
====
|
||||
|
||||
The remaining attributes are described earlier in this chapter. See <<jpaInboundChannelAdapterParameters>> and <<jpaOutboundChannelAdapterParameters>>.
|
||||
The remaining attributes are described earlier in this chapter.
|
||||
See <<jpaInboundChannelAdapterParameters>> and <<jpaOutboundChannelAdapterParameters>>.
|
||||
|
||||
==== Configuring with Java Configuration
|
||||
|
||||
@@ -1237,7 +1238,7 @@ The following example uses a retrieving outbound gateway and JPQL to retrieve (s
|
||||
----
|
||||
====
|
||||
|
||||
Retrieving an Entity by Using `id-expression`
|
||||
===== Retrieving an Entity by Using `id-expression`
|
||||
|
||||
The following example uses a retrieving outbound gateway with `id-expression` to retrieve (find) one and only one entity from the database:
|
||||
The `primaryKey` is the result of `id-expression` evaluation.
|
||||
|
||||
Reference in New Issue
Block a user