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.jdbc.operations.DefaultJdbcRepositoryOperations 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.jdbc.operations;
import io.micronaut.aop.InvocationContext;
import io.micronaut.context.BeanContext;
import io.micronaut.context.annotation.EachBean;
import io.micronaut.context.annotation.Parameter;
import io.micronaut.core.annotation.AnnotationMetadata;
import io.micronaut.core.annotation.Internal;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.beans.BeanProperty;
import io.micronaut.core.convert.ArgumentConversionContext;
import io.micronaut.core.convert.ConversionContext;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.data.annotation.QueryResult;
import io.micronaut.data.exceptions.DataAccessException;
import io.micronaut.data.jdbc.config.DataJdbcConfiguration;
import io.micronaut.data.jdbc.convert.JdbcConversionContext;
import io.micronaut.data.jdbc.mapper.ColumnIndexResultSetReader;
import io.micronaut.data.jdbc.mapper.ColumnNameResultSetReader;
import io.micronaut.data.jdbc.mapper.JdbcQueryStatement;
import io.micronaut.data.jdbc.mapper.SqlResultConsumer;
import io.micronaut.data.jdbc.runtime.ConnectionCallback;
import io.micronaut.data.jdbc.runtime.PreparedStatementCallback;
import io.micronaut.data.model.DataType;
import io.micronaut.data.model.JsonDataType;
import io.micronaut.data.model.Page;
import io.micronaut.data.model.query.JoinPath;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.model.runtime.AttributeConverterRegistry;
import io.micronaut.data.model.runtime.DeleteBatchOperation;
import io.micronaut.data.model.runtime.DeleteOperation;
import io.micronaut.data.model.runtime.EntityOperation;
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.QueryResultInfo;
import io.micronaut.data.model.runtime.RuntimeAssociation;
import io.micronaut.data.model.runtime.RuntimeEntityRegistry;
import io.micronaut.data.model.runtime.RuntimePersistentEntity;
import io.micronaut.data.model.runtime.RuntimePersistentProperty;
import io.micronaut.data.model.runtime.UpdateBatchOperation;
import io.micronaut.data.model.runtime.UpdateOperation;
import io.micronaut.data.model.runtime.convert.AttributeConverter;
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.convert.RuntimePersistentPropertyConversionContext;
import io.micronaut.data.runtime.date.DateTimeProvider;
import io.micronaut.data.runtime.mapper.DTOMapper;
import io.micronaut.data.runtime.mapper.ResultConsumer;
import io.micronaut.data.runtime.mapper.ResultReader;
import io.micronaut.data.runtime.mapper.TypeMapper;
import io.micronaut.data.runtime.mapper.sql.SqlDTOMapper;
import io.micronaut.data.runtime.mapper.sql.SqlResultEntityTypeMapper;
import io.micronaut.data.runtime.mapper.sql.SqlTypeMapper;
import io.micronaut.data.runtime.multitenancy.SchemaTenantResolver;
import io.micronaut.data.runtime.operations.ExecutorAsyncOperations;
import io.micronaut.data.runtime.operations.ExecutorReactiveOperations;
import io.micronaut.data.runtime.operations.internal.AbstractSyncEntitiesOperations;
import io.micronaut.data.runtime.operations.internal.AbstractSyncEntityOperations;
import io.micronaut.data.runtime.operations.internal.OperationContext;
import io.micronaut.data.runtime.operations.internal.SyncCascadeOperations;
import io.micronaut.data.runtime.operations.internal.query.BindableParametersStoredQuery;
import io.micronaut.data.runtime.operations.internal.sql.AbstractSqlRepositoryOperations;
import io.micronaut.data.runtime.operations.internal.sql.SqlJsonColumnMapperProvider;
import io.micronaut.data.runtime.operations.internal.sql.SqlPreparedQuery;
import io.micronaut.data.runtime.operations.internal.sql.SqlStoredQuery;
import io.micronaut.data.runtime.support.AbstractConversionContext;
import io.micronaut.json.JsonMapper;
import io.micronaut.transaction.TransactionOperations;
import io.micronaut.transaction.jdbc.DataSourceUtils;
import io.micronaut.transaction.jdbc.DelegatingDataSource;
import jakarta.inject.Named;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jakarta.annotation.PreDestroy;
import javax.sql.DataSource;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* Implementation of {@link JdbcRepositoryOperations}.
*
* @author graemerocher
* @author Denis Stepanov
* @since 1.0.0
*/
@EachBean(DataSource.class)
@Internal
public final class DefaultJdbcRepositoryOperations extends AbstractSqlRepositoryOperations implements
JdbcRepositoryOperations,
AsyncCapableRepository,
ReactiveCapableRepository,
AutoCloseable,
SyncCascadeOperations.SyncCascadeOperationsHelper {
private static final Logger LOG = LoggerFactory.getLogger(DefaultJdbcRepositoryOperations.class);
private final TransactionOperations transactionOperations;
private final DataSource dataSource;
private final DataSource unwrapedDataSource;
private ExecutorAsyncOperations asyncOperations;
private ExecutorService executorService;
private final SyncCascadeOperations cascadeOperations;
private final DataJdbcConfiguration jdbcConfiguration;
@Nullable
private final SchemaTenantResolver schemaTenantResolver;
private final JdbcSchemaHandler schemaHandler;
/**
* Default constructor.
*
* @param dataSourceName The data source name
* @param jdbcConfiguration The jdbcConfiguration
* @param dataSource The datasource
* @param transactionOperations The JDBC operations for the data source
* @param executorService The executor service
* @param beanContext The bean context
* @param dateTimeProvider The dateTimeProvider
* @param entityRegistry The entity registry
* @param conversionService The conversion service
* @param attributeConverterRegistry The attribute converter registry
* @param schemaTenantResolver The schema tenant resolver
* @param schemaHandler The schema handler
* @param jsonMapper The JSON mapper
* @param sqlJsonColumnMapperProvider The SQL JSON column mapper provider
*/
@Internal
@SuppressWarnings("ParameterNumber")
protected DefaultJdbcRepositoryOperations(@Parameter String dataSourceName,
@Parameter DataJdbcConfiguration jdbcConfiguration,
DataSource dataSource,
@Parameter TransactionOperations transactionOperations,
@Named("io") @Nullable ExecutorService executorService,
BeanContext beanContext,
@NonNull DateTimeProvider dateTimeProvider,
RuntimeEntityRegistry entityRegistry,
DataConversionService conversionService,
AttributeConverterRegistry attributeConverterRegistry,
@Nullable
SchemaTenantResolver schemaTenantResolver,
JdbcSchemaHandler schemaHandler,
@Nullable JsonMapper jsonMapper,
SqlJsonColumnMapperProvider sqlJsonColumnMapperProvider) {
super(
dataSourceName,
new ColumnNameResultSetReader(conversionService),
new ColumnIndexResultSetReader(conversionService),
new JdbcQueryStatement(conversionService),
dateTimeProvider,
entityRegistry,
beanContext,
conversionService,
attributeConverterRegistry,
jsonMapper,
sqlJsonColumnMapperProvider);
this.schemaTenantResolver = schemaTenantResolver;
this.schemaHandler = schemaHandler;
ArgumentUtils.requireNonNull("dataSource", dataSource);
ArgumentUtils.requireNonNull("transactionOperations", transactionOperations);
this.dataSource = dataSource;
this.unwrapedDataSource = DelegatingDataSource.unwrapDataSource(dataSource);
this.transactionOperations = transactionOperations;
this.executorService = executorService;
this.cascadeOperations = new SyncCascadeOperations<>(conversionService, this);
this.jdbcConfiguration = jdbcConfiguration;
}
@NonNull
private ExecutorService newLocalThreadPool() {
this.executorService = Executors.newCachedThreadPool();
return executorService;
}
@Override
public T persistOne(JdbcOperationContext ctx, T value, RuntimePersistentEntity persistentEntity) {
SqlStoredQuery storedQuery = resolveEntityInsert(ctx.annotationMetadata, ctx.repositoryType, (Class) value.getClass(), persistentEntity);
JdbcEntityOperations persistOneOp = new JdbcEntityOperations<>(ctx, storedQuery, persistentEntity, value, true);
persistOneOp.persist();
return persistOneOp.getEntity();
}
@Override
public List persistBatch(JdbcOperationContext ctx, Iterable values,
RuntimePersistentEntity childPersistentEntity,
Predicate predicate) {
SqlStoredQuery storedQuery = resolveEntityInsert(
ctx.annotationMetadata,
ctx.repositoryType,
childPersistentEntity.getIntrospection().getBeanType(),
childPersistentEntity
);
JdbcEntitiesOperations persistBatchOp = new JdbcEntitiesOperations<>(ctx, childPersistentEntity, values, storedQuery, true);
persistBatchOp.veto(predicate);
persistBatchOp.persist();
return persistBatchOp.getEntities();
}
@Override
public T updateOne(JdbcOperationContext ctx, T value, RuntimePersistentEntity persistentEntity) {
SqlStoredQuery storedQuery = resolveEntityUpdate(ctx.annotationMetadata, ctx.repositoryType, (Class) value.getClass(), persistentEntity);
JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, persistentEntity, value, storedQuery);
op.update();
return op.getEntity();
}
@Override
public void persistManyAssociation(JdbcOperationContext ctx,
RuntimeAssociation runtimeAssociation,
Object value, RuntimePersistentEntity persistentEntity,
Object child, RuntimePersistentEntity childPersistentEntity) {
SqlStoredQuery storedQuery = resolveSqlInsertAssociation(ctx.repositoryType, runtimeAssociation, persistentEntity, value);
try {
new JdbcEntityOperations<>(ctx, childPersistentEntity, child, storedQuery).execute();
} catch (Exception e) {
throw new DataAccessException("SQL error executing INSERT: " + e.getMessage(), e);
}
}
@Override
public void persistManyAssociationBatch(JdbcOperationContext ctx,
RuntimeAssociation runtimeAssociation,
Object value, RuntimePersistentEntity persistentEntity,
Iterable child, RuntimePersistentEntity childPersistentEntity) {
SqlStoredQuery storedQuery = resolveSqlInsertAssociation(ctx.repositoryType, runtimeAssociation, persistentEntity, value);
try {
JdbcEntitiesOperations assocOp = new JdbcEntitiesOperations<>(ctx, childPersistentEntity, child, storedQuery);
assocOp.veto(ctx.persisted::contains);
assocOp.execute();
} catch (Exception e) {
throw new DataAccessException("SQL error executing INSERT: " + e.getMessage(), e);
}
}
@NonNull
@Override
public ExecutorAsyncOperations async() {
ExecutorAsyncOperations asyncOperations = this.asyncOperations;
if (asyncOperations == null) {
synchronized (this) { // double check
asyncOperations = this.asyncOperations;
if (asyncOperations == null) {
asyncOperations = new ExecutorAsyncOperations(
this,
executorService != null ? executorService : newLocalThreadPool()
);
this.asyncOperations = asyncOperations;
}
}
}
return asyncOperations;
}
@NonNull
@Override
public ReactiveRepositoryOperations reactive() {
return new ExecutorReactiveOperations(async(), conversionService);
}
@Nullable
@Override
public R findOne(@NonNull PreparedQuery pq) {
return executeRead(connection -> {
SqlPreparedQuery preparedQuery = getSqlPreparedQuery(pq);
RuntimePersistentEntity persistentEntity = preparedQuery.getPersistentEntity();
try (PreparedStatement ps = prepareStatement(connection::prepareStatement, preparedQuery, false, true)) {
preparedQuery.bindParameters(new JdbcParameterBinder(connection, ps, preparedQuery));
try (ResultSet rs = ps.executeQuery()) {
Class resultType = preparedQuery.getResultType();
if (preparedQuery.getResultDataType() == DataType.ENTITY) {
RuntimePersistentEntity resultPersistentEntity = getEntity(resultType);
BiFunction, Object, Object> loadListener = (loadedEntity, o) -> {
if (loadedEntity.hasPostLoadEventListeners()) {
return triggerPostLoad(o, loadedEntity, preparedQuery.getAnnotationMetadata());
} else {
return o;
}
};
QueryResultInfo queryResultInfo = preparedQuery.getQueryResultInfo();
if (queryResultInfo == null || queryResultInfo.getType() == QueryResult.Type.TABULAR) {
final Set joinFetchPaths = preparedQuery.getJoinFetchPaths();
SqlResultEntityTypeMapper mapper = new SqlResultEntityTypeMapper<>(
resultPersistentEntity,
columnNameResultSetReader,
joinFetchPaths,
sqlJsonColumnMapperProvider.getJsonColumnReader(preparedQuery, ResultSet.class),
loadListener,
conversionService);
SqlResultEntityTypeMapper.PushingMapper oneMapper = mapper.readOneWithJoins();
if (rs.next()) {
oneMapper.processRow(rs);
}
while (!joinFetchPaths.isEmpty() && rs.next()) {
oneMapper.processRow(rs);
}
R result = oneMapper.getResult();
if (preparedQuery.hasResultConsumer()) {
preparedQuery.getParameterInRole(SqlResultConsumer.ROLE, SqlResultConsumer.class)
.ifPresent(consumer -> consumer.accept(result, newMappingContext(rs)));
}
return result;
} else {
return rs.next() ? mapQueryColumnResult(preparedQuery, rs, queryResultInfo.getColumnName(), queryResultInfo.getJsonDataType(),
persistentEntity, resultType, ResultSet.class, loadListener) : null;
}
} else if (rs.next()) {
if (preparedQuery.isDtoProjection()) {
QueryResultInfo queryResultInfo = preparedQuery.getQueryResultInfo();
if (queryResultInfo == null || queryResultInfo.getType() == QueryResult.Type.TABULAR) {
boolean isRawQuery = preparedQuery.isRawQuery();
TypeMapper introspectedDataMapper = new SqlDTOMapper<>(
persistentEntity,
isRawQuery ? getEntity(preparedQuery.getResultType()) : persistentEntity,
columnNameResultSetReader,
sqlJsonColumnMapperProvider.getJsonColumnReader(preparedQuery, ResultSet.class),
conversionService
);
return introspectedDataMapper.map(rs, resultType);
} else {
String column = queryResultInfo.getColumnName();
JsonDataType jsonDataType = queryResultInfo.getJsonDataType();
return mapQueryColumnResult(preparedQuery, rs, column, jsonDataType, persistentEntity, resultType, ResultSet.class, null);
}
} else {
QueryResultInfo queryResultInfo = preparedQuery.getQueryResultInfo();
if (queryResultInfo == null || queryResultInfo.getType() == QueryResult.Type.TABULAR) {
Object v = columnIndexResultSetReader.readDynamic(rs, 1, preparedQuery.getResultDataType());
if (v == null) {
return null;
} else if (resultType.isInstance(v)) {
return (R) v;
} else {
return columnIndexResultSetReader.convertRequired(v, resultType);
}
} else {
String column = queryResultInfo.getColumnName();
JsonDataType jsonDataType = queryResultInfo.getJsonDataType();
return mapQueryColumnResult(preparedQuery, rs, column, jsonDataType, persistentEntity, resultType, ResultSet.class, null);
}
}
}
}
} catch (SQLException e) {
throw new DataAccessException("Error executing SQL Query: " + e.getMessage(), e);
}
return null;
});
}
@Override
public boolean exists(@NonNull PreparedQuery pq) {
return executeRead(connection -> {
try {
SqlPreparedQuery preparedQuery = getSqlPreparedQuery(pq);
try (PreparedStatement ps = prepareStatement(connection::prepareStatement, preparedQuery, false, true)) {
preparedQuery.bindParameters(new JdbcParameterBinder(connection, ps, preparedQuery));
try (ResultSet rs = ps.executeQuery()) {
return rs.next();
}
}
} catch (SQLException e) {
throw new DataAccessException("Error executing SQL query: " + e.getMessage(), e);
}
});
}
@NonNull
@Override
public Stream findStream(@NonNull PreparedQuery preparedQuery) {
ConnectionContext connectionContext = getConnectionCtx();
return findStream(preparedQuery, connectionContext.connection, connectionContext.needsToBeClosed);
}
private Stream findStream(@NonNull PreparedQuery pq, Connection connection, boolean closeConnection) {
SqlPreparedQuery preparedQuery = getSqlPreparedQuery(pq);
Class resultType = preparedQuery.getResultType();
AtomicBoolean finished = new AtomicBoolean();
PreparedStatement ps;
try {
ps = prepareStatement(connection::prepareStatement, preparedQuery, false, false);
preparedQuery.bindParameters(new JdbcParameterBinder(connection, ps, preparedQuery));
} catch (Exception e) {
throw new DataAccessException("SQL Error preparing Query: " + e.getMessage(), e);
}
ResultSet openedRs = null;
ResultSet rs;
try {
openedRs = ps.executeQuery();
rs = openedRs;
boolean dtoProjection = preparedQuery.isDtoProjection();
boolean isEntity = preparedQuery.getResultDataType() == DataType.ENTITY;
Spliterator spliterator;
if (isEntity || dtoProjection) {
RuntimePersistentEntity persistentEntity = preparedQuery.getPersistentEntity();
SqlResultConsumer sqlMappingConsumer = preparedQuery.hasResultConsumer() ? preparedQuery.getParameterInRole(SqlResultConsumer.ROLE, SqlResultConsumer.class).orElse(null) : null;
SqlTypeMapper mapper;
if (dtoProjection) {
QueryResultInfo queryResultInfo = preparedQuery.getQueryResultInfo();
if (queryResultInfo == null || queryResultInfo.getType() == QueryResult.Type.TABULAR) {
boolean isRawQuery = preparedQuery.isRawQuery();
mapper = new SqlDTOMapper<>(
persistentEntity,
isRawQuery ? getEntity(preparedQuery.getResultType()) : persistentEntity,
columnNameResultSetReader,
sqlJsonColumnMapperProvider.getJsonColumnReader(preparedQuery, ResultSet.class),
conversionService
);
} else {
String column = queryResultInfo.getColumnName();
JsonDataType jsonDataType = queryResultInfo.getJsonDataType();
mapper = createQueryResultMapper(preparedQuery, column, jsonDataType, ResultSet.class, persistentEntity, null);
}
} else {
BiFunction, Object, Object> loadListener = (loadedEntity, o) -> {
if (loadedEntity.hasPostLoadEventListeners()) {
return triggerPostLoad(o, loadedEntity, preparedQuery.getAnnotationMetadata());
} else {
return o;
}
};
QueryResultInfo queryResultInfo = preparedQuery.getQueryResultInfo();
if (queryResultInfo == null || queryResultInfo.getType() == QueryResult.Type.TABULAR) {
Set joinFetchPaths = preparedQuery.getJoinFetchPaths();
SqlResultEntityTypeMapper entityTypeMapper = new SqlResultEntityTypeMapper<>(
getEntity(resultType),
columnNameResultSetReader,
joinFetchPaths,
sqlJsonColumnMapperProvider.getJsonColumnReader(preparedQuery, ResultSet.class),
loadListener,
conversionService);
boolean onlySingleEndedJoins = isOnlySingleEndedJoins(persistentEntity, joinFetchPaths);
// Cannot stream ResultSet for "many" joined query
if (!onlySingleEndedJoins) {
try {
SqlResultEntityTypeMapper.PushingMapper> manyMapper = entityTypeMapper.readAllWithJoins();
while (rs.next()) {
manyMapper.processRow(rs);
}
return manyMapper.getResult().stream();
} finally {
closeResultSet(connection, ps, rs, finished, closeConnection);
}
} else {
mapper = entityTypeMapper;
}
} else {
String column = queryResultInfo.getColumnName();
JsonDataType jsonDataType = queryResultInfo.getJsonDataType();
mapper = createQueryResultMapper(preparedQuery, column, jsonDataType, ResultSet.class, persistentEntity, loadListener);
}
}
spliterator = new Spliterators.AbstractSpliterator(Long.MAX_VALUE,
Spliterator.ORDERED | Spliterator.IMMUTABLE) {
@Override
public boolean tryAdvance(Consumer super R> action) {
if (finished.get()) {
return false;
}
boolean hasNext = mapper.hasNext(rs);
if (hasNext) {
R o = mapper.map(rs, resultType);
if (sqlMappingConsumer != null) {
sqlMappingConsumer.accept(rs, o);
}
action.accept(o);
} else {
closeResultSet(connection, ps, rs, finished, closeConnection);
}
return hasNext;
}
};
} else {
spliterator = new Spliterators.AbstractSpliterator(Long.MAX_VALUE,
Spliterator.ORDERED | Spliterator.IMMUTABLE) {
@Override
public boolean tryAdvance(Consumer super R> action) {
if (finished.get()) {
return false;
}
try {
boolean hasNext = rs.next();
if (hasNext) {
Object v = columnIndexResultSetReader
.readDynamic(rs, 1, preparedQuery.getResultDataType());
if (resultType.isInstance(v)) {
//noinspection unchecked
action.accept((R) v);
} else if (v != null) {
Object r = columnIndexResultSetReader.convertRequired(v, resultType);
if (r != null) {
action.accept((R) r);
}
}
} else {
closeResultSet(connection, ps, rs, finished, closeConnection);
}
return hasNext;
} catch (SQLException e) {
throw new DataAccessException("Error retrieving next JDBC result: " + e.getMessage(), e);
}
}
};
}
return StreamSupport.stream(spliterator, false)
.onClose(() -> closeResultSet(connection, ps, rs, finished, closeConnection));
} catch (Exception e) {
closeResultSet(connection, ps, openedRs, finished, closeConnection);
throw new DataAccessException("SQL Error executing Query: " + e.getMessage(), e);
}
}
private void closeResultSet(Connection connection, PreparedStatement ps, ResultSet rs, AtomicBoolean finished, boolean closeConnection) {
if (finished.compareAndSet(false, true)) {
try {
if (rs != null) {
rs.close();
}
if (ps != null) {
ps.close();
}
if (closeConnection) {
connection.close();
}
} catch (SQLException e) {
throw new DataAccessException("Error closing JDBC result stream: " + e.getMessage(), e);
}
}
}
@NonNull
@Override
public Iterable findAll(@NonNull PreparedQuery preparedQuery) {
return executeRead(connection -> {
try (Stream stream = findStream(preparedQuery, connection, false)) {
return stream.collect(Collectors.toList());
}
});
}
@NonNull
@Override
public Optional executeUpdate(@NonNull PreparedQuery, Number> pq) {
return executeWrite(connection -> {
SqlPreparedQuery, Number> preparedQuery = getSqlPreparedQuery(pq);
try {
try (PreparedStatement ps = prepareStatement(connection::prepareStatement, preparedQuery, true, false)) {
preparedQuery.bindParameters(new JdbcParameterBinder(connection, ps, preparedQuery));
int result = ps.executeUpdate();
if (QUERY_LOG.isTraceEnabled()) {
QUERY_LOG.trace("Update operation updated {} records", result);
}
if (preparedQuery.isOptimisticLock()) {
checkOptimisticLocking(1, result);
}
return Optional.of(result);
}
} catch (SQLException e) {
Throwable throwable = handleSqlException(e, preparedQuery.getDialect());
if (throwable instanceof DataAccessException dataAccessException) {
throw dataAccessException;
}
throw new DataAccessException("Error executing SQL UPDATE: " + e.getMessage(), e);
}
});
}
private Integer sum(Stream stream) {
return stream.mapToInt(i -> i).sum();
}
@Override
public Optional deleteAll(@NonNull DeleteBatchOperation operation) {
return Optional.ofNullable(executeWrite(connection -> {
SqlStoredQuery storedQuery = getSqlStoredQuery(operation.getStoredQuery());
JdbcOperationContext ctx = createContext(operation, connection, storedQuery);
RuntimePersistentEntity persistentEntity = storedQuery.getPersistentEntity();
if (isSupportsBatchDelete(persistentEntity, storedQuery.getDialect())) {
JdbcEntitiesOperations op = new JdbcEntitiesOperations<>(ctx, persistentEntity, operation, storedQuery);
op.delete();
return op.rowsUpdated;
}
return sum(
operation.split().stream()
.map(deleteOp -> {
JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, persistentEntity, deleteOp.getEntity(), storedQuery);
op.delete();
return op.rowsUpdated;
})
);
}));
}
@Override
public int delete(@NonNull DeleteOperation operation) {
return executeWrite(connection -> {
SqlStoredQuery storedQuery = getSqlStoredQuery(operation.getStoredQuery());
JdbcOperationContext ctx = createContext(operation, connection, storedQuery);
JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery.getPersistentEntity(), operation.getEntity(), storedQuery);
op.delete();
return op;
}).rowsUpdated;
}
@NonNull
@Override
public T update(@NonNull UpdateOperation operation) {
return executeWrite(connection -> {
SqlStoredQuery storedQuery = getSqlStoredQuery(operation.getStoredQuery());
JdbcOperationContext ctx = createContext(operation, connection, storedQuery);
JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery.getPersistentEntity(), operation.getEntity(), storedQuery);
op.update();
return op.getEntity();
});
}
@NonNull
@Override
public Iterable updateAll(@NonNull UpdateBatchOperation operation) {
return executeWrite(connection -> {
final SqlStoredQuery storedQuery = getSqlStoredQuery(operation.getStoredQuery());
final RuntimePersistentEntity persistentEntity = storedQuery.getPersistentEntity();
JdbcOperationContext ctx = createContext(operation, connection, storedQuery);
if (!isSupportsBatchUpdate(persistentEntity, storedQuery.getDialect())) {
return operation.split()
.stream()
.map(updateOp -> {
JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, persistentEntity, updateOp.getEntity(), storedQuery);
op.update();
return op.getEntity();
})
.collect(Collectors.toList());
}
JdbcEntitiesOperations op = new JdbcEntitiesOperations<>(ctx, persistentEntity, operation, storedQuery);
op.update();
return op.getEntities();
});
}
@NonNull
@Override
public T persist(@NonNull InsertOperation operation) {
return executeWrite(connection -> {
final SqlStoredQuery storedQuery = getSqlStoredQuery(operation.getStoredQuery());
JdbcOperationContext ctx = createContext(operation, connection, storedQuery);
JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery, storedQuery.getPersistentEntity(), operation.getEntity(), true);
op.persist();
return op;
}).getEntity();
}
@Nullable
@Override
public T findOne(@NonNull Class type, @NonNull Serializable id) {
throw new UnsupportedOperationException("The findOne method by ID is not supported. Execute the SQL query directly");
}
@NonNull
@Override
public Iterable findAll(@NonNull PagedQuery query) {
throw new UnsupportedOperationException("The findAll method without an explicit query is not supported. Use findAll(PreparedQuery) instead");
}
@Override
public long count(PagedQuery pagedQuery) {
throw new UnsupportedOperationException("The count method without an explicit query is not supported. Use findAll(PreparedQuery) instead");
}
@NonNull
@Override
public Stream findStream(@NonNull PagedQuery query) {
throw new UnsupportedOperationException("The findStream method without an explicit query is not supported. Use findStream(PreparedQuery) instead");
}
@Override
public Page findPage(@NonNull PagedQuery query) {
throw new UnsupportedOperationException("The findPage method without an explicit query is not supported. Use findPage(PreparedQuery) instead");
}
@NonNull
public Iterable persistAll(@NonNull InsertBatchOperation operation) {
return executeWrite(connection -> {
final SqlStoredQuery storedQuery = getSqlStoredQuery(operation.getStoredQuery());
final RuntimePersistentEntity persistentEntity = storedQuery.getPersistentEntity();
JdbcOperationContext ctx = createContext(operation, connection, storedQuery);
if (!isSupportsBatchInsert(persistentEntity, storedQuery.getDialect())) {
return operation.split().stream()
.map(persistOp -> {
JdbcEntityOperations op = new JdbcEntityOperations<>(ctx, storedQuery, persistentEntity, persistOp.getEntity(), true);
op.persist();
return op.getEntity();
})
.collect(Collectors.toList());
} else {
JdbcEntitiesOperations op = new JdbcEntitiesOperations<>(ctx, persistentEntity, operation, storedQuery, true);
op.persist();
return op.getEntities();
}
});
}
private I executeRead(Function fn) {
if (jdbcConfiguration.isTransactionPerOperation()) {
return transactionOperations.executeRead(status -> {
Connection connection = status.getConnection();
applySchema(connection);
return fn.apply(connection);
});
}
if (!jdbcConfiguration.isAllowConnectionPerOperation() || transactionOperations.hasConnection()) {
Connection connection = transactionOperations.getConnection();
applySchema(connection);
return fn.apply(connection);
}
try (Connection connection = unwrapedDataSource.getConnection()) {
applySchema(connection);
return fn.apply(connection);
} catch (SQLException e) {
throw new DataAccessException("Cannot get connection: " + e.getMessage(), e);
}
}
private I executeWrite(Function fn) {
if (jdbcConfiguration.isTransactionPerOperation()) {
return transactionOperations.executeWrite(status -> {
Connection connection = status.getConnection();
applySchema(connection);
return fn.apply(connection);
});
}
if (!jdbcConfiguration.isAllowConnectionPerOperation() || transactionOperations.hasConnection()) {
Connection connection = transactionOperations.getConnection();
applySchema(connection);
return fn.apply(connection);
}
try (Connection connection = unwrapedDataSource.getConnection()) {
applySchema(connection);
return fn.apply(connection);
} catch (SQLException e) {
throw new DataAccessException("Cannot get connection: " + e.getMessage(), e);
}
}
@Override
@PreDestroy
public void close() {
if (executorService != null) {
executorService.shutdown();
}
}
private void applySchema(Connection connection) {
if (schemaTenantResolver != null) {
String schema = schemaTenantResolver.resolveTenantSchemaName();
schemaHandler.useSchema(connection, jdbcConfiguration.getDialect(), schema);
}
}
@NonNull
@Override
public DataSource getDataSource() {
return dataSource;
}
@NonNull
@Override
public Connection getConnection() {
Connection connection;
if (jdbcConfiguration.isTransactionPerOperation() || !jdbcConfiguration.isAllowConnectionPerOperation() || transactionOperations.hasConnection()) {
connection = transactionOperations.getConnection();
} else {
connection = DataSourceUtils.getConnection(dataSource, true);
}
applySchema(connection);
return connection;
}
@NonNull
private ConnectionContext getConnectionCtx() {
boolean needsToCloseConnection;
Connection connection;
if (jdbcConfiguration.isTransactionPerOperation() || !jdbcConfiguration.isAllowConnectionPerOperation() || transactionOperations.hasConnection()) {
connection = transactionOperations.getConnection();
needsToCloseConnection = false;
} else {
connection = DataSourceUtils.getConnection(dataSource, true);
needsToCloseConnection = true;
}
applySchema(connection);
return new ConnectionContext(connection, needsToCloseConnection);
}
@NonNull
@Override
public R execute(@NonNull ConnectionCallback callback) {
return executeWrite(connection -> {
try {
return callback.call(connection);
} catch (SQLException e) {
throw new DataAccessException("Error executing SQL Callback: " + e.getMessage(), e);
}
});
}
@NonNull
@Override
public R prepareStatement(@NonNull String sql, @NonNull PreparedStatementCallback callback) {
ArgumentUtils.requireNonNull("sql", sql);
ArgumentUtils.requireNonNull("callback", callback);
if (QUERY_LOG.isDebugEnabled()) {
QUERY_LOG.debug("Executing Query: {}", sql);
}
ConnectionContext connectionCtx = getConnectionCtx();
try {
R result = null;
try {
PreparedStatement ps = connectionCtx.connection.prepareStatement(sql);
try {
result = callback.call(ps);
return result;
} finally {
if (!(result instanceof AutoCloseable)) {
ps.close();
}
}
} finally {
if (!(result instanceof AutoCloseable) && connectionCtx.needsToBeClosed) {
connectionCtx.connection.close();
}
}
} catch (SQLException e) {
throw new DataAccessException("Error preparing SQL statement: " + e.getMessage(), e);
}
}
@NonNull
@Override
public Stream entityStream(@NonNull ResultSet resultSet, @NonNull Class rootEntity) {
return entityStream(resultSet, null, rootEntity);
}
@NonNull
@Override
public E readEntity(@NonNull String prefix, @NonNull ResultSet resultSet, @NonNull Class type) throws DataAccessException {
return new SqlResultEntityTypeMapper<>(
prefix,
getEntity(type),
columnNameResultSetReader,
jsonMapper != null ? () -> jsonMapper : null,
conversionService).map(resultSet, type);
}
@NonNull
@Override
public D readDTO(@NonNull String prefix, @NonNull ResultSet resultSet, @NonNull Class rootEntity, @NonNull Class dtoType) throws DataAccessException {
return new DTOMapper(
getEntity(rootEntity),
columnNameResultSetReader,
jsonMapper != null ? () -> jsonMapper : null,
conversionService).map(resultSet, dtoType);
}
@NonNull
@Override
public Stream entityStream(@NonNull ResultSet resultSet, @Nullable String prefix, @NonNull Class rootEntity) {
ArgumentUtils.requireNonNull("resultSet", resultSet);
ArgumentUtils.requireNonNull("rootEntity", rootEntity);
TypeMapper mapper = new SqlResultEntityTypeMapper<>(prefix, getEntity(rootEntity), columnNameResultSetReader,
jsonMapper != null ? () -> jsonMapper : null, conversionService);
Iterable iterable = () -> new Iterator() {
boolean fetched = false;
boolean end = false;
@Override
public boolean hasNext() {
if (fetched) {
return true;
}
if (end) {
return false;
}
try {
if (resultSet.next()) {
fetched = true;
} else {
end = true;
}
} catch (SQLException e) {
throw new DataAccessException("Error retrieving next JDBC result: " + e.getMessage(), e);
}
return !end;
}
@Override
public T next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
fetched = false;
return mapper.map(resultSet, rootEntity);
}
};
return StreamSupport.stream(iterable.spliterator(), false);
}
@NonNull
private ResultConsumer.Context newMappingContext(ResultSet rs) {
return new ResultConsumer.Context() {
@Override
public ResultSet getResultSet() {
return rs;
}
@Override
public ResultReader getResultReader() {
return columnNameResultSetReader;
}
@NonNull
@Override
public E readEntity(String prefix, Class type) throws DataAccessException {
RuntimePersistentEntity entity = getEntity(type);
SqlResultEntityTypeMapper mapper = new SqlResultEntityTypeMapper<>(
prefix,
entity,
columnNameResultSetReader,
jsonMapper != null ? () -> jsonMapper : null,
conversionService);
return mapper.map(rs, type);
}
@NonNull
@Override
public D readDTO(@NonNull String prefix, @NonNull Class rootEntity, @NonNull Class dtoType) throws DataAccessException {
RuntimePersistentEntity entity = getEntity(rootEntity);
TypeMapper introspectedDataMapper = new DTOMapper<>(
entity,
columnNameResultSetReader,
jsonMapper != null ? () -> jsonMapper : null,
conversionService);
return introspectedDataMapper.map(rs, dtoType);
}
};
}
private JdbcOperationContext createContext(EntityOperation operation, Connection connection, SqlStoredQuery storedQuery) {
return new JdbcOperationContext(operation.getAnnotationMetadata(), operation.getInvocationContext(), operation.getRepositoryType(), storedQuery.getDialect(), connection);
}
/**
* Gets the generated id on record insert.
*
* @param generatedKeysResultSet the generated keys result set
* @param identity the identity persistent field
* @param dialect the SQL dialect
* @return the generated id
*/
private Object getGeneratedIdentity(@NonNull ResultSet generatedKeysResultSet, RuntimePersistentProperty> identity, Dialect dialect) {
if (dialect == Dialect.POSTGRES) {
// Postgres returns all fields, not just id so we need to access generated id by the name
return columnNameResultSetReader.readDynamic(generatedKeysResultSet, identity.getPersistedName(), identity.getDataType());
}
return columnIndexResultSetReader.readDynamic(generatedKeysResultSet, 1, identity.getDataType());
}
@Override
public boolean isSupportsBatchInsert(JdbcOperationContext jdbcOperationContext, RuntimePersistentEntity> persistentEntity) {
return isSupportsBatchInsert(persistentEntity, jdbcOperationContext.dialect);
}
private final class JdbcParameterBinder implements BindableParametersStoredQuery.Binder {
private final SqlStoredQuery, ?> sqlStoredQuery;
private final Connection connection;
private final PreparedStatement ps;
private int index = 1;
public JdbcParameterBinder(Connection connection, PreparedStatement ps, SqlStoredQuery, ?> sqlStoredQuery) {
this.connection = connection;
this.ps = ps;
this.sqlStoredQuery = sqlStoredQuery;
}
@Override
public Object autoPopulateRuntimeProperty(RuntimePersistentProperty> persistentProperty, Object previousValue) {
return runtimeEntityRegistry.autoPopulateRuntimeProperty(persistentProperty, previousValue);
}
@Override
public Object convert(Object value, RuntimePersistentProperty> property) {
AttributeConverter converter = property.getConverter();
if (converter != null) {
return converter.convertToPersistedValue(value, createTypeConversionContext(property, property.getArgument()));
}
return value;
}
@Override
public Object convert(Class> converterClass, Object value, Argument> argument) {
if (converterClass == null) {
return value;
}
AttributeConverter converter = attributeConverterRegistry.getConverter(converterClass);
ConversionContext conversionContext = createTypeConversionContext(null, argument);
return converter.convertToPersistedValue(value, conversionContext);
}
private ConversionContext createTypeConversionContext(RuntimePersistentProperty> property,
Argument> argument) {
Objects.requireNonNull(connection);
if (property != null) {
return new RuntimePersistentPropertyJdbcCC(connection, property);
}
if (argument != null) {
return new ArgumentJdbcCC(connection, argument);
}
return new JdbcConversionContextImpl(connection);
}
@Override
public void bindOne(QueryParameterBinding binding, Object value) {
JsonDataType jsonDataType = null;
if (binding.getDataType() == DataType.JSON) {
jsonDataType = binding.getJsonDataType();
}
setStatementParameter(ps, index, binding.getDataType(), jsonDataType, value, sqlStoredQuery);
index++;
}
@Override
public void bindMany(QueryParameterBinding binding, Collection values) {
for (Object value : values) {
bindOne(binding, value);
}
}
@Override
public int currentIndex() {
return index;
}
}
private final class JdbcEntityOperations extends AbstractSyncEntityOperations {
private final SqlStoredQuery storedQuery;
private Integer rowsUpdated;
private Map previousValues;
private JdbcEntityOperations(JdbcOperationContext ctx, RuntimePersistentEntity persistentEntity, T entity, SqlStoredQuery storedQuery) {
this(ctx, storedQuery, persistentEntity, entity, false);
}
private JdbcEntityOperations(JdbcOperationContext ctx, SqlStoredQuery storedQuery, RuntimePersistentEntity persistentEntity, T entity, boolean insert) {
super(ctx,
DefaultJdbcRepositoryOperations.this.cascadeOperations,
entityEventRegistry, persistentEntity,
DefaultJdbcRepositoryOperations.this.conversionService, entity, insert);
this.storedQuery = storedQuery;
}
@Override
protected void collectAutoPopulatedPreviousValues() {
previousValues = storedQuery.collectAutoPopulatedPreviousValues(entity);
}
private PreparedStatement prepare(Connection connection, SqlStoredQuery storedQuery) throws SQLException {
if (storedQuery instanceof SqlPreparedQuery) {
((SqlPreparedQuery) storedQuery).prepare(entity);
}
if (insert) {
Dialect dialect = storedQuery.getDialect();
if (hasGeneratedId && (dialect == Dialect.ORACLE || dialect == Dialect.SQL_SERVER)) {
return connection.prepareStatement(this.storedQuery.getQuery(), new String[]{persistentEntity.getIdentity().getPersistedName()});
} else {
return connection.prepareStatement(this.storedQuery.getQuery(), hasGeneratedId ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS);
}
} else {
return connection.prepareStatement(this.storedQuery.getQuery());
}
}
@Override
protected void execute() throws SQLException {
if (QUERY_LOG.isDebugEnabled()) {
QUERY_LOG.debug("Executing SQL query: {}", storedQuery.getQuery());
}
try (PreparedStatement ps = prepare(ctx.connection, storedQuery)) {
storedQuery.bindParameters(new JdbcParameterBinder(ctx.connection, ps, storedQuery), ctx.invocationContext, entity, previousValues);
rowsUpdated = ps.executeUpdate();
if (hasGeneratedId) {
try (ResultSet generatedKeys = ps.getGeneratedKeys()) {
if (generatedKeys.next()) {
RuntimePersistentProperty identity = persistentEntity.getIdentity();
Object id = getGeneratedIdentity(generatedKeys, identity, storedQuery.getDialect());
BeanProperty property = identity.getProperty();
entity = updateEntityId(property, entity, id);
} else {
throw new DataAccessException("Failed to generate ID for entity: " + entity);
}
}
}
if (storedQuery.isOptimisticLock()) {
checkOptimisticLocking(1, rowsUpdated);
}
}
}
}
private final class JdbcEntitiesOperations extends AbstractSyncEntitiesOperations {
private final SqlStoredQuery storedQuery;
private int rowsUpdated;
private JdbcEntitiesOperations(JdbcOperationContext ctx, RuntimePersistentEntity persistentEntity, Iterable entities, SqlStoredQuery storedQuery) {
this(ctx, persistentEntity, entities, storedQuery, false);
}
private JdbcEntitiesOperations(JdbcOperationContext ctx, RuntimePersistentEntity persistentEntity, Iterable entities, SqlStoredQuery storedQuery, boolean insert) {
super(ctx,
DefaultJdbcRepositoryOperations.this.cascadeOperations,
DefaultJdbcRepositoryOperations.this.conversionService,
entityEventRegistry, persistentEntity, entities, insert);
this.storedQuery = storedQuery;
}
@Override
protected void collectAutoPopulatedPreviousValues() {
for (Data d : entities) {
if (d.vetoed) {
continue;
}
d.previousValues = storedQuery.collectAutoPopulatedPreviousValues(d.entity);
}
}
private PreparedStatement prepare(Connection connection) throws SQLException {
if (insert) {
Dialect dialect = storedQuery.getDialect();
if (hasGeneratedId && (dialect == Dialect.ORACLE || dialect == Dialect.SQL_SERVER)) {
return connection.prepareStatement(storedQuery.getQuery(), new String[]{persistentEntity.getIdentity().getPersistedName()});
} else {
return connection.prepareStatement(storedQuery.getQuery(), hasGeneratedId ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS);
}
} else {
return connection.prepareStatement(storedQuery.getQuery());
}
}
private void setParameters(PreparedStatement stmt, SqlStoredQuery storedQuery) throws SQLException {
for (Data d : entities) {
if (d.vetoed) {
continue;
}
storedQuery.bindParameters(new JdbcParameterBinder(ctx.connection, stmt, storedQuery), ctx.invocationContext, d.entity, d.previousValues);
stmt.addBatch();
}
}
@Override
protected void execute() throws SQLException {
if (QUERY_LOG.isDebugEnabled()) {
QUERY_LOG.debug("Executing SQL query: {}", storedQuery.getQuery());
}
try (PreparedStatement ps = prepare(ctx.connection)) {
setParameters(ps, storedQuery);
rowsUpdated = Arrays.stream(ps.executeBatch()).sum();
if (hasGeneratedId) {
RuntimePersistentProperty identity = persistentEntity.getIdentity();
List ids = new ArrayList<>();
try (ResultSet generatedKeys = ps.getGeneratedKeys()) {
Dialect dialect = storedQuery.getDialect();
while (generatedKeys.next()) {
ids.add(getGeneratedIdentity(generatedKeys, identity, dialect));
}
}
Iterator iterator = ids.iterator();
for (Data d : entities) {
if (d.vetoed) {
continue;
}
if (!iterator.hasNext()) {
throw new DataAccessException("Failed to generate ID for entity: " + d.entity);
} else {
Object id = iterator.next();
d.entity = updateEntityId(identity.getProperty(), d.entity, id);
}
}
}
if (storedQuery.isOptimisticLock()) {
int expected = (int) entities.stream().filter(d -> !d.vetoed).count();
checkOptimisticLocking(expected, rowsUpdated);
}
}
}
}
@SuppressWarnings("VisibilityModifier")
protected static class JdbcOperationContext extends OperationContext {
public final Connection connection;
public final Dialect dialect;
private final InvocationContext, ?> invocationContext;
/**
* The old deprecated constructor.
*
* @param annotationMetadata the annotation metadata
* @param repositoryType the repository type
* @param dialect the dialect
* @param connection the connection
* @deprecated Use constructor with {@link InvocationContext}.
*/
@Deprecated
public JdbcOperationContext(AnnotationMetadata annotationMetadata, Class> repositoryType, Dialect dialect, Connection connection) {
this(annotationMetadata, null , repositoryType, dialect, connection);
}
/**
* The default constructor.
*
* @param annotationMetadata the annotation metadata
* @param invocationContext the invocation context
* @param repositoryType the repository type
* @param dialect the dialect
* @param connection the connection
*/
public JdbcOperationContext(AnnotationMetadata annotationMetadata, InvocationContext, ?> invocationContext, Class> repositoryType, Dialect dialect, Connection connection) {
super(annotationMetadata, repositoryType);
this.dialect = dialect;
this.connection = connection;
this.invocationContext = invocationContext;
}
}
private static final class RuntimePersistentPropertyJdbcCC extends JdbcConversionContextImpl implements RuntimePersistentPropertyConversionContext {
private final RuntimePersistentProperty> property;
public RuntimePersistentPropertyJdbcCC(Connection connection, RuntimePersistentProperty> property) {
super(ConversionContext.of(property.getArgument()), connection);
this.property = property;
}
@Override
public RuntimePersistentProperty> getRuntimePersistentProperty() {
return property;
}
}
private static final class ArgumentJdbcCC extends JdbcConversionContextImpl implements ArgumentConversionContext {
private final Argument argument;
public ArgumentJdbcCC(Connection connection, Argument argument) {
super(ConversionContext.of(argument), connection);
this.argument = argument;
}
@Override
public Argument getArgument() {
return argument;
}
}
private static class JdbcConversionContextImpl extends AbstractConversionContext
implements JdbcConversionContext {
private final Connection connection;
public JdbcConversionContextImpl(Connection connection) {
this(ConversionContext.DEFAULT, connection);
}
public JdbcConversionContextImpl(ConversionContext conversionContext, Connection connection) {
super(conversionContext);
this.connection = connection;
}
@Override
public Connection getConnection() {
return connection;
}
}
private static final class ConnectionContext {
private final Connection connection;
private final boolean needsToBeClosed;
private ConnectionContext(Connection connection, boolean needsToBeClosed) {
this.connection = connection;
this.needsToBeClosed = needsToBeClosed;
}
public Connection getConnection() {
return connection;
}
}
}