io.ebeaninternal.server.core.DefaultServer Maven / Gradle / Ivy
package io.ebeaninternal.server.core;
import io.avaje.lang.NonNullApi;
import io.avaje.lang.Nullable;
import io.ebean.*;
import io.ebean.annotation.Platform;
import io.ebean.annotation.TxIsolation;
import io.ebean.bean.*;
import io.ebean.bean.PersistenceContext.WithOption;
import io.ebean.cache.ServerCacheManager;
import io.ebean.common.CopyOnFirstWriteList;
import io.ebean.config.*;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.event.BeanPersistController;
import io.ebean.event.ShutdownManager;
import io.ebean.meta.*;
import io.ebean.migration.auto.AutoMigrationRunner;
import io.ebean.plugin.BeanType;
import io.ebean.plugin.Plugin;
import io.ebean.plugin.Property;
import io.ebean.plugin.SpiServer;
import io.ebean.text.json.JsonContext;
import io.ebeaninternal.api.*;
import io.ebeaninternal.api.SpiQuery.Type;
import io.ebeaninternal.server.autotune.AutoTuneService;
import io.ebeaninternal.server.cache.RemoteCacheEvent;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.dto.DtoBeanDescriptor;
import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.el.ElFilter;
import io.ebeaninternal.server.query.*;
import io.ebeaninternal.server.querydefn.*;
import io.ebeaninternal.server.rawsql.SpiRawSql;
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import io.ebeaninternal.server.transaction.RemoteTransactionEvent;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.util.ParamTypeHelper;
import io.ebeaninternal.util.ParamTypeHelper.TypeInfo;
import jakarta.persistence.EntityNotFoundException;
import jakarta.persistence.NonUniqueResultException;
import jakarta.persistence.OptimisticLockException;
import jakarta.persistence.PersistenceException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.time.Clock;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import static java.lang.System.Logger.Level.*;
import static java.util.Spliterators.spliteratorUnknownSize;
import static java.util.stream.StreamSupport.stream;
/**
* The default server side implementation of EbeanServer.
*/
@NonNullApi
public final class DefaultServer implements SpiServer, SpiEbeanServer {
private static final System.Logger log = CoreLog.internal;
private final ReentrantLock lock = new ReentrantLock();
private final DatabaseBuilder.Settings config;
private final String serverName;
private final DatabasePlatform databasePlatform;
private final TransactionManager transactionManager;
private final QueryPlanManager queryPlanManager;
private final ExtraMetrics extraMetrics;
private final DataTimeZone dataTimeZone;
private final ClockService clockService;
private final CallOriginFactory callStackFactory;
private final Persister persister;
private final OrmQueryEngine queryEngine;
private final RelationalQueryEngine relationalQueryEngine;
private final DtoQueryEngine dtoQueryEngine;
private final ServerCacheManager serverCacheManager;
private final DtoBeanManager dtoBeanManager;
private final BeanDescriptorManager descriptorManager;
private final AutoTuneService autoTuneService;
private final CQueryEngine cqueryEngine;
private final List serverPlugins;
private final SpiDdlGenerator ddlGenerator;
private final ScriptRunner scriptRunner;
private final ExpressionFactory expressionFactory;
private final SpiBackgroundExecutor backgroundExecutor;
private final DefaultBeanLoader beanLoader;
private final EncryptKeyManager encryptKeyManager;
private final SpiJsonContext jsonContext;
private final MetaInfoManager metaInfoManager;
private final CurrentTenantProvider currentTenantProvider;
private final SpiLogManager logManager;
private final PersistenceContextScope defaultPersistenceContextScope;
private final int lazyLoadBatchSize;
private final boolean updateAllPropertiesInBatch;
private final long slowQueryMicros;
private final SlowQueryListener slowQueryListener;
private final boolean disableL2Cache;
private boolean shutdown;
/**
* Create the DefaultServer.
*/
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
this.logManager = config.getLogManager();
this.dtoBeanManager = config.getDtoBeanManager();
this.config = config.getConfig();
this.disableL2Cache = this.config.isDisableL2Cache();
this.serverCacheManager = cache;
this.databasePlatform = config.getDatabasePlatform();
this.backgroundExecutor = config.getBackgroundExecutor();
this.extraMetrics = config.getExtraMetrics();
this.serverName = this.config.getName();
this.lazyLoadBatchSize = this.config.getLazyLoadBatchSize();
this.cqueryEngine = config.getCQueryEngine();
this.expressionFactory = config.getExpressionFactory();
this.encryptKeyManager = this.config.getEncryptKeyManager();
this.defaultPersistenceContextScope = this.config.getPersistenceContextScope();
this.currentTenantProvider = this.config.getCurrentTenantProvider();
this.slowQueryMicros = config.getSlowQueryMicros();
this.slowQueryListener = config.getSlowQueryListener();
this.descriptorManager = config.getBeanDescriptorManager();
descriptorManager.setEbeanServer(this);
this.updateAllPropertiesInBatch = this.config.isUpdateAllPropertiesInBatch();
this.callStackFactory = initCallStackFactory(this.config);
this.persister = config.createPersister(this);
this.queryEngine = config.createOrmQueryEngine();
this.relationalQueryEngine = config.createRelationalQueryEngine();
this.dtoQueryEngine = config.createDtoQueryEngine();
this.autoTuneService = config.createAutoTuneService(this);
this.beanLoader = new DefaultBeanLoader(this);
this.jsonContext = config.createJsonContext(this);
this.dataTimeZone = config.getDataTimeZone();
this.clockService = config.getClockService();
this.transactionManager = config.createTransactionManager(this);
this.queryPlanManager = config.initQueryPlanManager(transactionManager);
this.metaInfoManager = new DefaultMetaInfoManager(this, this.config.getMetricNaming());
this.serverPlugins = config.getPlugins();
this.ddlGenerator = config.initDdlGenerator(this);
this.scriptRunner = new DScriptRunner(this);
configureServerPlugins();
// Register with the JVM Shutdown hook
ShutdownManager.registerDatabase(this);
}
/**
* Create the CallStackFactory depending if AutoTune is being used.
*/
private CallOriginFactory initCallStackFactory(DatabaseBuilder.Settings config) {
if (!config.getAutoTuneConfig().isActive()) {
// use a common CallStack for performance as we don't care with no AutoTune
return new NoopCallOriginFactory();
}
return new DefaultCallOriginFactory(config.getMaxCallStack());
}
private void configureServerPlugins() {
autoTuneService.startup();
for (Plugin plugin : serverPlugins) {
plugin.configure(this);
}
}
/**
* Execute all the plugins with an online flag indicating the DB is up or not.
*/
public void executePlugins(boolean online) {
ddlGenerator.execute(online);
for (Plugin plugin : serverPlugins) {
plugin.online(online);
}
}
@Override
public boolean isDisableL2Cache() {
return disableL2Cache;
}
@Override
public SpiLogManager log() {
return logManager;
}
@Override
public boolean isUpdateAllPropertiesInBatch() {
return updateAllPropertiesInBatch;
}
@Override
public int lazyLoadBatchSize() {
return lazyLoadBatchSize;
}
@Nullable
@Override
public Object currentTenantId() {
return currentTenantProvider == null ? null : currentTenantProvider.currentId();
}
@Override
public DatabaseBuilder.Settings config() {
return config;
}
@Override
public DatabasePlatform databasePlatform() {
return databasePlatform;
}
@Override
public ScriptRunner script() {
return scriptRunner;
}
@Override
public DataTimeZone dataTimeZone() {
return dataTimeZone;
}
@Override
public MetaInfoManager metaInfo() {
return metaInfoManager;
}
@Override
public Platform platform() {
return databasePlatform.platform();
}
@Override
public SpiServer pluginApi() {
return this;
}
@Override
public BackgroundExecutor backgroundExecutor() {
return backgroundExecutor;
}
@Override
public ExpressionFactory expressionFactory() {
return expressionFactory;
}
@Override
public AutoTune autoTune() {
return autoTuneService;
}
@Override
public DataSource dataSource() {
return transactionManager.dataSource();
}
@Override
public DataSource readOnlyDataSource() {
return transactionManager.readOnlyDataSource();
}
/**
* Run any initialisation required before registering with the ClusterManager.
*/
public void initialise() {
if (encryptKeyManager != null) {
encryptKeyManager.initialise();
}
serverCacheManager.enabledRegions(config.getEnabledL2Regions());
}
/**
* Start any services after registering with the ClusterManager.
*/
public void start() {
if (config.isRunMigration() && TenantMode.DB != config.getTenantMode()) {
AutoMigrationRunner migrationRunner = config.getServiceObject(AutoMigrationRunner.class);
if (migrationRunner == null) {
migrationRunner = ServiceUtil.service(AutoMigrationRunner.class);
}
if (migrationRunner == null) {
throw new IllegalStateException("No AutoMigrationRunner found. Probably ebean-migration is not in the classpath?");
}
final String dbSchema = config.getDbSchema();
if (dbSchema != null) {
migrationRunner.setDefaultDbSchema(dbSchema);
}
migrationRunner.setName(config.getName());
Platform platform = config.getDatabasePlatform().platform();
migrationRunner.setBasePlatform(platform.base().name().toLowerCase());
migrationRunner.setPlatform(platform.name().toLowerCase());
migrationRunner.loadProperties(config.getProperties());
migrationRunner.run(config.getDataSource());
}
startQueryPlanCapture();
}
private void startQueryPlanCapture() {
if (config.isQueryPlanCapture()) {
long secs = config.getQueryPlanCapturePeriodSecs();
if (secs > 10) {
log.log(INFO, "capture query plan enabled, every {0}secs", secs);
backgroundExecutor.scheduleWithFixedDelay(this::collectQueryPlans, secs, secs, TimeUnit.SECONDS);
}
}
}
private void collectQueryPlans() {
QueryPlanRequest request = new QueryPlanRequest();
request.maxCount(config.getQueryPlanCaptureMaxCount());
request.maxTimeMillis(config.getQueryPlanCaptureMaxTimeMillis());
// obtains query explain plans ...
List plans = metaInfoManager.queryPlanCollectNow(request);
QueryPlanListener listener = config.getQueryPlanListener();
if (listener == null) {
listener = DefaultQueryPlanListener.INSTANT;
}
listener.process(new QueryPlanCapture(this, plans));
}
@Override
public void shutdown() {
shutdown(true, false);
}
@Override
public void shutdown(boolean shutdownDataSource, boolean deregisterDriver) {
lock.lock();
try {
ShutdownManager.unregisterDatabase(this);
log.log(TRACE, "shutting down instance {0}", serverName);
if (shutdown) {
// already shutdown
return;
}
shutdownPlugins();
autoTuneService.shutdown();
// shutdown background threads
backgroundExecutor.shutdown();
// shutdown DataSource (if its an Ebean one)
transactionManager.shutdown(shutdownDataSource, deregisterDriver);
dumpMetrics();
shutdown = true;
if (shutdownDataSource) {
config.setDataSource(null);
}
} finally {
lock.unlock();
}
}
private void dumpMetrics() {
if (config.isDumpMetricsOnShutdown()) {
new DumpMetrics(this, config.getDumpMetricsOptions()).dump();
}
}
private void shutdownPlugins() {
for (Plugin plugin : serverPlugins) {
try {
plugin.shutdown();
} catch (Exception e) {
log.log(ERROR, "Error when shutting down plugin", e);
}
}
}
@Override
public String toString() {
return "Database{" + serverName + "}";
}
/**
* Return the server name.
*/
@Override
public String name() {
return serverName;
}
@Override
public ExtendedServer extended() {
return this;
}
@Override
public long clockNow() {
return clockService.nowMillis();
}
@Override
public void setClock(Clock clock) {
this.clockService.setClock(clock);
}
@Override
public BeanState beanState(Object bean) {
if (bean instanceof EntityBean) {
return new DefaultBeanState((EntityBean) bean);
}
throw new IllegalArgumentException("Bean is not an entity bean");
}
/**
* Compile a query. Only valid for ORM queries.
*/
@Override
public CQuery compileQuery(Type type, SpiQuery query, Transaction transaction) {
query.usingTransaction(transaction);
SpiOrmQueryRequest qr = createQueryRequest(type, query);
OrmQueryRequest orm = (OrmQueryRequest) qr;
return cqueryEngine.buildQuery(orm);
}
@Override
public ServerCacheManager cacheManager() {
return serverCacheManager;
}
@Override
public void refreshMany(Object parentBean, String propertyName) {
beanLoader.refreshMany(checkEntityBean(parentBean), propertyName);
}
@Override
public void loadMany(LoadManyRequest loadRequest) {
beanLoader.loadMany(loadRequest);
}
@Override
public void loadMany(BeanCollection> bc, boolean onlyIds) {
beanLoader.loadMany(bc, onlyIds);
}
@Override
public void refresh(Object bean) {
beanLoader.refresh(checkEntityBean(bean));
}
@Override
public void loadBean(LoadBeanRequest loadRequest) {
beanLoader.loadBean(loadRequest);
}
@Override
public BeanLoader beanLoader() {
return new SingleBeanLoader.Dflt(this);
}
@Override
public void loadBean(EntityBeanIntercept ebi) {
beanLoader.loadBean(ebi);
extraMetrics.incrementLoadOneNoLoader();
}
@Override
public void loadBeanRef(EntityBeanIntercept ebi) {
beanLoader.loadBean(ebi);
extraMetrics.incrementLoadOneRef();
}
@Override
public void loadBeanL2(EntityBeanIntercept ebi) {
beanLoader.loadBean(ebi);
extraMetrics.incrementLoadOneL2();
}
@Override
public Map diff(@Nullable Object a, Object b) {
if (a == null) {
return Collections.emptyMap();
}
BeanDescriptor> desc = descriptor(a.getClass());
return DiffHelp.diff(a, b, desc);
}
/**
* Process committed beans from another framework or server in another
* cluster.
*
* This notifies this instance of the framework that beans have been committed
* externally to it. Either by another framework or clustered server. It needs
* to maintain its cache and text indexes appropriately.
*
*/
@Override
public void externalModification(TransactionEventTable tableEvent) {
transactionManager.externalModification(tableEvent);
}
/**
* Developer informing eBean that tables where modified outside of eBean.
* Invalidate the cache etc as required.
*/
@Override
public void externalModification(String tableName, boolean inserts, boolean updates, boolean deletes) {
TransactionEventTable evt = new TransactionEventTable();
evt.add(tableName, inserts, updates, deletes);
externalModification(evt);
}
@Override
public void truncate(Class>... types) {
List tableNames = new ArrayList<>();
for (Class> type : types) {
tableNames.add(descriptor(type).baseTable());
}
truncate(tableNames.toArray(new String[0]));
}
@Override
public void truncate(String... tables) {
try (Connection connection = dataSource().getConnection()) {
for (String table : tables) {
executeSql(connection, databasePlatform.truncateStatement(table));
if (databasePlatform.platform().base() == Platform.DB2) {
// DB2 requires commit after each truncate statement
connection.commit();
}
}
connection.commit();
} catch (SQLException e) {
throw new PersistenceException("Error executing truncate", e);
}
}
private void executeSql(Connection connection, @Nullable String sql) throws SQLException {
if (sql != null) {
try (Statement stmt = connection.createStatement()) {
transactionManager.log().sql().debug(sql);
stmt.execute(sql);
}
}
}
/**
* Clear the query execution statistics.
*/
@Override
public void clearQueryStatistics() {
for (BeanDescriptor> desc : descriptors()) {
desc.clearQueryStatistics();
}
}
/**
* Create a new EntityBean bean.
*
* This will generally return a subclass of the parameter 'type' which
* additionally implements the EntityBean interface. That is, the returned
* bean is typically an instance of a dynamically generated class.
*
*/
@Override
public T createEntityBean(Class type) {
final BeanDescriptor desc = descriptor(type);
if (desc == null) {
throw new IllegalArgumentException("No bean type " + type.getName() + " registered");
}
return desc.createBean();
}
/**
* Return a Reference bean.
*
* If a current transaction is active then this will check the Context of that
* transaction to see if the bean is already loaded. If it is already loaded
* then it will returned that object.
*
*/
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public T reference(Class type, Object id) {
Objects.requireNonNull(id);
BeanDescriptor desc = descriptor(type);
id = desc.convertId(id);
PersistenceContext pc = null;
SpiTransaction t = transactionManager.active();
if (t != null) {
pc = t.persistenceContext();
Object existing = desc.contextGet(pc, id);
if (existing != null) {
return (T) existing;
}
}
return (T) desc.contextRef(pc, null, false, id);
}
@Override
public void register(TransactionCallback transactionCallback) {
Transaction transaction = transactionManager.active();
if (transaction == null) {
throw new PersistenceException("Not currently active transaction when trying to register transactionCallback");
}
transaction.register(transactionCallback);
}
/**
* Creates a new Transaction that is NOT stored in TransactionThreadLocal. Use
* this when you want a thread to have a second independent transaction.
*/
@Override
public Transaction createTransaction() {
return transactionManager.createTransaction(true, -1);
}
/**
* Create a transaction additionally specify the Isolation level.
*
* Note that this transaction is not stored in a thread local.
*
*/
@Override
public Transaction createTransaction(TxIsolation isolation) {
return transactionManager.createTransaction(true, isolation.getLevel());
}
@Override
public T executeCall(Callable callable) {
return executeCall(null, callable);
}
@Override
public T executeCall(@Nullable TxScope scope, Callable callable) {
ScopedTransaction scopeTrans = transactionManager.beginScopedTransaction(scope);
try {
return callable.call();
} catch (Error e) {
throw scopeTrans.caughtError(e);
} catch (Exception e) {
throw new PersistenceException(scopeTrans.caughtThrowable(e));
} finally {
scopeTrans.complete();
}
}
@Override
public void execute(Runnable runnable) {
execute(null, runnable);
}
@Override
public void execute(@Nullable TxScope scope, Runnable runnable) {
ScopedTransaction t = transactionManager.beginScopedTransaction(scope);
try {
runnable.run();
} catch (Error e) {
throw t.caughtError(e);
} catch (Exception e) {
throw new PersistenceException(t.caughtThrowable(e));
} finally {
t.complete();
}
}
@Override
public void scopedTransactionEnter(TxScope txScope) {
beginTransaction(txScope);
}
@Override
public void scopedTransactionExit(Object returnOrThrowable, int opCode) {
transactionManager.exitScopedTransaction(returnOrThrowable, opCode);
}
@Nullable
@Override
public SpiTransaction currentServerTransaction() {
return transactionManager.active();
}
@Override
public Transaction beginTransaction() {
return beginTransaction(TxScope.required());
}
@Override
public Transaction beginTransaction(TxScope txScope) {
return transactionManager.beginScopedTransaction(txScope);
}
@Override
public Transaction beginTransaction(TxIsolation isolation) {
// start an explicit transaction
SpiTransaction t = transactionManager.createTransaction(true, isolation.getLevel());
try {
// note that we are not supporting nested scoped transactions in this case
transactionManager.set(t);
} catch (PersistenceException existingTransactionError) {
t.end();
throw existingTransactionError;
}
return t;
}
@Override
public Transaction currentTransaction() {
return transactionManager.active();
}
@Override
public void flush() {
currentTransaction().flush();
}
@Override
public void endTransaction() {
Transaction transaction = transactionManager.inScope();
if (transaction != null) {
transaction.end();
}
}
@Override
public Object nextId(Class> beanType) {
return descriptor(beanType).nextId(null);
}
@Override
@SuppressWarnings("unchecked")
public void sort(List list, String sortByClause) {
Objects.requireNonNull(list);
Objects.requireNonNull(sortByClause);
if (list.isEmpty()) {
// don't need to sort an empty list
return;
}
// use first bean in the list as the correct type
Class beanType = (Class) list.get(0).getClass();
desc(beanType).sort(list, sortByClause);
}
@Override
public Set validateQuery(Query query) {
return ((SpiQuery) query).validate(desc(query.getBeanType()));
}
@Override
public Filter filter(Class beanType) {
return new ElFilter<>(desc(beanType));
}
@Override
public UpdateQuery update(Class beanType) {
return new DefaultUpdateQuery<>(createQuery(beanType));
}
@Override
public void merge(Object bean) {
merge(bean, MergeOptionsBuilder.defaultOptions(), null);
}
@Override
public void merge(Object bean, MergeOptions options) {
merge(bean, options, null);
}
@Override
public void merge(Object bean, MergeOptions options, @Nullable Transaction transaction) {
BeanDescriptor> desc = desc(bean.getClass());
executeInTrans((txn) -> persister.merge(desc, checkEntityBean(bean), options, txn), transaction);
}
@Override
public void lock(Object bean) {
BeanDescriptor> desc = desc(bean.getClass());
Object id = desc.id(bean);
Objects.requireNonNull(id, "Bean missing an @Id value which is required to lock");
new DefaultOrmQuery<>(desc, this, expressionFactory)
.setId(id)
.withLock(Query.LockType.DEFAULT, Query.LockWait.NOWAIT)
.findOne();
}
@Override
public Query find(Class beanType) {
return createQuery(beanType);
}
@Override
public Query findNative(Class beanType, String nativeSql) {
DefaultOrmQuery query = new DefaultOrmQuery<>(desc(beanType), this, expressionFactory);
query.setNativeSql(nativeSql);
return query;
}
@Override
public DefaultOrmQuery createQuery(Class beanType) {
return new DefaultOrmQuery<>(desc(beanType), this, expressionFactory);
}
@Override
public Update createUpdate(Class beanType, String ormUpdate) {
return new DefaultOrmUpdate<>(beanType, this, desc(beanType).baseTable(), ormUpdate);
}
@Override
public DtoQuery findDto(Class dtoType, String sql) {
DtoBeanDescriptor descriptor = dtoBeanManager.descriptor(dtoType);
return new DefaultDtoQuery<>(this, descriptor, sql.trim());
}
@Override
public DtoQuery findDto(Class dtoType, SpiQuery> ormQuery) {
DtoBeanDescriptor descriptor = dtoBeanManager.descriptor(dtoType);
return new DefaultDtoQuery<>(this, descriptor, ormQuery);
}
@Override
public SpiResultSet findResultSet(SpiQuery> ormQuery) {
SpiOrmQueryRequest> request = createQueryRequest(ormQuery.type(), ormQuery);
request.initTransIfRequired();
return request.findResultSet();
}
@Override
public SqlQuery sqlQuery(String sql) {
return new DefaultRelationalQuery(this, sql.trim());
}
@Override
public SqlUpdate sqlUpdate(String sql) {
return new DefaultSqlUpdate(this, sql.trim());
}
@Override
public CallableSql createCallableSql(String sql) {
return new DefaultCallableSql(this, sql.trim());
}
@Override
public T find(Class beanType, Object uid) {
return find(beanType, uid, null);
}
/**
* Find a bean using its unique id.
*/
@Override
public T find(Class beanType, Object id, @Nullable Transaction transaction) {
Objects.requireNonNull(id);
SpiQuery query = createQuery(beanType);
query.usingTransaction(transaction);
query.setId(id);
return findId(query);
}
SpiOrmQueryRequest createQueryRequest(Type type, SpiQuery query) {
SpiOrmQueryRequest request = buildQueryRequest(type, query);
request.prepareQuery();
return request;
}
SpiOrmQueryRequest buildQueryRequest(Type type, SpiQuery query) {
query.setType(type);
query.checkNamedParameters();
return buildQueryRequest(query);
}
private SpiOrmQueryRequest buildQueryRequest(SpiQuery query) {
SpiTransaction transaction = query.transaction();
if (transaction == null) {
transaction = currentServerTransaction();
}
if (!query.isRawSql()) {
if (!query.isAutoTunable() || !autoTuneService.tuneQuery(query)) {
// use deployment FetchType.LAZY/EAGER annotations
// to define the 'default' select clause
query.setDefaultSelectClause();
}
query.selectAllForLazyLoadProperty();
}
ProfileLocation profileLocation = query.profileLocation();
if (profileLocation != null) {
profileLocation.obtain();
}
// if determine cost and no origin for AutoTune
if (query.parentNode() == null) {
query.setOrigin(createCallOrigin());
}
return new OrmQueryRequest<>(this, queryEngine, query, transaction);
}
/**
* Try to get the object out of the persistence context.
*/
@Nullable
@SuppressWarnings("unchecked")
private T findIdCheckPersistenceContextAndCache(SpiQuery query, Object id) {
SpiTransaction t = query.transaction();
if (t == null) {
t = currentServerTransaction();
}
BeanDescriptor desc = query.descriptor();
id = desc.convertId(id);
PersistenceContext pc = null;
if (t != null && useTransactionPersistenceContext(query)) {
// first look in the transaction scoped persistence context
pc = t.persistenceContext();
if (pc != null) {
WithOption o = desc.contextGetWithOption(pc, id);
if (o != null) {
if (o.isDeleted()) {
// Bean was previously deleted in the same transaction / persistence context
return null;
}
return (T) o.getBean();
}
}
}
if (!query.isBeanCacheGet() || (t != null && t.isSkipCache())) {
return null;
}
// Hit the L2 bean cache
return desc.cacheBeanGet(id, query.isReadOnly(), pc);
}
/**
* Return true if transactions PersistenceContext should be used.
*/
private boolean useTransactionPersistenceContext(SpiQuery query) {
return PersistenceContextScope.TRANSACTION == persistenceContextScope(query);
}
/**
* Return the PersistenceContextScope to use defined at query or server level.
*/
@Override
public PersistenceContextScope persistenceContextScope(SpiQuery> query) {
PersistenceContextScope scope = query.persistenceContextScope();
return (scope != null) ? scope : defaultPersistenceContextScope;
}
@Nullable
@SuppressWarnings("unchecked")
private T findId(SpiQuery query) {
query.setType(Type.BEAN);
if (SpiQuery.Mode.NORMAL == query.mode() && !query.isForceHitDatabase()) {
// See if we can skip doing the fetch completely by getting the bean from the
// persistence context or the bean cache
T bean = findIdCheckPersistenceContextAndCache(query, query.getId());
if (bean != null) {
return bean;
}
}
SpiOrmQueryRequest request = buildQueryRequest(query);
request.prepareQuery();
try {
request.initTransIfRequired();
return (T) request.findId();
} finally {
request.endTransIfRequired();
}
}
@Override
public Optional findOneOrEmpty(SpiQuery query) {
return Optional.ofNullable(findOne(query));
}
@Nullable
@Override
public T findOne(SpiQuery query) {
if (query.isFindById()) {
// actually a find by Id query
return findId(query);
}
// a query that is expected to return either 0 or 1 beans
List list = findList(query, true);
return extractUnique(list);
}
@Nullable
private T extractUnique(List list) {
if (list.isEmpty()) {
return null;
} else if (list.size() > 1) {
throw new NonUniqueResultException("Unique expecting 0 or 1 results but got " + list.size());
} else {
return list.get(0);
}
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Set findSet(SpiQuery query) {
SpiOrmQueryRequest request = buildQueryRequest(Type.SET, query);
request.resetBeanCacheAutoMode(false);
if (request.isGetAllFromBeanCache()) {
// hit bean cache and got all results from cache
return request.beanCacheHitsAsSet();
}
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
return (Set) result;
}
try {
request.initTransIfRequired();
return request.findSet();
} finally {
request.endTransIfRequired();
}
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Map findMap(SpiQuery query) {
SpiOrmQueryRequest request = buildQueryRequest(Type.MAP, query);
request.resetBeanCacheAutoMode(false);
if (request.isGetAllFromBeanCache()) {
// hit bean cache and got all results from cache
return request.beanCacheHitsAsMap();
}
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
return (Map) result;
}
try {
request.initTransIfRequired();
return request.findMap();
} finally {
request.endTransIfRequired();
}
}
@Override
@SuppressWarnings("unchecked")
public List findSingleAttributeList(SpiQuery query) {
SpiOrmQueryRequest request = buildQueryRequest(Type.ATTRIBUTE, query);
request.query().setSingleAttribute();
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
return (List) result;
}
try {
request.initTransIfRequired();
return request.findSingleAttributeCollection(new ArrayList<>());
} finally {
request.endTransIfRequired();
}
}
@Override
@SuppressWarnings("unchecked")
public Set findSingleAttributeSet(SpiQuery query) {
SpiOrmQueryRequest request = buildQueryRequest(Type.ATTRIBUTE_SET, query);
request.query().setSingleAttribute();
request.prepareQuery();
Object result = request.getFromQueryCache();
if (result != null) {
return (Set) result;
}
try {
request.initTransIfRequired();
return request.findSingleAttributeCollection(new LinkedHashSet<>());
} finally {
request.endTransIfRequired();
}
}
@Override
public int findCount(SpiQuery query) {
if (!query.isDistinct()) {
query = query.copy();
}
return findCountWithCopy(query);
}
@Override
public int findCountWithCopy(SpiQuery query) {
SpiOrmQueryRequest request = createQueryRequest(Type.COUNT, query);
Integer result = request.getFromQueryCache();
if (result != null) {
return result;
}
try {
request.initTransIfRequired();
return request.findCount();
} finally {
request.endTransIfRequired();
}
}
@Override
public boolean exists(Class> beanType, Object beanId, Transaction transaction) {
final DefaultOrmQuery> query = createQuery(beanType);
query.usingTransaction(transaction);
query.setId(beanId);
return !findIdsWithCopy(query).isEmpty();
}
@Override
public boolean exists(SpiQuery ormQuery) {
SpiQuery ormQueryCopy = ormQuery.copy();
ormQueryCopy.setMaxRows(1);
SpiOrmQueryRequest> request = createQueryRequest(Type.EXISTS, ormQueryCopy);
List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy