mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70ccc6588d | ||
|
|
ac36172cc6 | ||
|
|
1829c74694 | ||
|
|
cc7e22014d | ||
|
|
a5c6f483b0 | ||
|
|
5237e8ba66 | ||
|
|
9926cad7f1 | ||
|
|
6c1b295d41 |
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user