Compare commits

..
20 changed files with 101 additions and 97 deletions
@@ -3,13 +3,24 @@ package io.ebean.meta;
/** /**
* Request used to capture query plans. * Request used to capture query plans.
*/ */
public class QueryPlanRequest { public final class QueryPlanRequest {
private long since; private long since;
private int maxCount; private final int maxCount;
private long maxTimeMillis; private final long maxTimeMillis;
/**
* Create with the max number of plans and max capture time.
*
* @param maxCount The maximum number of plans to capture
* @param maxTimeMillis The maximum time after which we stop capturing more plans
*/
public QueryPlanRequest(int maxCount, long maxTimeMillis) {
this.maxCount = maxCount;
this.maxTimeMillis = maxTimeMillis;
}
/** /**
* Return the epoch time in millis for minimum bind capture time. * Return the epoch time in millis for minimum bind capture time.
@@ -39,16 +50,6 @@ public class QueryPlanRequest {
return maxCount; return maxCount;
} }
/**
* Set the maximum number of plans to capture.
* <p>
* Use this to limit how much query plan capturing is done as query
* plan capture is actual database load.
*/
public void maxCount(int maxCount) {
this.maxCount = maxCount;
}
/** /**
* Return the maximum amount of time we want to use to capture plans. * Return the maximum amount of time we want to use to capture plans.
* <p> * <p>
@@ -57,15 +58,4 @@ public class QueryPlanRequest {
public long maxTimeMillis() { public long maxTimeMillis() {
return maxTimeMillis; return maxTimeMillis;
} }
/**
* Set the maximum amount of time we want to use to capture plans.
* <p>
* Query plan collection will stop once this time is exceeded. We use
* this to ensure the query plan capture does not use excessive amount
* of time - put too much load on the database.
*/
public void maxTimeMillis(long maxTimeMillis) {
this.maxTimeMillis = maxTimeMillis;
}
} }
@@ -8,6 +8,11 @@ import java.util.List;
final class NoopQueryPlanManager implements QueryPlanManager { final class NoopQueryPlanManager implements QueryPlanManager {
@Override
public void startPlanCapture() {
// do nothing
}
@Override @Override
public void setDefaultThreshold(long thresholdMicros) { public void setDefaultThreshold(long thresholdMicros) {
// do nothing // do nothing
@@ -12,6 +12,11 @@ public interface QueryPlanManager {
QueryPlanManager NOOP = new NoopQueryPlanManager(); QueryPlanManager NOOP = new NoopQueryPlanManager();
/**
* Start background capture of query plans if enabled.
*/
void startPlanCapture();
/** /**
* Update the global default threshold used when new query plans are created. * Update the global default threshold used when new query plans are created.
*/ */
@@ -1,6 +1,7 @@
package io.ebeaninternal.server.core; package io.ebeaninternal.server.core;
import io.ebean.meta.*; import io.ebean.meta.*;
import io.ebeaninternal.api.QueryPlanManager;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
@@ -11,11 +12,13 @@ import java.util.function.Function;
final class DefaultMetaInfoManager implements MetaInfoManager { final class DefaultMetaInfoManager implements MetaInfoManager {
private final DefaultServer server; private final DefaultServer server;
private final QueryPlanManager queryPlanManager;
private final Function<String, String> naming; private final Function<String, String> naming;
DefaultMetaInfoManager(DefaultServer server, Function<String, String> naming) { DefaultMetaInfoManager(DefaultServer server, QueryPlanManager queryPlanManager) {
this.server = server; this.server = server;
this.naming = naming; this.naming = server.config().getMetricNaming();
this.queryPlanManager = queryPlanManager;
} }
@Override @Override
@@ -25,7 +28,7 @@ final class DefaultMetaInfoManager implements MetaInfoManager {
@Override @Override
public List<MetaQueryPlan> queryPlanCollectNow(QueryPlanRequest request) { public List<MetaQueryPlan> queryPlanCollectNow(QueryPlanRequest request) {
return server.queryPlanCollectNow(request); return queryPlanManager.collect(request);
} }
@Override @Override
@@ -56,7 +56,6 @@ import java.sql.Statement;
import java.time.Clock; import java.time.Clock;
import java.util.*; import java.util.*;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@@ -105,7 +104,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final EncryptKeyManager encryptKeyManager; private final EncryptKeyManager encryptKeyManager;
private final SpiJsonContext jsonContext; private final SpiJsonContext jsonContext;
private final DocumentStore documentStore; private final DocumentStore documentStore;
private final MetaInfoManager metaInfoManager; private final DefaultMetaInfoManager metaInfoManager;
private final CurrentTenantProvider currentTenantProvider; private final CurrentTenantProvider currentTenantProvider;
private final SpiLogManager logManager; private final SpiLogManager logManager;
private final PersistenceContextScope defaultPersistenceContextScope; private final PersistenceContextScope defaultPersistenceContextScope;
@@ -156,8 +155,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this); DocStoreIntegration docStoreComponents = config.createDocStoreIntegration(this);
this.transactionManager = config.createTransactionManager(this, docStoreComponents.updateProcessor()); this.transactionManager = config.createTransactionManager(this, docStoreComponents.updateProcessor());
this.documentStore = docStoreComponents.documentStore(); this.documentStore = docStoreComponents.documentStore();
this.queryPlanManager = config.initQueryPlanManager(transactionManager); this.queryPlanManager = config.initQueryPlanManager(this, transactionManager);
this.metaInfoManager = new DefaultMetaInfoManager(this, this.config.getMetricNaming()); this.metaInfoManager = new DefaultMetaInfoManager(this, queryPlanManager);
this.serverPlugins = config.getPlugins(); this.serverPlugins = config.getPlugins();
this.ddlGenerator = config.initDdlGenerator(this); this.ddlGenerator = config.initDdlGenerator(this);
this.scriptRunner = new DScriptRunner(this); this.scriptRunner = new DScriptRunner(this);
@@ -326,31 +325,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
migrationRunner.loadProperties(config.getProperties()); migrationRunner.loadProperties(config.getProperties());
migrationRunner.run(config.getDataSource()); migrationRunner.run(config.getDataSource());
} }
startQueryPlanCapture(); queryPlanManager.startPlanCapture();
}
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<MetaQueryPlan> plans = metaInfoManager.queryPlanCollectNow(request);
QueryPlanListener listener = config.getQueryPlanListener();
if (listener == null) {
listener = DefaultQueryPlanListener.INSTANT;
}
listener.process(new QueryPlanCapture(this, plans));
} }
@Override @Override
@@ -575,12 +575,12 @@ public final class InternalConfiguration {
return new DefaultServerCacheManager(builder); return new DefaultServerCacheManager(builder);
} }
public QueryPlanManager initQueryPlanManager(TransactionManager transactionManager) { public QueryPlanManager initQueryPlanManager(DefaultServer server, TransactionManager transactionManager) {
if (!config.isQueryPlanEnable()) { if (!config.isQueryPlanEnable()) {
return QueryPlanManager.NOOP; return QueryPlanManager.NOOP;
} }
long threshold = config.getQueryPlanThresholdMicros(); long threshold = config.getQueryPlanThresholdMicros();
return new CQueryPlanManager(transactionManager, threshold, queryPlanLogger(databasePlatform.platform()), extraMetrics); return new CQueryPlanManager(server, transactionManager, threshold, queryPlanLogger(databasePlatform.platform()), extraMetrics);
} }
/** /**
@@ -1,22 +1,27 @@
package io.ebeaninternal.server.query; package io.ebeaninternal.server.query;
import io.ebean.config.QueryPlanCapture;
import io.ebean.config.QueryPlanListener;
import io.ebean.meta.MetaQueryPlan; import io.ebean.meta.MetaQueryPlan;
import io.ebean.meta.QueryPlanRequest; import io.ebean.meta.QueryPlanRequest;
import io.ebean.metric.TimedMetric; import io.ebean.metric.TimedMetric;
import io.ebeaninternal.api.*; import io.ebeaninternal.api.*;
import io.ebeaninternal.server.core.DefaultServer;
import io.ebeaninternal.server.transaction.TransactionManager; import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.bind.capture.BindCapture; import io.ebeaninternal.server.bind.capture.BindCapture;
import java.sql.Connection; import java.sql.Connection;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.*;
import static java.lang.System.Logger.Level.ERROR; import static java.lang.System.Logger.Level.ERROR;
import static java.lang.System.Logger.Level.INFO;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
public final class CQueryPlanManager implements QueryPlanManager { public final class CQueryPlanManager implements QueryPlanManager {
private static final System.Logger log = CoreLog.internal;
private static final Object dummy = new Object(); private static final Object dummy = new Object();
private final ConcurrentHashMap<CQueryBindCapture, Object> plans = new ConcurrentHashMap<>(); private final ConcurrentHashMap<CQueryBindCapture, Object> plans = new ConcurrentHashMap<>();
@@ -24,14 +29,25 @@ public final class CQueryPlanManager implements QueryPlanManager {
private final QueryPlanLogger planLogger; private final QueryPlanLogger planLogger;
private final TimedMetric timeCollection; private final TimedMetric timeCollection;
private final TimedMetric timeBindCapture; private final TimedMetric timeBindCapture;
private final DefaultServer server;
private final QueryPlanListener listener;
private final int maxCount;
private final long maxTimeMillis;
private long defaultThreshold; private long defaultThreshold;
public CQueryPlanManager(TransactionManager transactionManager, long defaultThreshold, QueryPlanLogger planLogger, ExtraMetrics extraMetrics) { public CQueryPlanManager(DefaultServer server, TransactionManager transactionManager, long defaultThreshold, QueryPlanLogger planLogger, ExtraMetrics extraMetrics) {
this.server = server;
this.transactionManager = transactionManager; this.transactionManager = transactionManager;
this.defaultThreshold = defaultThreshold; this.defaultThreshold = defaultThreshold;
this.planLogger = planLogger; this.planLogger = planLogger;
this.timeCollection = extraMetrics.planCollect(); this.timeCollection = extraMetrics.planCollect();
this.timeBindCapture = extraMetrics.bindCapture(); this.timeBindCapture = extraMetrics.bindCapture();
final var config = server.config();
this.maxCount = config.getQueryPlanCaptureMaxCount();
this.maxTimeMillis = config.getQueryPlanCaptureMaxTimeMillis();
final var planListener = config.getQueryPlanListener();
this.listener = (planListener != null) ? planListener : DefaultQueryPlanListener.INSTANT;
} }
@Override @Override
@@ -49,15 +65,28 @@ public final class CQueryPlanManager implements QueryPlanManager {
timeBindCapture.addSinceNanos(startNanos); timeBindCapture.addSinceNanos(startNanos);
} }
@Override
public void startPlanCapture() {
final var config = server.config();
if (config.isQueryPlanCapture()) {
long secs = config.getQueryPlanCapturePeriodSecs();
if (secs > 10) {
log.log(INFO, "capture query plan enabled, every {0}secs", secs);
server.backgroundExecutor().scheduleWithFixedDelay(this::collectQueryPlans, secs, secs, TimeUnit.SECONDS);
}
}
}
private void collectQueryPlans() {
List<MetaQueryPlan> plans = collect(new QueryPlanRequest(maxCount, maxTimeMillis));
listener.process(new QueryPlanCapture(server, plans));
}
@Override @Override
public List<MetaQueryPlan> collect(QueryPlanRequest request) { public List<MetaQueryPlan> collect(QueryPlanRequest request) {
if (plans.isEmpty()) { if (plans.isEmpty()) {
return emptyList(); return emptyList();
} }
return collectPlans(request);
}
private List<MetaQueryPlan> collectPlans(QueryPlanRequest request) {
try (Connection connection = transactionManager.queryPlanConnection()) { try (Connection connection = transactionManager.queryPlanConnection()) {
CQueryPlanRequest req = new CQueryPlanRequest(connection, request, plans.keySet().iterator()); CQueryPlanRequest req = new CQueryPlanRequest(connection, request, plans.keySet().iterator());
while (req.hasNext()) { while (req.hasNext()) {
@@ -1,4 +1,4 @@
package io.ebeaninternal.server.core; package io.ebeaninternal.server.query;
import io.avaje.applog.AppLog; import io.avaje.applog.AppLog;
import io.ebean.config.QueryPlanCapture; import io.ebean.config.QueryPlanCapture;
+1 -1
View File
@@ -86,7 +86,7 @@
<extensions>true</extensions> <extensions>true</extensions>
<configuration> <configuration>
<tiles> <tiles>
<tile>io.ebean.tile:enhancement:14.2.0</tile> <tile>io.ebean.tile:enhancement:14.1.0</tile>
</tiles> </tiles>
</configuration> </configuration>
</plugin> </plugin>
+1 -1
View File
@@ -100,7 +100,7 @@
<extensions>true</extensions> <extensions>true</extensions>
<configuration> <configuration>
<tiles> <tiles>
<tile>io.ebean.tile:enhancement:14.2.0</tile> <tile>io.ebean.tile:enhancement:14.1.0</tile>
</tiles> </tiles>
</configuration> </configuration>
</plugin> </plugin>
@@ -46,7 +46,7 @@ public abstract class TQAssocBean<T, R, QB> extends TQAssoc<T, R> {
* Eagerly fetch this association fetching all the properties. * Eagerly fetch this association fetching all the properties.
*/ */
public final R fetch() { public final R fetch() {
((QueryBean) _root).query().fetch(_name); ((TQRootBean) _root).query().fetch(_name);
return _root; return _root;
} }
@@ -54,7 +54,7 @@ public abstract class TQAssocBean<T, R, QB> extends TQAssoc<T, R> {
* Eagerly fetch this association using a "query join". * Eagerly fetch this association using a "query join".
*/ */
public final R fetchQuery() { public final R fetchQuery() {
((QueryBean) _root).query().fetchQuery(_name); ((TQRootBean) _root).query().fetchQuery(_name);
return _root; return _root;
} }
@@ -63,7 +63,7 @@ public abstract class TQAssocBean<T, R, QB> extends TQAssoc<T, R> {
* Cache misses are populated via fetchQuery(). * Cache misses are populated via fetchQuery().
*/ */
public final R fetchCache() { public final R fetchCache() {
((QueryBean) _root).query().fetchCache(_name); ((TQRootBean) _root).query().fetchCache(_name);
return _root; return _root;
} }
@@ -71,7 +71,7 @@ public abstract class TQAssocBean<T, R, QB> extends TQAssoc<T, R> {
* Use lazy loading for fetching this association. * Use lazy loading for fetching this association.
*/ */
public final R fetchLazy() { public final R fetchLazy() {
((QueryBean) _root).query().fetchLazy(_name); ((TQRootBean) _root).query().fetchLazy(_name);
return _root; return _root;
} }
@@ -79,7 +79,7 @@ public abstract class TQAssocBean<T, R, QB> extends TQAssoc<T, R> {
* Eagerly fetch this association with the properties specified. * Eagerly fetch this association with the properties specified.
*/ */
public final R fetch(String properties) { public final R fetch(String properties) {
((QueryBean) _root).query().fetch(_name, properties); ((TQRootBean) _root).query().fetch(_name, properties);
return _root; return _root;
} }
@@ -87,7 +87,7 @@ public abstract class TQAssocBean<T, R, QB> extends TQAssoc<T, R> {
* Eagerly fetch this association using a "query join" with the properties specified. * Eagerly fetch this association using a "query join" with the properties specified.
*/ */
public final R fetchQuery(String properties) { public final R fetchQuery(String properties) {
((QueryBean) _root).query().fetchQuery(_name, properties); ((TQRootBean) _root).query().fetchQuery(_name, properties);
return _root; return _root;
} }
@@ -96,7 +96,7 @@ public abstract class TQAssocBean<T, R, QB> extends TQAssoc<T, R> {
* Cache misses are populated via fetchQuery(). * Cache misses are populated via fetchQuery().
*/ */
public final R fetchCache(String properties) { public final R fetchCache(String properties) {
((QueryBean) _root).query().fetchCache(_name, properties); ((TQRootBean) _root).query().fetchCache(_name, properties);
return _root; return _root;
} }
@@ -177,7 +177,7 @@ public abstract class TQAssocBean<T, R, QB> extends TQAssoc<T, R> {
} }
private SpiQueryFetch spiQuery() { private SpiQueryFetch spiQuery() {
return (SpiQueryFetch) ((QueryBean) _root).query(); return (SpiQueryFetch) ((TQRootBean) _root).query();
} }
private Set<String> properties(TQProperty<?, ?>... props) { private Set<String> properties(TQProperty<?, ?>... props) {
@@ -41,7 +41,7 @@ public class TQProperty<R, T> implements Query.Property<T> {
* Internal method to return the underlying expression list. * Internal method to return the underlying expression list.
*/ */
protected final ExpressionList<?> expr() { protected final ExpressionList<?> expr() {
return ((QueryBean<?, ?>) _root).peekExprList(); return ((TQRootBean<?, ?>) _root).peekExprList();
} }
/** /**
@@ -57,7 +57,7 @@ import java.util.stream.Stream;
* @param <R> the specific root query bean type (e.g. QCustomer) * @param <R> the specific root query bean type (e.g. QCustomer)
*/ */
@NonNullApi @NonNullApi
public abstract class QueryBean<T, R> implements IQueryBean<T, R> { public abstract class TQRootBean<T, R> implements IQueryBean<T, R> {
/** /**
* The underlying query. * The underlying query.
@@ -88,21 +88,21 @@ public abstract class QueryBean<T, R> implements IQueryBean<T, R> {
/** /**
* Construct using the type of bean to query on and the default database. * Construct using the type of bean to query on and the default database.
*/ */
public QueryBean(Class<T> beanType) { public TQRootBean(Class<T> beanType) {
this(beanType, DB.getDefault()); this(beanType, DB.getDefault());
} }
/** /**
* Construct using the type of bean to query on and a given database. * Construct using the type of bean to query on and a given database.
*/ */
public QueryBean(Class<T> beanType, Database database) { public TQRootBean(Class<T> beanType, Database database) {
this(database.find(beanType)); this(database.find(beanType));
} }
/** /**
* Construct with a transaction. * Construct with a transaction.
*/ */
protected QueryBean(Class<T> beanType, Transaction transaction) { protected TQRootBean(Class<T> beanType, Transaction transaction) {
this(beanType); this(beanType);
query.usingTransaction(transaction); query.usingTransaction(transaction);
} }
@@ -110,7 +110,7 @@ public abstract class QueryBean<T, R> implements IQueryBean<T, R> {
/** /**
* Construct with a database and transaction. * Construct with a database and transaction.
*/ */
protected QueryBean(Class<T> beanType, Database database, Transaction transaction) { protected TQRootBean(Class<T> beanType, Database database, Transaction transaction) {
this(beanType, database); this(beanType, database);
query.usingTransaction(transaction); query.usingTransaction(transaction);
} }
@@ -119,7 +119,7 @@ public abstract class QueryBean<T, R> implements IQueryBean<T, R> {
* Construct using a query. * Construct using a query.
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public QueryBean(Query<T> query) { public TQRootBean(Query<T> query) {
this.query = query; this.query = query;
this.root = (R) this; this.root = (R) this;
} }
@@ -129,13 +129,13 @@ public abstract class QueryBean<T, R> implements IQueryBean<T, R> {
* values for select() and fetch(). * values for select() and fetch().
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public QueryBean(boolean aliasDummy) { public TQRootBean(boolean aliasDummy) {
this.query = null; this.query = null;
this.root = (R) this; this.root = (R) this;
} }
/** Construct for FilterMany */ /** Construct for FilterMany */
protected QueryBean(ExpressionList<T> filter) { protected TQRootBean(ExpressionList<T> filter) {
this.query = null; this.query = null;
this.root = null; this.root = null;
this.whereStack = new ArrayStack<>(); this.whereStack = new ArrayStack<>();
+1 -1
View File
@@ -72,7 +72,7 @@
<extensions>true</extensions> <extensions>true</extensions>
<configuration> <configuration>
<tiles> <tiles>
<tile>io.ebean.tile:enhancement:14.2.0</tile> <tile>io.ebean.tile:enhancement:14.1.0</tile>
</tiles> </tiles>
</configuration> </configuration>
</plugin> </plugin>
+1 -1
View File
@@ -99,7 +99,7 @@
<extensions>true</extensions> <extensions>true</extensions>
<configuration> <configuration>
<tiles> <tiles>
<tile>io.ebean.tile:enhancement:14.2.0</tile> <tile>io.ebean.tile:enhancement:14.1.0</tile>
</tiles> </tiles>
</configuration> </configuration>
</plugin> </plugin>
+1 -1
View File
@@ -309,7 +309,7 @@
<extensions>true</extensions> <extensions>true</extensions>
<configuration> <configuration>
<tiles> <tiles>
<tile>io.ebean.tile:enhancement:14.2.0</tile> <tile>io.ebean.tile:enhancement:14.1.0</tile>
</tiles> </tiles>
</configuration> </configuration>
</plugin> </plugin>
@@ -199,11 +199,8 @@ public class TestCustomerFinder extends BaseTestCase {
} }
// obtains db query plans ... // obtains db query plans ...
QueryPlanRequest request = new QueryPlanRequest(); // collect max 10 plans, after 10 secs don't collect any more plans
// collect max 1000 plans (use something more like 10) var request = new QueryPlanRequest(10, 10_000);
request.maxCount(1_000);
// don't collect any more plans if used 10 secs
request.maxTimeMillis(10_000);
List<MetaQueryPlan> plans0 = server().metaInfo().queryPlanCollectNow(request); List<MetaQueryPlan> plans0 = server().metaInfo().queryPlanCollectNow(request);
assertThat(plans0).isNotEmpty(); assertThat(plans0).isNotEmpty();
@@ -214,7 +211,7 @@ public class TestCustomerFinder extends BaseTestCase {
System.out.println(plan); System.out.println(plan);
} }
//DB.getBackgroundExecutor().scheduleWithFixedDelay(...) // DB.backgroundExecutor().scheduleWithFixedDelay(...)
} }
@Test @Test
@@ -337,7 +337,7 @@ class SimpleQueryBeanWriter {
writer.append(" */").eol(); writer.append(" */").eol();
writer.append(Constants.AT_GENERATED).eol(); writer.append(Constants.AT_GENERATED).eol();
writer.append(Constants.AT_TYPEQUERYBEAN).eol(); writer.append(Constants.AT_TYPEQUERYBEAN).eol();
writer.append("class Q%s : io.ebean.typequery.QueryBean<%s, Q%s> {", shortName, beanFullName, shortName).eol(); writer.append("class Q%s : io.ebean.typequery.TQRootBean<%s, Q%s> {", shortName, beanFullName, shortName).eol();
} }
writer.eol(); writer.eol();
@@ -225,7 +225,7 @@ class SimpleQueryBeanWriter {
writer.append("public final class Q%s {", shortName).eol(); writer.append("public final class Q%s {", shortName).eol();
} else { } else {
writer.append(Constants.AT_TYPEQUERYBEAN).eol(); writer.append(Constants.AT_TYPEQUERYBEAN).eol();
writer.append("public final class Q%s extends io.ebean.typequery.QueryBean<%s,Q%s> {", shortName, beanFullName, shortName).eol(); writer.append("public final class Q%s extends io.ebean.typequery.TQRootBean<%s,Q%s> {", shortName, beanFullName, shortName).eol();
} }
writer.eol(); writer.eol();
} }
+2 -2
View File
@@ -11,7 +11,7 @@
<artifactId>test-java16</artifactId> <artifactId>test-java16</artifactId>
<properties> <properties>
<maven.compiler.release>17</maven.compiler.release> <java.release>16</java.release>
</properties> </properties>
<dependencies> <dependencies>
@@ -52,7 +52,7 @@
<extensions>true</extensions> <extensions>true</extensions>
<configuration> <configuration>
<tiles> <tiles>
<tile>io.ebean.tile:enhancement:14.2.0</tile> <tile>io.ebean.tile:enhancement:14.1.0</tile>
</tiles> </tiles>
</configuration> </configuration>
</plugin> </plugin>