Compare commits

...
25 changed files with 749 additions and 73 deletions
+2 -2
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.10.5</version>
<version>11.10.6</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -17,7 +17,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.10.5</tag>
<tag>ebean-11.10.6</tag>
</scm>
<profiles>
+24
View File
@@ -54,6 +54,25 @@ public interface SqlUpdate {
*/
int execute();
/**
* Return the generated key value.
*/
Object getGeneratedKey();
/**
* Execute and return the generated key. This is effectively a short cut for:
*
* <pre>{@code
*
* sqlUpdate.execute();
* Object key = sqlUpdate.getGeneratedKey();
*
* }</pre>
*
* @return The generated key value
*/
Object executeGetKey();
/**
* Return true if eBean should automatically deduce the table modification
* information and process it.
@@ -88,6 +107,11 @@ public interface SqlUpdate {
*/
SqlUpdate setLabel(String label);
/**
* Set to true when we want to use getGeneratedKeys with this statement.
*/
SqlUpdate setGetGeneratedKeys(boolean getGeneratedKeys);
/**
* Return the sql statement.
*/
@@ -463,6 +463,12 @@ public class ServerConfig {
*/
private boolean disableL2Cache;
/**
* Generally we want to perform L2 cache notification in the background and not impact
* the performance of executing transactions.
*/
private boolean notifyL2CacheInForeground;
/**
* The time in millis used to determine when a query is alerted for being slow.
*/
@@ -473,7 +479,6 @@ public class ServerConfig {
*/
private SlowQueryListener slowQueryListener;
private ProfilingConfig profilingConfig = new ProfilingConfig();
/**
@@ -2680,6 +2685,7 @@ public class ServerConfig {
slowQueryMillis = p.getLong("slowQueryMillis", slowQueryMillis);
docStoreOnly = p.getBoolean("docStoreOnly", docStoreOnly);
disableL2Cache = p.getBoolean("disableL2Cache", disableL2Cache);
notifyL2CacheInForeground = p.getBoolean("notifyL2CacheInForeground", notifyL2CacheInForeground);
explicitTransactionBeginMode = p.getBoolean("explicitTransactionBeginMode", explicitTransactionBeginMode);
autoCommitMode = p.getBoolean("autoCommitMode", autoCommitMode);
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
@@ -2900,6 +2906,25 @@ public class ServerConfig {
this.disableL2Cache = disableL2Cache;
}
/**
* Return true if L2 cache notification should run in the foreground.
*/
public boolean isNotifyL2CacheInForeground() {
return notifyL2CacheInForeground;
}
/**
* Set this to true to run L2 cache notification in the foreground.
* <p>
* In general we don't want to do that as when we use a distributed cache (like Ignite, Hazelcast etc)
* we are making network calls and we prefer to do this in background and not impact the response time
* of the executing transaction.
* </p>
*/
public void setNotifyL2CacheInForeground(boolean notifyL2CacheInForeground) {
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
}
/**
* Return the query plan time to live.
*/
@@ -4,7 +4,23 @@ import io.ebean.SqlUpdate;
public interface SpiSqlUpdate extends SqlUpdate {
/**
* Return the Bind parameters.
*/
BindParams getBindParams();
/**
* Set the final sql being executed with named parameters replaced etc.
*/
void setGeneratedSql(String sql);
/**
* Return true if we are using getGeneratedKeys.
*/
boolean isGetGeneratedKeys();
/**
* Set the generated key value.
*/
void setGeneratedKey(Object idValue);
}
@@ -65,6 +65,10 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
*/
private int addPos;
private boolean getGeneratedKeys;
private Object generatedKey;
/**
* Create with server sql and bindParams object.
* <p>
@@ -93,6 +97,12 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
this(null, sql, new BindParams());
}
@Override
public Object executeGetKey() {
execute();
return getGeneratedKey();
}
@Override
public int execute() {
if (server != null) {
@@ -103,6 +113,16 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
}
}
@Override
public Object getGeneratedKey() {
return generatedKey;
}
@Override
public void setGeneratedKey(Object idValue) {
this.generatedKey = idValue;
}
@Override
public boolean isAutoTableMod() {
return isAutoTableMod;
@@ -125,6 +145,17 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
return this;
}
@Override
public boolean isGetGeneratedKeys() {
return getGeneratedKeys;
}
@Override
public SqlUpdate setGetGeneratedKeys(boolean getGeneratedKeys) {
this.getGeneratedKeys = getGeneratedKeys;
return this;
}
@Override
public String getGeneratedSql() {
return generatedSql;
@@ -372,10 +372,10 @@ public class InternalConfiguration {
public TransactionManager createTransactionManager(DocStoreUpdateProcessor indexUpdateProcessor) {
TransactionScopeManager scopeManager = createTransactionScopeManager();
boolean localL2 = cacheManager.isLocalL2Caching();
boolean notifyL2CacheInForeground = cacheManager.isLocalL2Caching() || serverConfig.isNotifyL2CacheInForeground();
TransactionManagerOptions options =
new TransactionManagerOptions(localL2, serverConfig, scopeManager, clusterManager, backgroundExecutor,
new TransactionManagerOptions(notifyL2CacheInForeground, serverConfig, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler());
if (serverConfig.isExplicitTransactionBeginMode()) {
@@ -73,6 +73,11 @@ public final class PersistRequestUpdateSql extends PersistRequest {
*/
@Override
public void setGeneratedKey(Object idValue) {
updateSql.setGeneratedKey(idValue);
}
public boolean isGetGeneratedKeys() {
return updateSql.isGetGeneratedKeys();
}
/**
@@ -16,7 +16,7 @@ import java.sql.SQLException;
/**
* Executes the UpdateSql requests.
*/
public class ExeOrmUpdate {
class ExeOrmUpdate {
private static final Logger logger = LoggerFactory.getLogger(ExeOrmUpdate.class);
@@ -27,7 +27,7 @@ public class ExeOrmUpdate {
/**
* Create with a given binder.
*/
public ExeOrmUpdate(Binder binder) {
ExeOrmUpdate(Binder binder) {
this.pstmtFactory = new PstmtFactory();
this.binder = binder;
}
@@ -107,7 +107,7 @@ public class ExeOrmUpdate {
if (logSql) {
t.logSql(sql);
}
pstmt = pstmtFactory.getPstmt(t, sql);
pstmt = pstmtFactory.getPstmt(t, sql, false);
}
String bindLog = null;
@@ -6,17 +6,19 @@ import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.core.PersistRequestUpdateSql;
import io.ebeaninternal.server.core.PersistRequestUpdateSql.SqlType;
import io.ebeaninternal.server.util.BindParamsParser;
import io.ebeaninternal.util.JdbcClose;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Executes the UpdateSql requests.
*/
public class ExeUpdateSql {
class ExeUpdateSql {
private static final Logger logger = LoggerFactory.getLogger(ExeUpdateSql.class);
@@ -27,7 +29,7 @@ public class ExeUpdateSql {
/**
* Create with a given binder.
*/
public ExeUpdateSql(Binder binder) {
ExeUpdateSql(Binder binder) {
this.binder = binder;
this.pstmtFactory = new PstmtFactory();
}
@@ -51,6 +53,9 @@ public class ExeUpdateSql {
} else {
int rowCount = pstmt.executeUpdate();
request.checkRowCount(rowCount);
if (request.isGetGeneratedKeys()) {
readGeneratedKeys(pstmt, request);
}
request.postExecute();
return rowCount;
}
@@ -68,6 +73,22 @@ public class ExeUpdateSql {
}
}
private void readGeneratedKeys(PreparedStatement stmt, PersistRequestUpdateSql request) {
ResultSet resultSet = null;
try {
resultSet = stmt.getGeneratedKeys();
if (resultSet.next()) {
request.setGeneratedKey(resultSet.getObject(1));
}
} catch (SQLException ex) {
throw new PersistenceException(ex);
} finally {
JdbcClose.close(resultSet);
}
}
private PreparedStatement bindStmt(PersistRequestUpdateSql request, boolean batchThisRequest) throws SQLException {
SpiSqlUpdate updateSql = request.getUpdateSql();
@@ -90,7 +111,7 @@ public class ExeUpdateSql {
if (logSql) {
t.logSql(TrimLogSql.trim(sql));
}
pstmt = pstmtFactory.getPstmt(t, sql);
pstmt = pstmtFactory.getPstmt(t, sql, request.isGetGeneratedKeys());
}
if (updateSql.getTimeout() > 0) {
@@ -6,6 +6,7 @@ import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Factory for creating Statements.
@@ -14,9 +15,9 @@ import java.sql.SQLException;
* getGeneratedKeys.
* </p>
*/
public class PstmtFactory {
class PstmtFactory {
public PstmtFactory() {
PstmtFactory() {
}
/**
@@ -30,9 +31,13 @@ public class PstmtFactory {
/**
* Get a prepared statement without any batching.
*/
public PreparedStatement getPstmt(SpiTransaction t, String sql) throws SQLException {
public PreparedStatement getPstmt(SpiTransaction t, String sql, boolean getGeneratedKeys) throws SQLException {
Connection conn = t.getInternalConnection();
return conn.prepareStatement(sql);
if (getGeneratedKeys) {
return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
} else {
return conn.prepareStatement(sql);
}
}
/**
@@ -24,6 +24,8 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
private static final Logger logger = LoggerFactory.getLogger(DmlHandler.class);
private static final int[] GENERATED_KEY_COLUMNS = new int[]{1};
/**
* The originating request.
*/
@@ -260,8 +262,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
// the Id generated is always the first column
// Required to stop Oracle10 giving us Oracle rowId??
// Other jdbc drivers seem fine without this hint.
int[] columns = {1};
return conn.prepareStatement(sql, columns);
return conn.prepareStatement(sql, GENERATED_KEY_COLUMNS);
} else {
return conn.prepareStatement(sql);
@@ -366,6 +366,10 @@ class CQueryBuilder {
return new SqlTreeBuilder(this, request, predicates).build();
}
private String nativeQueryPaging(SpiQuery<?> query, String sql) {
return dbPlatform.getBasicSqlLimiter().limit(sql, query.getFirstRow(), query.getMaxRows());
}
/**
* Create the SqlTree by reading the ResultSetMetaData and mapping table/columns to bean property paths.
*/
@@ -375,6 +379,9 @@ class CQueryBuilder {
// parse named parameters returning the final sql to execute
String sql = predicates.parseBindParams(query.getNativeSql());
if (query.hasMaxRowsOrFirstRow()) {
sql = nativeQueryPaging(query, sql);
}
query.setGeneratedSql(sql);
Connection connection = request.getTransaction().getConnection();
@@ -996,7 +996,7 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
CQueryPlanKey createQueryPlanKey() {
if (isNativeSql()) {
queryPlanKey = new NativeSqlQueryPlanKey(nativeSql);
queryPlanKey = new NativeSqlQueryPlanKey(nativeSql + "-" + firstRow + "-" + maxRows);
} else {
queryPlanKey = new OrmQueryPlanKey(beanDescriptor.getDiscValue(), m2mIncludeJoin, type, detail, maxRows, firstRow,
disableLazyLoading, orderBy,
@@ -86,7 +86,7 @@ public final class PostCommitProcessing {
*/
void notifyLocalCache() {
processTableEvents(event.getEventTables());
if (manager.localL2Caching) {
if (manager.notifyL2CacheInForeground) {
// process l2 cache changes in foreground
processCacheChanges(event.buildCacheChanges(manager.viewInvalidation));
} else {
@@ -120,7 +120,7 @@ public class TransactionManager implements SpiTransactionManager {
*/
private final boolean changeLogAsync;
protected final boolean localL2Caching;
protected final boolean notifyL2CacheInForeground;
protected final boolean viewInvalidation;
@@ -144,7 +144,7 @@ public class TransactionManager implements SpiTransactionManager {
this.databasePlatform = options.config.getDatabasePlatform();
this.skipCacheAfterWrite = options.config.isSkipCacheAfterWrite();
this.localL2Caching = options.localL2Caching;
this.notifyL2CacheInForeground = options.notifyL2CacheInForeground;
this.persistBatch = options.config.getPersistBatch();
this.persistBatchOnCascade = options.config.appliedPersistBatchOnCascade();
this.rollbackOnChecked = options.config.isTransactionRollbackOnChecked();
@@ -12,7 +12,7 @@ import io.ebeanservice.docstore.api.DocStoreUpdateProcessor;
*/
public class TransactionManagerOptions {
final boolean localL2Caching;
final boolean notifyL2CacheInForeground;
final ServerConfig config;
final ClusterManager clusterManager;
final BackgroundExecutor backgroundExecutor;
@@ -23,11 +23,11 @@ public class TransactionManagerOptions {
final SpiProfileHandler profileHandler;
final TransactionScopeManager scopeManager;
public TransactionManagerOptions(boolean localL2Caching, ServerConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
public TransactionManagerOptions(boolean notifyL2CacheInForeground, ServerConfig config, TransactionScopeManager scopeManager, ClusterManager clusterManager,
BackgroundExecutor backgroundExecutor, DocStoreUpdateProcessor docStoreUpdateProcessor,
BeanDescriptorManager descMgr, DataSourceSupplier dataSourceSupplier, SpiProfileHandler profileHandler) {
this.localL2Caching = localL2Caching;
this.notifyL2CacheInForeground = notifyL2CacheInForeground;
this.config = config;
this.scopeManager = scopeManager;
this.clusterManager = clusterManager;
@@ -6,13 +6,7 @@ import java.lang.reflect.Type;
public class TypeReflectHelper {
public static Class<?>[] getParams(Class<?> cls, Class<?> matchRawType) {
Type[] types = getParamType(cls, matchRawType);
Class<?>[] result = new Class<?>[types.length];
for (int i = 0; i < result.length; i++) {
result[i] = getClass(types[i]);
}
return result;
return TypeResolver.resolveRawArgs(matchRawType, cls);
}
public static Class<?> getClass(Type type) {
@@ -26,20 +20,4 @@ public class TypeReflectHelper {
return (Class<?>) type;
}
}
private static Type[] getParamType(Class<?> cls, Class<?> matchRawType) {
Type[] gis = cls.getGenericInterfaces();
for (Type type : gis) {
if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
Type rawType = paramType.getRawType();
if (rawType.equals(matchRawType)) {
return paramType.getActualTypeArguments();
}
}
}
return null;
}
}
@@ -0,0 +1,244 @@
package io.ebeaninternal.server.type;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.HashMap;
import java.util.Map;
/**
* This class is a modified version of TypeResolver from https://github.com/jhalterman/typetools
* which is Apache 2 license.
*
* It is a cut down version removing the lambda support and related sun.misc.Unsafe use etc.
*/
class TypeResolver {
/** An unknown type. */
private static final class Unknown {
private Unknown() {
}
}
private TypeResolver() {
}
/**
* Returns an array of raw classes representing arguments for the {@code type} using type variable information from
* the {@code subType}. Arguments for {@code type} that cannot be resolved are returned as {@code Unknown.class}. If
* no arguments can be resolved then {@code null} is returned.
*
* @param type to resolve arguments for
* @param subType to extract type variable information from
* @return array of raw classes representing arguments for the {@code type} else {@code null} if no type arguments are
* declared
*/
static Class<?>[] resolveRawArgs(Class<?> type, Class<?> subType) {
return resolveRawArguments(resolveGenericType(type, subType), subType);
}
/**
* Returns an array of raw classes representing arguments for the {@code genericType} using type variable information
* from the {@code subType}. Arguments for {@code genericType} that cannot be resolved are returned as
* {@code Unknown.class}. If no arguments can be resolved then {@code null} is returned.
*
* @param genericType to resolve arguments for
* @param subType to extract type variable information from
* @return array of raw classes representing arguments for the {@code genericType} else {@code null} if no type
* arguments are declared
*/
private static Class<?>[] resolveRawArguments(Type genericType, Class<?> subType) {
Class<?>[] result = null;
if (genericType instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) genericType;
Type[] arguments = paramType.getActualTypeArguments();
result = new Class[arguments.length];
for (int i = 0; i < arguments.length; i++) {
result[i] = resolveRawClass(arguments[i], subType);
}
} else if (genericType instanceof TypeVariable) {
result = new Class[1];
result[0] = resolveRawClass(genericType, subType);
} else if (genericType instanceof Class) {
TypeVariable<?>[] typeParams = ((Class<?>) genericType).getTypeParameters();
result = new Class[typeParams.length];
for (int i = 0; i < typeParams.length; i++) {
result[i] = resolveRawClass(typeParams[i], subType);
}
}
return result;
}
/**
* Returns the generic {@code type} using type variable information from the {@code subType} else {@code null} if the
* generic type cannot be resolved.
*
* @param type to resolve generic type for
* @param subType to extract type variable information from
* @return generic {@code type} else {@code null} if it cannot be resolved
*/
private static Type resolveGenericType(Class<?> type, Type subType) {
Class<?> rawType;
if (subType instanceof ParameterizedType) {
rawType = (Class<?>) ((ParameterizedType) subType).getRawType();
} else {
rawType = (Class<?>) subType;
}
if (type.equals(rawType)) {
return subType;
}
Type result;
if (type.isInterface()) {
for (Type superInterface : rawType.getGenericInterfaces())
if (superInterface != null && !superInterface.equals(Object.class))
if ((result = resolveGenericType(type, superInterface)) != null)
return result;
}
Type superClass = rawType.getGenericSuperclass();
if (superClass != null && !superClass.equals(Object.class)) {
if ((result = resolveGenericType(type, superClass)) != null) {
return result;
}
}
return null;
}
private static Class<?> resolveRawClass(Type genericType, Class<?> subType) {
if (genericType instanceof Class) {
return (Class<?>) genericType;
} else if (genericType instanceof ParameterizedType) {
return resolveRawClass(((ParameterizedType) genericType).getRawType(), subType);
} else if (genericType instanceof GenericArrayType) {
GenericArrayType arrayType = (GenericArrayType) genericType;
Class<?> component = resolveRawClass(arrayType.getGenericComponentType(), subType);
return Array.newInstance(component, 0).getClass();
} else if (genericType instanceof TypeVariable) {
TypeVariable<?> variable = (TypeVariable<?>) genericType;
genericType = getTypeVariableMap(subType).get(variable);
genericType = genericType == null ? resolveBound(variable)
: resolveRawClass(genericType, subType);
}
return genericType instanceof Class ? (Class<?>) genericType : Unknown.class;
}
private static Map<TypeVariable<?>, Type> getTypeVariableMap(final Class<?> targetType) {
Map<TypeVariable<?>, Type> map = new HashMap<>();
// Populate interfaces
populateSuperTypeArgs(targetType.getGenericInterfaces(), map);
// Populate super classes and interfaces
Type genericType = targetType.getGenericSuperclass();
Class<?> type = targetType.getSuperclass();
while (type != null && !Object.class.equals(type)) {
if (genericType instanceof ParameterizedType) {
populateTypeArgs((ParameterizedType) genericType, map);
}
populateSuperTypeArgs(type.getGenericInterfaces(), map);
genericType = type.getGenericSuperclass();
type = type.getSuperclass();
}
// Populate enclosing classes
type = targetType;
while (type.isMemberClass()) {
genericType = type.getGenericSuperclass();
if (genericType instanceof ParameterizedType) {
populateTypeArgs((ParameterizedType) genericType, map);
}
type = type.getEnclosingClass();
}
return map;
}
/**
* Populates the {@code map} with with variable/argument pairs for the given {@code types}.
*/
private static void populateSuperTypeArgs(final Type[] types, final Map<TypeVariable<?>, Type> map) {
for (Type type : types) {
if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) type;
populateTypeArgs(parameterizedType, map);
Type rawType = parameterizedType.getRawType();
if (rawType instanceof Class) {
populateSuperTypeArgs(((Class<?>) rawType).getGenericInterfaces(), map);
}
} else if (type instanceof Class) {
populateSuperTypeArgs(((Class<?>) type).getGenericInterfaces(), map);
}
}
}
/**
* Populates the {@code map} with variable/argument pairs for the given {@code type}.
*/
private static void populateTypeArgs(ParameterizedType type, Map<TypeVariable<?>, Type> map) {
if (type.getRawType() instanceof Class) {
TypeVariable<?>[] typeVariables = ((Class<?>) type.getRawType()).getTypeParameters();
Type[] typeArguments = type.getActualTypeArguments();
if (type.getOwnerType() != null) {
Type owner = type.getOwnerType();
if (owner instanceof ParameterizedType) {
populateTypeArgs((ParameterizedType) owner, map);
}
}
for (int i = 0; i < typeArguments.length; i++) {
TypeVariable<?> variable = typeVariables[i];
Type typeArgument = typeArguments[i];
if (typeArgument instanceof Class) {
map.put(variable, typeArgument);
} else if (typeArgument instanceof GenericArrayType) {
map.put(variable, typeArgument);
} else if (typeArgument instanceof ParameterizedType) {
map.put(variable, typeArgument);
} else if (typeArgument instanceof TypeVariable) {
TypeVariable<?> typeVariableArgument = (TypeVariable<?>) typeArgument;
Type resolvedType = map.get(typeVariableArgument);
if (resolvedType == null)
resolvedType = resolveBound(typeVariableArgument);
map.put(variable, resolvedType);
}
}
}
}
/**
* Resolves the first bound for the {@code typeVariable}, returning {@code Unknown.class} if none can be resolved.
*/
private static Type resolveBound(TypeVariable<?> typeVariable) {
Type[] bounds = typeVariable.getBounds();
if (bounds.length == 0) {
return Unknown.class;
}
Type bound = bounds[0];
if (bound instanceof TypeVariable) {
bound = resolveBound((TypeVariable<?>) bound);
}
return bound == Object.class ? Unknown.class : bound;
}
}
@@ -4,6 +4,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
@@ -13,6 +14,19 @@ public class JdbcClose {
private static final Logger logger = LoggerFactory.getLogger(JdbcClose.class);
/**
* Close the resultSet logging if an error occurs.
*/
public static void close(ResultSet resultSet) {
try {
if (resultSet != null) {
resultSet.close();
}
} catch (SQLException e) {
logger.warn("Error closing resultSet", e);
}
}
/**
* Close the connection logging if an error occurs.
*/
@@ -7,6 +7,7 @@ import org.junit.Test;
import java.util.Properties;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -43,9 +44,13 @@ public class ServerConfigTest {
props.setProperty("dbOffline", "true");
props.setProperty("jsonDateTime", "ISO8601");
props.setProperty("autoReadOnlyDataSource", "true");
props.setProperty("disableL2Cache", "true");
props.setProperty("notifyL2CacheInForeground", "true");
serverConfig.loadFromProperties(props);
assertTrue(serverConfig.isDisableL2Cache());
assertTrue(serverConfig.isNotifyL2CacheInForeground());
assertTrue(serverConfig.isDbOffline());
assertTrue(serverConfig.isAutoReadOnlyDataSource());
@@ -66,7 +71,15 @@ public class ServerConfigTest {
props1.setProperty("ebean.persistBatch", "ALL");
props1.setProperty("ebean.persistBatchOnCascade", "ALL");
serverConfig.setNotifyL2CacheInForeground(true);
serverConfig.setDisableL2Cache(true);
props1.setProperty("ebean.disableL2Cache", "false");
props1.setProperty("ebean.notifyL2CacheInForeground", "false");
serverConfig.loadFromProperties(props1);
assertFalse(serverConfig.isDisableL2Cache());
assertFalse(serverConfig.isNotifyL2CacheInForeground());
serverConfig.loadTestProperties();
assertEquals(PersistBatch.ALL, serverConfig.getPersistBatch());
@@ -0,0 +1,84 @@
package io.ebeaninternal.server.type;
import io.ebean.config.ScalarTypeConverter;
import org.junit.Test;
import org.tests.model.ivo.Money;
import org.tests.model.ivo.Oid;
import org.tests.model.ivo.SysTime;
import org.tests.model.ivo.converter.MoneyTypeConverter;
import org.tests.model.ivo.converter.OidTypeConverter;
import org.tests.model.ivo.converter.SysTimeConverter;
import javax.persistence.AttributeConverter;
import java.math.BigDecimal;
import java.sql.Timestamp;
import static org.assertj.core.api.Assertions.assertThat;
public class TypeReflectHelperTest {
@Test
public void getParams_MoneyTypeConverter() {
Class<?>[] params = TypeReflectHelper.getParams(MoneyTypeConverter.class, AttributeConverter.class);
assertThat(params.length).isEqualTo(2);
assertThat(params[0]).isEqualTo(Money.class);
assertThat(params[1]).isEqualTo(BigDecimal.class);
}
@Test
public void getParams_OidTypeConverter() {
Class<?>[] params = TypeReflectHelper.getParams(OidTypeConverter.class, ScalarTypeConverter.class);
assertThat(params.length).isEqualTo(2);
assertThat(params[0]).isEqualTo(Oid.class);
assertThat(params[1]).isEqualTo(Long.class);
}
@Test
public void getParams_SysTimeConverter() {
Class<?>[] params = TypeReflectHelper.getParams(SysTimeConverter.class, ScalarTypeConverter.class);
assertThat(params.length).isEqualTo(2);
assertThat(params[0]).isEqualTo(SysTime.class);
assertThat(params[1]).isEqualTo(Timestamp.class);
}
@Test
public void getParams_RichTextConverter() {
Class<?>[] params = TypeReflectHelper.getParams(RichTextConverter.class, ScalarTypeConverter.class);
assertThat(params.length).isEqualTo(2);
assertThat(params[0]).isEqualTo(RichText.class);
assertThat(params[1]).isEqualTo(byte[].class);
}
static class RichText {
}
static class RichTextConverter extends Direct<RichText> {}
static class Direct<M> implements ScalarTypeConverter<M, byte[]> {
@Override
public M getNullValue() {
return null;
}
@Override
public M wrapValue(byte[] scalarType) {
return null;
}
@Override
public byte[] unwrapValue(M beanType) {
return new byte[0];
}
}
}
@@ -3,7 +3,6 @@ package org.tests.rawsql.nativesql;
import io.ebean.BaseTestCase;
import io.ebean.BeanState;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.Query;
import io.ebean.annotation.IgnorePlatform;
import io.ebean.annotation.Platform;
@@ -24,16 +23,14 @@ public class TestNativeSqlBasic extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getDefaultServer();
String nativeSql = "select id, name from o_customer";
Query<Customer> query = server.findNative(Customer.class, nativeSql);
Query<Customer> query = Ebean.findNative(Customer.class, nativeSql);
List<Customer> customers = query.findList();
assertThat(customers).isNotEmpty();
BeanState beanState = server.getBeanState(customers.get(0));
BeanState beanState = Ebean.getBeanState(customers.get(0));
assertThat(beanState.getLoadedProps()).contains("id", "name");
}
@@ -42,15 +39,13 @@ public class TestNativeSqlBasic extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getDefaultServer();
String nativeSql = "select * from o_customer";
Query<Customer> query = server.findNative(Customer.class, nativeSql);
Query<Customer> query = Ebean.findNative(Customer.class, nativeSql);
List<Customer> customers = query.findList();
assertThat(customers).isNotEmpty();
BeanState beanState = server.getBeanState(customers.get(0));
BeanState beanState = Ebean.getBeanState(customers.get(0));
assertThat(beanState.getLoadedProps().size()).isGreaterThan(10);
}
@@ -59,10 +54,8 @@ public class TestNativeSqlBasic extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getDefaultServer();
String nativeSql = "select c.*, b.city from o_customer c join o_address b on b.id = c.billing_address_id";
Query<Customer> query = server.findNative(Customer.class, nativeSql);
Query<Customer> query = Ebean.findNative(Customer.class, nativeSql);
List<Customer> customers = query.findList();
assertThat(customers).isNotEmpty();
@@ -73,33 +66,67 @@ public class TestNativeSqlBasic extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getDefaultServer();
String nativeSql = "select id, name from o_customer where id > :some";
Query<Customer> query = server.findNative(Customer.class, nativeSql);
Query<Customer> query = Ebean.findNative(Customer.class, nativeSql);
query.setParameter("some", 1);
List<Customer> customers = query.findList();
assertThat(customers).isNotEmpty();
Query<Customer> query2 = server.findNative(Customer.class, nativeSql);
Query<Customer> query2 = Ebean.findNative(Customer.class, nativeSql);
query2.setParameter("some", 2);
List<Customer> customers2 = query2.findList();
assertThat(customers2).isNotEmpty();
}
@Test
public void withMaxRows() {
ResetBasicData.reset();
String nativeSql = "select id, name from o_customer where id > :some";
Query<Customer> query = Ebean.findNative(Customer.class, nativeSql)
.setParameter("some", 1)
.setMaxRows(10);
query.findList();
if (isH2() || isPostgres()) {
assertThat(sqlOf(query)).contains(" limit 10");
}
}
@Test
public void withFirstRowsMaxRows() {
ResetBasicData.reset();
String nativeSql = "select id, name from o_customer where id > :some";
Query<Customer> query = Ebean.findNative(Customer.class, nativeSql)
.setParameter("some", 1)
.setFirstRow(20)
.setMaxRows(10);
query.findList();
if (isH2() || isPostgres()) {
assertThat(sqlOf(query)).contains(" limit 10 offset 20");
}
}
@Test
public void partialAndLazyLoad() {
ResetBasicData.reset();
EbeanServer server = Ebean.getDefaultServer();
String nativeSql = "select id, name from o_customer where id > ?";
List<Customer> customers = server.findNative(Customer.class, nativeSql)
List<Customer> customers = Ebean.findNative(Customer.class, nativeSql)
.setParameter(1, 1)
.findList();
@@ -117,11 +144,9 @@ public class TestNativeSqlBasic extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getDefaultServer();
String nativeSql = "select o.id, o.status, c.id, c.name, c.version from o_order o join o_customer c on c.id = o.kcustomer_id ";
List<Order> orders = server.findNative(Order.class, nativeSql)
List<Order> orders = Ebean.findNative(Order.class, nativeSql)
.findList();
for (Order order : orders) {
@@ -135,11 +160,9 @@ public class TestNativeSqlBasic extends BaseTestCase {
ResetBasicData.reset();
EbeanServer server = Ebean.getDefaultServer();
String nativeSql = "select o.id, o.status, o.kcustomer_id from o_order o";
List<Order> orders = server.findNative(Order.class, nativeSql)
List<Order> orders = Ebean.findNative(Order.class, nativeSql)
.findList();
LoggedSqlCollector.start();
@@ -0,0 +1,57 @@
package org.tests.update;
import io.ebean.annotation.Index;
import io.ebean.annotation.WhenModified;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import java.time.Instant;
@Entity
@Table(name = "e_person_online")
public class EPersonOnline {
@Id
Long id;
@Index(unique = true)
String email;
boolean online;
@WhenModified
Instant whenUpdated;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public boolean isOnline() {
return online;
}
public void setOnline(boolean online) {
this.online = online;
}
public Instant getWhenUpdated() {
return whenUpdated;
}
public void setWhenUpdated(Instant whenUpdated) {
this.whenUpdated = whenUpdated;
}
}
@@ -0,0 +1,128 @@
package org.tests.update;
import io.ebean.BaseTestCase;
import io.ebean.Ebean;
import io.ebean.SqlUpdate;
import io.ebean.annotation.ForPlatform;
import io.ebean.annotation.Platform;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class TestSqlUpdateUpsert extends BaseTestCase {
@ForPlatform(Platform.H2)
@Test
public void h2Merge() {
String sql = "merge into e_person_online (email, online, when_updated) key(email) values (?, ?, now())";
String email = "baz@one.com";
Object key = Ebean.createSqlUpdate(sql)
.setGetGeneratedKeys(true)
.setParameter(1, email)
.setParameter(2, true)
.executeGetKey();
EPersonOnline found = Ebean.find(EPersonOnline.class, key);
assertThat(found).isNotNull();
assertThat(found.getEmail()).isEqualTo(email);
assertThat(found.isOnline()).isTrue();
String sqlNamed = "merge into e_person_online (email, online, when_updated) key(email) values (:email, :online, now())";
SqlUpdate sqlUpdate2 = Ebean.createSqlUpdate(sqlNamed)
.setGetGeneratedKeys(true)
.setParameter("email", email)
.setParameter("online", false);
Object key2 = sqlUpdate2.executeGetKey();
assertThat(key2).isNull();
EPersonOnline found2 = Ebean.find(EPersonOnline.class).where().eq("email", email).findOne();
assertThat(found2).isNotNull();
assertThat(found2.getId()).isEqualTo(key);
assertThat(found2.getEmail()).isEqualTo(email);
assertThat(found2.isOnline()).isFalse();
assertThat(found2.getWhenUpdated()).isGreaterThan(found.getWhenUpdated());
}
@ForPlatform(Platform.POSTGRES)
@Test
public void postgresUpsert() {
String sql = "insert into e_person_online (email, online, when_updated) values (?, ?, now()) on conflict (email) do update set when_updated=now(), online = ?";
String email = "foo@one.com";
Object key = Ebean.createSqlUpdate(sql)
.setGetGeneratedKeys(true)
.setParameter(1, email)
.setParameter(2, true)
.setParameter(3, true)
.executeGetKey();
EPersonOnline found = Ebean.find(EPersonOnline.class, key);
assertThat(found).isNotNull();
assertThat(found.getEmail()).isEqualTo("foo@one.com");
assertThat(found.isOnline()).isTrue();
String sqlNamed = "insert into e_person_online (email, online, when_updated) values (:email, :online, now()) on conflict (email) do update set when_updated=now(), online = :online";
SqlUpdate sqlUpdate2 = Ebean.createSqlUpdate(sqlNamed)
.setGetGeneratedKeys(true)
.setParameter("email", email)
.setParameter("online", false);
Object key2 = sqlUpdate2.executeGetKey();
EPersonOnline found2 = Ebean.find(EPersonOnline.class, key2);
assertThat(found2).isNotNull();
assertThat(found2.getId()).isEqualTo(key);
assertThat(found2.getEmail()).isEqualTo("foo@one.com");
assertThat(found2.isOnline()).isFalse();
assertThat(found2.getWhenUpdated()).isGreaterThan(found.getWhenUpdated());
}
@ForPlatform(Platform.MYSQL)
@Test
public void mySqlUpsert() {
String email = "bar@one.com";
String sql = "insert into e_person_online (email, online, when_updated) values (?, ?, current_time) on duplicate key update when_updated=current_time, online = ?";
SqlUpdate sqlUpdate = Ebean.createSqlUpdate(sql)
.setGetGeneratedKeys(true)
.setParameter(1, email)
.setParameter(2, true)
.setParameter(3, true);
Object key = sqlUpdate.executeGetKey();
assertThat(key).isNotNull();
EPersonOnline found = Ebean.find(EPersonOnline.class, key);
assertThat(found).isNotNull();
assertThat(found.getEmail()).isEqualTo("bar@one.com");
assertThat(found.isOnline()).isTrue();
String sqlNamed = "insert into e_person_online (email, online, when_updated) values (:email, :online, current_time) on duplicate key update when_updated=current_time, online = :online";
SqlUpdate sqlUpdate2 = Ebean.createSqlUpdate(sqlNamed)
.setGetGeneratedKeys(true)
.setParameter("email", email)
.setParameter("online", false);
sqlUpdate2.execute();
Object key2 = sqlUpdate2.getGeneratedKey();
EPersonOnline found2 = Ebean.find(EPersonOnline.class, key2);
assertThat(found2).isNotNull();
assertThat(found2.getId()).isEqualTo(key);
assertThat(found2.getEmail()).isEqualTo("bar@one.com");
assertThat(found2.isOnline()).isFalse();
}
}
+1 -1
View File
@@ -103,7 +103,7 @@ datasource.hsqldb.databaseDriver=org.hsqldb.jdbcDriver
datasource.mysql.username=test_ebean
datasource.mysql.password=test
datasource.mysql.databaseUrl=jdbc:mysql://127.0.0.1:3306/test_ebean
datasource.mysql.databaseUrl=jdbc:mysql://127.0.0.1:4306/test_ebean
datasource.mysql.databaseDriver=com.mysql.jdbc.Driver