#2072 - Extract ebean-api module - NB: this includes moving ShutdownManager and small adjustment to DocStore/Elastic SPI

This commit is contained in:
rob bygrave
2020-10-09 17:12:27 +13:00
parent 11839aca5e
commit 60becb6346
389 changed files with 253 additions and 109 deletions
@@ -0,0 +1,21 @@
package io.ebean;
import javax.persistence.PessimisticLockException;
/**
* Thrown when failing to acquire a pessimistic lock.
* <p>
* Typically when "select for update nowait" or "select for update" is being used and
* the lock can not be obtained (as it is held by another transaction).
* </p>
*/
public class AcquireLockException extends PessimisticLockException {
private static final long serialVersionUID = -8585962352965876691L;
/**
* Create with a message and cause.
*/
public AcquireLockException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,24 @@
package io.ebean;
/**
* Administrative control of AutoTune during runtime.
*/
public interface AutoTune {
/**
* Fire a garbage collection (hint to the JVM). Assuming garbage collection
* fires this will gather remaining usage profiling information.
*/
void collectProfiling();
/**
* Output the profiling.
* <p>
* When profiling updates are applied to tuning at runtime this reports all tuning and profiling combined.
* When profiling is not applied at runtime then this reports the diff report with new and diff entries relative
* to the existing tuning.
* </p>
*/
void reportProfiling();
}
@@ -0,0 +1,62 @@
package io.ebean;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Background thread pool service for executing of tasks asynchronously.
* <p>
* This service is used internally by Ebean for executing background tasks such
* as the {@link Query#findFutureList()} and also for executing background tasks
* periodically.
* </p>
* <p>
* This service has been made available so you can use it for your application
* code if you want. It can be useful for some server caching implementations
* (background population and trimming of the cache etc).
* </p>
*
* @author rbygrave
*/
public interface BackgroundExecutor {
/**
* Execute a task in the background.
*/
void execute(Runnable r);
/**
* Execute a task periodically with a fixed delay between each execution.
* <p>
* For example, execute a runnable every minute.
* <p>
* The delay is the time between executions no matter how long the task took.
* That is, this method has the same behaviour characteristics as
* {@link ScheduledExecutorService#scheduleWithFixedDelay(Runnable, long, long, TimeUnit)}
*/
void executePeriodically(Runnable r, long delay, TimeUnit unit);
/**
* Execute a task periodically additionally with an initial delay different from delay.
*/
void executePeriodically(Runnable r, long initialDelay, long delay, TimeUnit unit);
/**
* Schedules a Runnable for one-shot action that becomes enabled after the given delay.
*
* @return a ScheduledFuture representing pending completion of the task and
* whose get() method will return null upon completion
*/
ScheduledFuture<?> schedule(Runnable r, long delay, TimeUnit unit);
/**
* Schedules a Callable for one-shot action that becomes enabled after the given delay.
*
* @return a ScheduledFuture that can be used to extract result or cancel
*/
<V> ScheduledFuture<V> schedule(Callable<V> c, long delay, TimeUnit unit);
}
@@ -0,0 +1,164 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Optional;
/**
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
* <p>
* Note that typically users would extend BeanRepository rather than BeanFinder.
* </p>
* <pre>{@code
*
* public class CustomerFinder extends BeanFinder<Long,Customer> {
*
* Inject
* public CustomerFinder(Database database) {
* super(Customer.class, database);
* }
*
* // ... add customer specific finders
* }
*
* }</pre>
*
* @param <I> The ID type
* @param <T> The Bean type
*/
public abstract class BeanFinder<I,T> {
protected final Database server;
protected final Class<T> type;
/**
* Create with the given bean type and Database instance.
*
* @param type The bean type
* @param server The Database instance typically created via Spring factory or equivalent.
*/
protected BeanFinder(Class<T> type, Database server) {
this.type = type;
this.server = server;
}
/**
* Return the Database to use.
*/
public Database db() {
return server;
}
/**
* Return the current transaction.
*/
public Transaction currentTransaction() {
return db().currentTransaction();
}
/**
* Flush the JDBC batch on the current transaction.
*/
public void flush() {
db().flush();
}
/**
* Return typically a different Database to the default.
* <p>
* This is equivalent to {@link DB#byName(String)}
*
* @param server The name of the Database. If this is null then the default Database is returned.
*/
public Database db(String server) {
return DB.byName(server);
}
/**
* Creates an entity reference for this ID.
* <p>
* Equivalent to {@link Database#getReference(Class, Object)}
*/
@Nonnull
public T ref(I id) {
return db().getReference(type, id);
}
/**
* Retrieves an entity by ID.
*/
@Nullable
public T findById(I id) {
return db().find(type, id);
}
/**
* Find an entity by ID returning an Optional.
*/
@Nullable
public Optional<T> findByIdOrEmpty(I id) {
return db().find(type).setId(id).findOneOrEmpty();
}
/**
* Delete a bean by Id.
*/
public void deleteById(I id) {
db().delete(type, id);
}
/**
* Retrieves all entities of the given type.
*/
@Nonnull
public List<T> findAll() {
return query().findList();
}
/**
* Creates an update query.
*
* <pre>{@code
*
* int rows =
* updateQuery()
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* <p>
* Equivalent to {@link Database#update(Class)}
*/
protected UpdateQuery<T> updateQuery() {
return db().update(type);
}
/**
* Creates a query.
* <p>
* Equivalent to {@link Database#find(Class)}
*/
protected Query<T> query() {
return db().find(type);
}
/**
* Creates a native sql query.
*/
protected Query<T> nativeSql(String nativeSql) {
return db().findNative(type, nativeSql);
}
/**
* Creates a query using the ORM query language.
*/
protected Query<T> query(String ormQuery) {
return db().createQuery(type, ormQuery);
}
}
@@ -0,0 +1,218 @@
package io.ebean;
import io.ebean.bean.EntityBean;
import java.util.Collection;
/**
* Provides finder functionality for use with "Dependency Injection style" use of Ebean.
* <p>
* <pre>{@code
*
* Repository
* public class CustomerRepository extends BeanRepository<Long,Customer> {
*
* Inject
* public CustomerRepository(Database server) {
* super(Customer.class, server);
* }
*
* // ... add customer specific finders and persist logic
*
* public List<Customer> findByName(String nameStart) {
* return query().where()
* .istartsWith("name", nameStart)
* .findList();
* }
*
* }
* }</pre>
*
* @param <I> The ID type
* @param <T> The Bean type
*/
public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
/**
* Create with the given bean type and Database instance.
* <p>
* Typically users would extend BeanRepository rather than BeanFinder.
* </p>
* <pre>{@code
*
* @Inject
* public CustomerRepository(Database server) {
* super(Customer.class, server);
* }
*
* }</pre>
*
* @param type The bean type
* @param server The Database instance typically created via Spring factory or equivalent
*/
protected BeanRepository(Class<T> type, Database server) {
super(type, server);
}
/**
* Marks the entity bean as dirty.
* <p>
* This is used so that when a bean that is otherwise unmodified is updated the version
* property is updated.
* <p>
* An unmodified bean that is saved or updated is normally skipped and this marks the bean as
* dirty so that it is not skipped.
* <p>
* <pre>{@code
*
* Customer customer = customerRepository.byId(id);
*
* // mark the bean as dirty so that a save() or update() will
* // increment the version property
*
* customerRepository.markAsDirty(customer);
* customerRepository.save(customer);
*
* }</pre>
*
* @see Database#markAsDirty(Object)
*/
public void markAsDirty(T bean) {
db().markAsDirty(bean);
}
/**
* Mark the property as unset or 'not loaded'.
* <p>
* This would be used to specify a property that we did not wish to include in a stateless update.
* </p>
* <pre>{@code
*
* // populate an entity bean from JSON or whatever
* Customer customer = ...;
*
* // mark the email property as 'unset' so that it is not
* // included in a 'stateless update'
* customerRepository.markPropertyUnset(customer, "email");
*
* customerRepository.update(customer);
*
* }</pre>
*
* @param propertyName the name of the property on the bean to be marked as 'unset'
*/
public void markPropertyUnset(T bean, String propertyName) {
((EntityBean) bean)._ebean_getIntercept().setPropertyLoaded(propertyName, false);
}
/**
* Insert or update this entity depending on its state.
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see Database#save(Object)
*/
public void save(T bean) {
db().save(bean);
}
/**
* Save all the beans in the collection.
*/
public int saveAll(Collection<T> bean) {
return db().saveAll(bean);
}
/**
* Update this entity.
*
* @see Database#update(Object)
*/
public void update(T bean) {
db().update(bean);
}
/**
* Insert this entity.
*
* @see Database#insert(Object)
*/
public void insert(T bean) {
db().insert(bean);
}
/**
* Delete this bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. Note that, if JDBC batch mode is used then this always returns true.
* </p>
*
* @see Database#delete(Object)
*/
public boolean delete(T bean) {
return db().delete(bean);
}
/**
* Delete all the beans in the collection.
*/
public int deleteAll(Collection<T> beans) {
return db().deleteAll(beans);
}
/**
* Delete a bean permanently without soft delete.
* <p>
* This is used when the bean contains a <code>@SoftDelete</code> property and we
* want to perform a hard/permanent delete.
* </p>
*
* @see Database#deletePermanent(Object)
*/
public boolean deletePermanent(T bean) {
return db().deletePermanent(bean);
}
/**
* Merge this entity using the default merge options.
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see Database#merge(Object)
*/
public void merge(T bean) {
db().merge(bean);
}
/**
* Merge this entity using the specified merge options.
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see Database#merge(Object, MergeOptions)
*/
public void merge(T bean, MergeOptions options) {
db().merge(bean, options);
}
/**
* Refreshes this entity from the database.
*
* @see Database#refresh(Object)
*/
public void refresh(T bean) {
db().refresh(bean);
}
}
@@ -0,0 +1,121 @@
package io.ebean;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.Set;
/**
* Provides access to the internal state of an entity bean.
*/
public interface BeanState {
/**
* Return true if this is a lazy loading reference bean.
* <p>
* If so the this bean only holds the Id property and will invoke lazy loading
* if any other property is get or set.
* </p>
*/
boolean isReference();
/**
* Return true if the bean is new (and not yet saved).
*/
boolean isNew();
/**
* Return true if the bean is new or dirty (and probably needs to be saved).
*/
boolean isNewOrDirty();
/**
* Return true if the bean has been changed but not yet saved.
*/
boolean isDirty();
/**
* This can be called with true to disable lazy loading on the bean.
*/
void setDisableLazyLoad(boolean disableLazyLoading);
/**
* Return true if the bean has lazy loading disabled.
*/
boolean isDisableLazyLoad();
/**
* Set the loaded state of the property given it's name.
* <p>
* Typically this would be used to set the loaded state of a property
* to false to ensure that the specific property is excluded from a
* stateless update.
* </p>
* <pre>{@code
*
* // populate a bean via say JSON
* User user = ...;
*
* // set loaded state on the email property to false so that
* // the email property is not included in a stateless update
* DB.getBeanState(user).setPropertyLoaded("email", false);
*
* user.update();
*
* }</pre>
* <p>
* This will throw an IllegalArgumentException if the property is unknown.
*/
void setPropertyLoaded(String propertyName, boolean loaded);
/**
* For partially populated beans returns the properties that are loaded on the
* bean.
* <p>
* Accessing another property will cause lazy loading to occur.
* </p>
*/
Set<String> getLoadedProps();
/**
* Return the set of changed properties.
*/
Set<String> getChangedProps();
/**
* Return a map of the updated properties and their new and old values.
*/
Map<String, ValuePair> getDirtyValues();
/**
* Return true if the bean is readOnly.
* <p>
* If a setter is called on a readOnly bean it will throw an exception.
* </p>
*/
boolean isReadOnly();
/**
* Set the readOnly status for the bean.
*/
void setReadOnly(boolean readOnly);
/**
* Advanced - Used to programmatically build a partially or fully loaded
* entity bean. First create an entity bean via
* {@link Database#createEntityBean(Class)}, then populate its properties
* and then call this method specifying which properties where loaded or null
* for a fully loaded entity bean.
*/
void setLoaded();
/**
* Reset the bean putting it into NEW state such that a save() results in an insert.
*/
void resetForInsert();
/**
* Returns a map with load erros.
*/
@Nullable
Map<String, Exception> getLoadErrors();
}
@@ -0,0 +1,93 @@
package io.ebean;
/**
* Enum to control the different cache modes for queryCache and beanCache.
* <h3>Bean cache</h3>
* <p>
* The bean cache is automatically used by default on <code>@Cache</code> beans for
* the following queries:
* </p>
* <ul>
* <li>findOne() by id</li>
* <li>findOne() by natural key(s)</li>
* <li>findList() by ids</li>
* </ul>
* <p>
* Bean caching needs to be explicitly turned on for queries that are findList() by natural keys.
* </p>
* <h3>Query cache</h3>
* <p>
* For query cache use note that you must be careful, what you do with the returned collection.
* By default the returned collections are read only and you will get an exception if you try
* to change them.
* If you add ".setReadOnly(false)" to your query, you'll get a collection that is a clone from the
* one in the cache. That means, changing does not affect the cache.
* </p>
*
* @author Roland Praml, FOCONIS AG
*/
public enum CacheMode {
/**
* Do not use cache.
*/
OFF(false, false),
/**
* Use the cache and store a result when needed.
*/
ON(true, true),
/**
* Only used for bean caching.
* <p>
* The bean cache is automatically used by default on <code>@Cache</code> beans for
* the following queries:
* </p>
* <ul>
* <li>findOne() by id</li>
* <li>findOne() by natural key(s)</li>
* <li>findList() by ids</li>
* </ul>
* <p>
* Bean caching needs to be explicitly turned on for queries that are findList() by natural keys.
* </p>
*/
AUTO(true, true),
/**
* Do not read from cache, but put beans into the cache and invalidate parts of the cache as necessary.
* <p>
* Use this on a query if you want to get the fresh value from database and put it into the cache.
*/
PUT(false, true),
/**
* GET only from the cache.
* <p>
* This mode does not put entries into the cache or invalidate parts of the cache.
*/
GET(true, false);
private final boolean get;
private final boolean put;
CacheMode(boolean get, boolean put) {
this.get = get;
this.put = put;
}
/**
* Return true if value is read from cache.
*/
public boolean isGet() {
return get;
}
/**
* Return true if a newly loaded value (from database) is put into the cache.
*/
public boolean isPut() {
return put;
}
}
@@ -0,0 +1,185 @@
package io.ebean;
import java.sql.CallableStatement;
import java.sql.SQLException;
/**
* For making calls to stored procedures. Refer to the Ebean execute() method.
* <p>
* Note that UpdateSql is designed for general DML sql and CallableSql is
* designed for use with stored procedures. Also note that when using this in
* batch mode the out parameters are not read.
* </p>
* <p>
* Example 1:
* </p>
* <pre>{@code
*
* String sql = "{call sp_order_mod(?,?)}";
*
* CallableSql cs = DB.createCallableSql(sql);
* cs.setParameter(1, "turbo");
* cs.registerOut(2, Types.INTEGER);
*
* DB.execute(cs);
*
* // read the out parameter
* Integer returnValue = (Integer) cs.getObject(2);
*
* }</pre>
* <p>
* Example 2:<br>
* Includes batch mode, table modification information and label. Note that the
* label is really only to help people reading the transaction logs to identify
* the procedure called etc.
* </p>
*
* <pre>{@code
*
* String sql = "{call sp_insert_order(?,?)}";
*
* CallableSql cs = DB.createCallableSql(sql);
*
* // Inform Ebean this stored procedure inserts into the
* // oe_order table and inserts + updates the oe_order_detail table.
* // this is used to invalidate objects in the cache
* cs.addModification("oe_order", true, false, false);
* cs.addModification("oe_order_detail", true, true, false);
*
*
* try (Transaction t = DB.beginTransaction()) {
*
* // execute using JDBC batching 10 statements at a time
* t.setBatchMode(true);
* t.setBatchSize(10);
*
* cs.setParameter(1, "Was");
* cs.setParameter(2, "Banana");
* DB.execute(cs);
*
* cs.setParameter(1, "Here");
* cs.setParameter(2, "Kumera");
* DB.execute(cs);
*
* cs.setParameter(1, "More");
* cs.setParameter(2, "Apple");
* DB.execute(cs);
*
* // DB.externalModification("oe_order",true,false,false);
* // DB.externalModification("oe_order_detail",true,true,false);
* t.commit();
*
* }
* }</pre>
*
* @see SqlUpdate
*/
public interface CallableSql {
/**
* Return the label that is put into the transaction log.
*/
String getLabel();
/**
* Set the label that is put in the transaction log.
*/
CallableSql setLabel(String label);
/**
* Return the statement execution timeout.
*/
int getTimeout();
/**
* Return the callable sql.
*/
String getSql();
/**
* Set the statement execution timeout. Zero implies unlimited time.
* <p>
* This is set to the underlying CallableStatement.
* </p>
*/
CallableSql setTimeout(int secs);
/**
* Set the callable sql.
*/
CallableSql setSql(String sql);
/**
* Bind a parameter that is bound as a IN parameter.
* <p>
* position starts at value 1 (not 0) to be consistent with CallableStatement.
* </p>
* <p>
* This is designed so that you do not need to set params in index order. You
* can set/register param 2 before param 1 etc.
* </p>
*
* @param position the index position of the parameter.
* @param value the value of the parameter.
*/
CallableSql bind(int position, Object value);
/**
* Bind a positioned parameter (same as bind method).
*
* @param position the index position of the parameter.
* @param value the value of the parameter.
*/
CallableSql setParameter(int position, Object value);
/**
* Register an OUT parameter.
* <p>
* Note that position starts at value 1 (not 0) to be consistent with
* CallableStatement.
* </p>
* <p>
* This is designed so that you do not need to register params in index order.
* You can set/register param 2 before param 1 etc.
* </p>
*
* @param position the index position of the parameter (starts with 1).
* @param type the jdbc type of the OUT parameter that will be read.
*/
CallableSql registerOut(int position, int type);
/**
* Return an OUT parameter value.
* <p>
* position starts at value 1 (not 0) to be consistent with CallableStatement.
* </p>
* <p>
* This can only be called after the CallableSql has been executed. When run
* in batch mode you effectively can't use this method.
* </p>
*/
Object getObject(int position);
/**
* You can extend this object and override this method for more advanced
* stored procedure calls. This would be the case when ResultSets are returned
* etc.
*/
boolean executeOverride(CallableStatement cstmt) throws SQLException;
/**
* Add table modification information to the TransactionEvent.
* <p>
* This would be similar to using the
* <code>DB.externalModification()</code> method. It may be easier and make
* more sense to set it here with the CallableSql.
* </p>
* <p>
* For UpdateSql the table modification information is derived by parsing the
* sql to determine the table name and whether it was an insert, update or
* delete.
* </p>
*/
CallableSql addModification(String tableName, boolean inserts, boolean updates, boolean deletes);
}
@@ -0,0 +1,27 @@
package io.ebean;
/**
* Enumeration to use with {@link Query#setCountDistinct(CountDistinctOrder)}.
* @author Roland Praml, FOCONIS AG
*
*/
public enum CountDistinctOrder {
NO_ORDERING,
/** order by attribute ascending */
ATTR_ASC,
/** order by attribute descending */
ATTR_DESC,
/** order by count ascending and attribute ascending */
COUNT_ASC_ATTR_ASC,
/** order by count ascending and attribute descending */
COUNT_ASC_ATTR_DESC,
/** order by count descending and attribute ascending */
COUNT_DESC_ATTR_ASC,
/** order by count descending and attribute descending */
COUNT_DESC_ATTR_DESC,
}
@@ -0,0 +1,33 @@
package io.ebean;
import java.io.Serializable;
/**
* Holds a distinct value with it's count.
* (Used with {@link Query#findSingleAttributeList()} and {@link Query#setCountDistinct(CountDistinctOrder)}.)
* @author Roland Praml, FOCONIS AG
*/
public class CountedValue<A> implements Serializable {
private static final long serialVersionUID = -2267971668356749695L;
private final A value;
private final long count;
public CountedValue(A value, long count) {
this.value = value;
this.count = count;
}
public long getCount() {
return count;
}
public A getValue() {
return value;
}
@Override
public String toString() {
return count + ": " + value;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,17 @@
package io.ebean;
import javax.persistence.PersistenceException;
/**
* Thrown when a foreign key constraint is enforced.
*/
public class DataIntegrityException extends PersistenceException {
private static final long serialVersionUID = -6740171949170180970L;
/**
* Create with a message and cause.
*/
public DataIntegrityException(String message, Throwable cause) {
super(message, cause);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,133 @@
package io.ebean;
import io.ebean.config.ContainerConfig;
import io.ebean.config.DatabaseConfig;
import io.ebean.service.SpiContainer;
import io.ebean.service.SpiContainerFactory;
import javax.persistence.PersistenceException;
import java.util.Iterator;
import java.util.Properties;
import java.util.ServiceLoader;
/**
* Creates Database instances.
* <p>
* This uses either DatabaseConfig or properties in the application.properties file to
* configure and create a Database instance.
* </p>
* <p>
* The Database instance can either be registered with the DB singleton or
* not. The DB singleton effectively holds a map of Database by a name.
* If the Database is registered with the DB singleton you can retrieve it
* later via {@link DB#byName(String)}.
* </p>
* <p>
* One Database can be nominated as the 'default/primary' Database. Many
* methods on the DB singleton such as {@link DB#find(Class)} are just a
* convenient way of using the 'default/primary' Database.
* </p>
*/
public class DatabaseFactory {
private static SpiContainer container;
static {
EbeanVersion.getVersion();
}
/**
* Initialise the container with clustering configuration.
* <p>
* Call this prior to creating any Database instances or alternatively set the
* ContainerConfig on the DatabaseConfig when creating the first Database instance.
*/
public static synchronized void initialiseContainer(ContainerConfig containerConfig) {
getContainer(containerConfig);
}
/**
* Create using properties to configure the database.
*/
public static synchronized Database create(String name) {
// construct based on loading properties files
return getContainer(null).createServer(name);
}
/**
* Create using the DatabaseConfig object to configure the database.
*/
public static synchronized Database create(DatabaseConfig config) {
if (config.getName() == null) {
throw new PersistenceException("The name is null (it is required)");
}
Database server = createInternal(config);
if (config.isRegister()) {
DbPrimary.setSkip(true);
DbContext.getInstance().register(server, config.isDefaultServer());
}
return server;
}
/**
* Create using the DatabaseConfig additionally specifying a classLoader to use as the context class loader.
*/
public static synchronized Database createWithContextClassLoader(DatabaseConfig config, ClassLoader classLoader) {
ClassLoader currentContextLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(classLoader);
try {
return DatabaseFactory.create(config);
} finally {
// set the currentContextLoader back
Thread.currentThread().setContextClassLoader(currentContextLoader);
}
}
/**
* Shutdown gracefully all Database instances cleaning up any resources as required.
* <p>
* This is typically invoked via JVM shutdown hook and not explicitly called.
* </p>
*/
public static synchronized void shutdown() {
container.shutdown();
}
private static Database createInternal(DatabaseConfig config) {
return getContainer(config.getContainerConfig()).createServer(config);
}
/**
* Get the EbeanContainer initialising it if necessary.
*
* @param containerConfig the configuration controlling clustering communication
*/
private static SpiContainer getContainer(ContainerConfig containerConfig) {
// thread safe in that all calling methods are synchronized
if (container != null) {
return container;
}
if (containerConfig == null) {
// effectively load configuration from ebean.properties
Properties properties = DbPrimary.getProperties();
containerConfig = new ContainerConfig();
containerConfig.loadFromProperties(properties);
}
container = createContainer(containerConfig);
return container;
}
/**
* Create the container instance using the configuration.
*/
protected static SpiContainer createContainer(ContainerConfig containerConfig) {
Iterator<SpiContainerFactory> factories = ServiceLoader.load(SpiContainerFactory.class).iterator();
if (factories.hasNext()) {
return factories.next().create(containerConfig);
}
throw new IllegalStateException("Service loader didn't find a SpiContainerFactory?");
}
}
@@ -0,0 +1,140 @@
package io.ebean;
import io.ebean.config.BeanNotEnhancedException;
import io.ebean.datasource.DataSourceConfigurationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
/**
* Holds Database instances.
*/
final class DbContext {
private static final Logger logger = LoggerFactory.getLogger(DbContext.class);
static {
EbeanVersion.getVersion();
}
private static final DbContext INSTANCE = new DbContext();
/**
* Cache for fast concurrent read access.
*/
private final ConcurrentHashMap<String, Database> concMap = new ConcurrentHashMap<>();
/**
* Cache for synchronized read, creation and put. Protected by the monitor object.
*/
private final HashMap<String, Database> syncMap = new HashMap<>();
private final Object monitor = new Object();
/**
* The 'default' Database.
*/
private Database defaultDatabase;
private DbContext() {
try {
if (!DbPrimary.isSkip()) {
// look to see if there is a default server defined
String defaultName = DbPrimary.getDefaultServerName();
logger.debug("defaultName:{}", defaultName);
if (defaultName != null && !defaultName.trim().isEmpty()) {
defaultDatabase = getWithCreate(defaultName.trim());
}
}
} catch (BeanNotEnhancedException e) {
throw e;
} catch (DataSourceConfigurationException e) {
String msg = "Configuration error creating DataSource for the default Database." +
" This typically means a missing application-test.yaml or missing ebean-test dependency." +
" See https://ebean.io/docs/trouble-shooting#datasource";
throw new DataSourceConfigurationException(msg, e);
} catch (Throwable e) {
logger.error("Error trying to create the default Database", e);
throw new RuntimeException(e);
}
}
/**
* Return the shared singleton instance.
*/
static DbContext getInstance() {
return INSTANCE;
}
/**
* Return the default database.
*/
Database getDefault() {
if (defaultDatabase == null) {
String msg = "The default Database has not been defined?";
msg += " This is normally set via the ebean.datasource.default property.";
msg += " Otherwise it should be registered programmatically via registerServer()";
throw new PersistenceException(msg);
}
return defaultDatabase;
}
/**
* Return the database by name.
*/
Database get(String name) {
if (name == null || name.isEmpty()) {
return defaultDatabase;
}
// non-synchronized read
Database server = concMap.get(name);
if (server != null) {
return server;
}
// synchronized read, create and put
return getWithCreate(name);
}
/**
* Synchronized read, create and put of Databases.
*/
private Database getWithCreate(String name) {
synchronized (monitor) {
Database server = syncMap.get(name);
if (server == null) {
// register when creating server this way
server = EbeanServerFactory.create(name);
register(server, false);
}
return server;
}
}
/**
* Register a server so we can get it by its name.
*/
void register(Database server, boolean isDefault) {
registerWithName(server.getName(), server, isDefault);
}
private void registerWithName(String name, Database server, boolean isDefault) {
synchronized (monitor) {
concMap.put(name, server);
syncMap.put(name, server);
if (isDefault) {
defaultDatabase = server;
}
}
}
Database mock(String name, Database server, boolean defaultServer) {
Database originalPrimaryServer = this.defaultDatabase;
registerWithName(name, server, defaultServer);
return originalPrimaryServer;
}
}
@@ -0,0 +1,76 @@
package io.ebean;
import io.avaje.config.Config;
import java.util.Properties;
/**
* Provides singleton state for the default database.
* <p/>
* Intended for internal use as part of bootup, construction, registration of the default database.
*/
class DbPrimary {
private static String defaultServerName;
private static boolean skip;
/**
* Set whether to skip automatically creating the primary database.
*/
static synchronized void setSkip(boolean skip) {
DbPrimary.skip = skip;
}
/**
* Return true to skip automatically creating the primary database.
*/
static synchronized boolean isSkip() {
return skip;
}
/**
* Return the default database name.
*/
static synchronized String getDefaultServerName() {
getProperties();
return defaultServerName;
}
/**
* Return the default configuration Properties.
*/
static synchronized Properties getProperties() {
if (defaultServerName == null) {
defaultServerName = determineDefaultServerName();
}
return Config.asProperties();
}
/**
* Determine and return the default server name checking system environment variables and then global properties.
*/
private static String determineDefaultServerName() {
String defaultServerName = System.getenv("EBEAN_DB");
defaultServerName = System.getProperty("db", defaultServerName);
defaultServerName = System.getProperty("ebean_db", defaultServerName);
if (isEmpty(defaultServerName)) {
defaultServerName = Config.get("datasource.default", null);
if (isEmpty(defaultServerName)) {
defaultServerName = Config.get("ebean.default.datasource", null);
}
}
if (defaultServerName == null) {
defaultServerName = "db";
}
return defaultServerName;
}
/**
* Return true if the string is null or empty.
*/
private static boolean isEmpty(String value) {
return value == null || value.trim().isEmpty();
}
}
@@ -0,0 +1,94 @@
package io.ebean;
/**
* Bean holding the details to update the document store.
*/
public final class DocStoreQueueEntry {
/**
* Action to either update or delete a document from the index.
*/
public enum Action {
/**
* Action is to update a document in the doc store.
*/
INDEX(1),
/**
* Action is to delete a document from the doc store..
*/
DELETE(2),
/**
* An update is required based on a change to a nested/embedded object at a given path.
*/
NESTED(3);
int value;
Action(int value) {
this.value = value;
}
/**
* Return the value associated with this action type.
*/
public int getValue() {
return value;
}
}
private final Action type;
private final String queueId;
private final String path;
private final Object beanId;
/**
* Construct for an INDEX or DELETE action.
*/
public DocStoreQueueEntry(Action type, String queueId, Object beanId) {
this(type, queueId, null, beanId);
}
/**
* Construct for an NESTED/embedded path invalidation action.
*/
public DocStoreQueueEntry(Action type, String queueId, String path, Object beanId) {
this.type = type;
this.queueId = queueId;
this.path = path;
this.beanId = beanId;
}
/**
* Return the event type.
*/
public Action getType() {
return type;
}
/**
* Return the associate queueId.
*/
public String getQueueId() {
return queueId;
}
/**
* Return the path if this is a nested update.
*/
public String getPath() {
return path;
}
/**
* Return the bean id (which matches the document id).
*/
public Object getBeanId() {
return beanId;
}
}
@@ -0,0 +1,310 @@
package io.ebean;
import io.ebean.docstore.DocQueryContext;
import io.ebean.docstore.RawDoc;
import javax.annotation.Nullable;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Document storage operations.
*/
public interface DocumentStore {
/**
* Update the associated document store using the result of the query.
* <p>
* This will execute the query against the database creating a document for each
* bean graph and sending this to the document store.
* </p>
* <p>
* Note that the select and fetch paths of the query is set for you to match the
* document structure needed based on <code>@DocStore</code> and <code>@DocStoreEmbedded</code>
* so what this query requires is the predicates only.
* </p>
* <p>
* This query will be executed using findEach so it is safe to use a query
* that will fetch a lot of beans. The default bulkBatchSize is used.
* </p>
*
* @param query The query that selects object to send to the document store.
*/
<T> void indexByQuery(Query<T> query);
/**
* Update the associated document store index using the result of the query additionally specifying a
* bulkBatchSize to use for sending the messages to ElasticSearch.
*
* @param query The query that selects object to send to the document store.
* @param bulkBatchSize The batch size to use when bulk sending to the document store.
*/
<T> void indexByQuery(Query<T> query, int bulkBatchSize);
/**
* Update the document store for all beans of this type.
* <p>
* This is the same as indexByQuery where the query has no predicates and so fetches all rows.
* </p>
*/
void indexAll(Class<?> beanType);
/**
* Return the bean by fetching it's content from the document store.
* If the document is not found null is returned.
* <p>
* Typically this is called indirectly by findOne() on the query.
* </p>
* <pre>{@code
*
* Customer customer =
* database.find(Customer.class)
* .setUseDocStore(true)
* .setId(42)
* .findOne();
*
* }</pre>
*/
@Nullable
<T> T find(DocQueryContext<T> request);
/**
* Execute the find list query. This request is prepared to execute secondary queries.
* <p>
* Typically this is called indirectly by findList() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* List<Customer> newCustomers =
* database.find(Customer.class)
* .setUseDocStore(true)
* .where().eq("status, Customer.Status.NEW)
* .findList();
*
* }</pre>
*/
<T> List<T> findList(DocQueryContext<T> request);
/**
* Execute the query against the document store returning the paged list.
* <p>
* The query should have <code>firstRow</code> or <code>maxRows</code> set prior to calling this method.
* </p>
* <p>
* Typically this is called indirectly by findPagedList() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* PagedList<Customer> newCustomers =
* database.find(Customer.class)
* .setUseDocStore(true)
* .where().eq("status, Customer.Status.NEW)
* .setMaxRows(50)
* .findPagedList();
*
* }</pre>
*/
<T> PagedList<T> findPagedList(DocQueryContext<T> request);
/**
* Execute the query against the document store with the expectation of a large set of results
* that are processed in a scrolling resultSet fashion.
* <p>
* For example, with the ElasticSearch doc store this uses SCROLL.
* </p>
* <p>
* Typically this is called indirectly by findEach() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* database.find(Order.class)
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEach((Order order) -> {
* // process the bean ...
* });
*
* }</pre>
*/
<T> void findEach(DocQueryContext<T> query, Consumer<T> consumer);
/**
* Execute the query against the document store with the expectation of a large set of results
* that are processed in a scrolling resultSet fashion.
* <p>
* Unlike findEach() this provides the opportunity to stop iterating through the large query.
* </p>
* <p>
* For example, with the ElasticSearch doc store this uses SCROLL.
* </p>
* <p>
* Typically this is called indirectly by findEachWhile() on the query that has setUseDocStore(true).
* </p>
* <pre>{@code
*
* database.find(Order.class)
* .setUseDocStore(true)
* .where()... // perhaps add predicates
* .findEachWhile(new Predicate<Order>() {
* Override
* public void accept(Order bean) {
* // process the bean
*
* // return true to continue, false to stop
* // boolean shouldContinue = ...
* return shouldContinue;
* }
* });
*
* }</pre>
*/
<T> void findEachWhile(DocQueryContext<T> query, Predicate<T> consumer);
/**
* Find each processing raw documents.
*
* @param indexNameType The full index name and type
* @param rawQuery The query to execute
* @param consumer Consumer to process each document
*/
void findEach(String indexNameType, String rawQuery, Consumer<RawDoc> consumer);
/**
* Find each processing raw documents stopping when the predicate returns false.
*
* @param indexNameType The full index name and type
* @param rawQuery The query to execute
* @param consumer Consumer to process each document until false is returned
*/
void findEachWhile(String indexNameType, String rawQuery, Predicate<RawDoc> consumer);
/**
* Process the queue entries sending updates to the document store or queuing them for later processing.
*/
long process(List<DocStoreQueueEntry> queueEntries) throws IOException;
/**
* Drop the index from the document store (similar to DDL drop table).
* <pre>{@code
*
* DocumentStore documentStore = database.docStore();
*
* documentStore.dropIndex("product_copy");
*
* }</pre>
*/
void dropIndex(String indexName);
/**
* Create an index given a mapping file as a resource in the classPath (similar to DDL create table).
* <pre>{@code
*
* DocumentStore documentStore = database.docStore();
*
* // uses product_copy.mapping.json resource
* // ... to define mappings for the index
*
* documentStore.createIndex("product_copy", null);
*
* }</pre>
*
* @param indexName the name of the new index
* @param alias the alias of the index
*/
void createIndex(String indexName, String alias);
/**
* Modify the settings on an index.
* <p>
* For example, this can be used be used to set elasticSearch refresh_interval
* on an index before a bulk update.
* </p>
* <pre>{@code
*
* // refresh_interval -1 ... disable refresh while bulk loading
*
* Map<String,Object> settings = new LinkedHashMap<>();
* settings.put("refresh_interval", "-1");
*
* documentStore.indexSettings("product", settings);
*
* }</pre>
* <pre>{@code
*
* // refresh_interval 1s ... restore after bulk loading
*
* Map<String,Object> settings = new LinkedHashMap<>();
* settings.put("refresh_interval", "1s");
*
* documentStore.indexSettings("product", settings);
*
* }</pre>
*
* @param indexName the name of the index to update settings on
* @param settings the settings to set on the index
*/
void indexSettings(String indexName, Map<String, Object> settings);
/**
* Copy the index to a new index.
* <p>
* This copy process does not use the database but instead will copy from the source index to a destination index.
* </p>
* <pre>{@code
*
* long copyCount = documentStore.copyIndex(Product.class, "product_copy");
*
* }</pre>
*
* @param beanType The bean type of the source index
* @param newIndex The name of the index to copy to
* @return the number of documents copied to the new index
*/
long copyIndex(Class<?> beanType, String newIndex);
/**
* Copy entries from an index to a new index but limiting to documents that have been
* modified since the sinceEpochMillis time.
* <p>
* To support this the document needs to have a <code>@WhenModified</code> property.
* </p>
* <pre>{@code
*
* long copyCount = documentStore.copyIndex(Product.class, "product_copy", sinceMillis);
*
* }</pre>
*
* @param beanType The bean type of the source index
* @param newIndex The name of the index to copy to
* @return the number of documents copied to the new index
*/
long copyIndex(Class<?> beanType, String newIndex, long sinceEpochMillis);
/**
* Copy from a source index to a new index taking only the documents
* matching the given query.
* <pre>{@code
*
* // predicates to select the source documents to copy
* Query<Product> query = database.find(Product.class)
* .where()
* .ge("whenModified", new Timestamp(since))
* .ge("name", "A")
* .lt("name", "D")
* .query();
*
* // copy from the source index to "product_copy" index
* long copyCount = documentStore.copyIndex(query, "product_copy", 1000);
*
* }</pre>
*
* @param query The query to select the source documents to copy
* @param newIndex The target index to copy the documents to
* @param bulkBatchSize The ElasticSearch bulk batch size, if 0 uses the default.
* @return The number of documents copied to the new index.
*/
long copyIndex(Query<?> query, String newIndex, int bulkBatchSize);
}
@@ -0,0 +1,160 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Query for performing native SQL queries that return DTO Bean's.
* <p>
* These beans are just normal classes. They must have public constructors
* and setters.
* <p>
* Constructors with arguments are used if the number of constructor arguments
* matches the number of columns in the resultSet.
* </p>
* <p>
* If the number of columns in the resultSet is greater than the largest constructor
* then the largest constructor is used for the first columns and remaining columns
* are mapped by setter methods.
* </p>
*
* <pre>{@code
*
* // CustomerDto is just a 'bean like' class
* // with public constructor(s) and public setter methods
*
* String sql = "select id, name from customer where name like :name and status_code = :status";
*
* List<CustomerDto> beans =
* DB.findDto(CustomerDto.class, sql)
* .setParameter("name", "Acme%")
* .setParameter("status", "ACTIVE")
* .findList();
*
* }</pre>
*/
public interface DtoQuery<T> {
/**
* Execute the query returning a list.
*/
@Nonnull
List<T> findList();
/**
* Execute the query iterating a row at a time.
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEach(Consumer<T> consumer);
/**
* Execute the query iterating a row at a time with the ability to stop consuming part way through.
* <p>
* Returning false after processing a row stops the iteration through the query results.
* </p>
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEachWhile(Predicate<T> consumer);
/**
* Execute the query returning a single bean.
*/
@Nullable
T findOne();
/**
* Execute the query returning an optional bean.
*/
@Nonnull
Optional<T> findOneOrEmpty();
/**
* Bind all the parameters using index positions.
* <p>
* Binds each parameter moving the index position each time.
* <p>
* A convenience for multiple calls to {@link #setParameter(Object)}
*/
DtoQuery<T> setParameters(Object... value);
/**
* Bind the next parameter using index position.
* <p>
* Bind the parameter using index position starting at 1 and incrementing.
* <p>
*/
DtoQuery<T> setParameter(Object value);
/**
* Bind the named parameter.
*/
DtoQuery<T> setParameter(String name, Object value);
/**
* Bind the parameter by its index position (1 based like JDBC).
*/
DtoQuery<T> setParameter(int position, Object value);
/**
* Set the index of the first row of the results to return.
*/
DtoQuery<T> setFirstRow(int firstRow);
/**
* Set the maximum number of query results to return.
*/
DtoQuery<T> setMaxRows(int maxRows);
/**
* When resultSet columns are not able to be mapped to a bean property then instead of
* throwing effectively skip reading that column.
*/
DtoQuery<T> setRelaxedMode();
/**
* Set a label on the query to make it easier to identify queries related to query execution statistics.
*
* @param label A label that is unique to the DTO bean type.
*/
DtoQuery<T> setLabel(String label);
/**
* Set the profile location of this query. This is used to relate query execution metrics
* back to a location like a specific line of code.
*/
DtoQuery<T> setProfileLocation(ProfileLocation profileLocation);
/**
* Set a timeout on this query.
* <p>
* This will typically result in a call to setQueryTimeout() on a
* preparedStatement. If the timeout occurs an exception will be thrown - this
* will be a SQLException wrapped up in a PersistenceException.
* </p>
*
* @param secs the query timeout limit in seconds. Zero means there is no limit.
*/
DtoQuery<T> setTimeout(int secs);
/**
* A hint which for JDBC translates to the Statement.fetchSize().
* <p>
* Gives the JDBC driver a hint as to the number of rows that should be
* fetched from the database when more rows are needed for ResultSet.
* </p>
*/
DtoQuery<T> setBufferFetchSizeHint(int bufferFetchSizeHint);
/**
* Use the explicit transaction to execute the query.
*/
DtoQuery<T> usingTransaction(Transaction transaction);
}
@@ -0,0 +1,15 @@
package io.ebean;
/**
* Thrown when a duplicate is attempted on a unique constraint.
*/
public class DuplicateKeyException extends DataIntegrityException {
private static final long serialVersionUID = -4771932723285724817L;
/**
* Create with a message and cause.
*/
public DuplicateKeyException(String message, Throwable cause) {
super(message, cause);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,15 @@
package io.ebean;
/**
* Deprecated - please migrate to <code>io.ebean.Database</code>.
* Provides the API for fetching and saving beans to a particular Database.
* <p>
* Effectively this is an alias for {@link Database} which is now the new
* and improved name for EbeanServer.
* <p>
* The preference is to use DB and Database rather than Ebean and EbeanServer.
*/
@Deprecated
public interface EbeanServer extends Database {
}
@@ -0,0 +1,77 @@
package io.ebean;
import io.ebean.config.ContainerConfig;
import io.ebean.config.ServerConfig;
import io.ebean.service.SpiContainer;
import io.ebean.service.SpiContainerFactory;
import javax.persistence.PersistenceException;
import java.util.Iterator;
import java.util.Properties;
import java.util.ServiceLoader;
/**
* Deprecated - please migrate to DatabaseFactory.
* <p>
* Creates EbeanServer instances.
* <p>
* This uses either a ServerConfig or properties in the ebean.properties file to
* configure and create a EbeanServer instance.
* </p>
* <p>
* The EbeanServer instance can either be registered with the Ebean singleton or
* not. The Ebean singleton effectively holds a map of EbeanServers by a name.
* If the EbeanServer is registered with the Ebean singleton you can retrieve it
* later via {@link Ebean#getServer(String)}.
* </p>
* <p>
* One EbeanServer can be nominated as the 'default/primary' EbeanServer. Many
* methods on the Ebean singleton such as {@link Ebean#find(Class)} are just a
* convenient way of using the 'default/primary' EbeanServer.
* </p>
*/
@Deprecated
public class EbeanServerFactory {
/**
* Initialise the container with clustering configuration.
* <p>
* Call this prior to creating any EbeanServer instances or alternatively set the
* ContainerConfig on the ServerConfig when creating the first EbeanServer instance.
*/
public static synchronized void initialiseContainer(ContainerConfig containerConfig) {
DatabaseFactory.initialiseContainer(containerConfig);
}
/**
* Create using ebean.properties to configure the database.
*/
public static synchronized EbeanServer create(String name) {
return (EbeanServer)DatabaseFactory.create(name);
}
/**
* Create using the ServerConfig object to configure the database.
*/
public static synchronized EbeanServer create(ServerConfig config) {
return (EbeanServer)DatabaseFactory.create(config);
}
/**
* Create using the ServerConfig additionally specifying a classLoader to use as the context class loader.
*/
public static synchronized EbeanServer createWithContextClassLoader(ServerConfig config, ClassLoader classLoader) {
return (EbeanServer)DatabaseFactory.createWithContextClassLoader(config, classLoader);
}
/**
* Shutdown gracefully all EbeanServers cleaning up any resources as required.
* <p>
* This is typically invoked via JVM shutdown hook and not explicitly called.
* </p>
*/
public static synchronized void shutdown() {
DatabaseFactory.shutdown();
}
}
@@ -0,0 +1,48 @@
package io.ebean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
/**
* Class to determine the ebean version.
*
* @author Roland Praml, FOCONIS AG
*/
public class EbeanVersion {
private static final Logger logger = LoggerFactory.getLogger(EbeanVersion.class);
private static String version = "unknown";
static {
try {
Properties prop = new Properties();
try (InputStream in = DB.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties")) {
if (in != null) {
prop.load(in);
in.close();
version = prop.getProperty("version");
}
}
logger.info("ebean version: {}", version);
} catch (IOException e) {
logger.warn("Could not determine ebean version: {}", e.getMessage());
}
}
private EbeanVersion() {
// hide
}
/**
* Returns the ebean version (read from /META-INF/maven/io.ebean/ebean/pom.properties)
*/
public static String getVersion() {
return version;
}
}
@@ -0,0 +1,74 @@
package io.ebean;
import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Future;
/**
* An empty PagedList.
* <p>
* For use in application code when we need to return a PagedList but don't want to
* execute a query.
* </p>
*
* <pre>{@code
*
* PagedList<Customer> empty = PagedList.emptyList();
*
* }</pre>
*/
public class EmptyPagedList<T> implements PagedList<T> {
@Override
public void loadCount() {
// do nothing
}
@Nonnull
@Override
public Future<Integer> getFutureCount() {
return null;
}
@Nonnull
@Override
public List<T> getList() {
return Collections.emptyList();
}
@Override
public int getTotalCount() {
return 0;
}
@Override
public int getTotalPageCount() {
return 0;
}
@Override
public int getPageSize() {
return 0;
}
@Override
public int getPageIndex() {
return 0;
}
@Override
public boolean hasNext() {
return false;
}
@Override
public boolean hasPrev() {
return false;
}
@Override
public String getDisplayXtoYofZ(String to, String of) {
return "";
}
}
@@ -0,0 +1,93 @@
package io.ebean;
/**
* Query by Example expression.
* <p>
* Pass in an example entity and for each non-null scalar properties an
* expression is added.
* </p>
* <p>
* By Default this case sensitive, will ignore numeric zero values and will use
* a Like for string values (you must put in your own wildcards).
* </p>
* <p>
* To get control over the options you can create an ExampleExpression and set
* those options such as case insensitive etc.
* </p>
*
* <pre>{@code
* // create an example bean and set the properties
* // with the query parameters you want
* Customer example = new Customer();
* example.setName("Rob%");
* example.setNotes("%something%");
*
* List<Customer> list =
* DB.find(Customer.class)
* .where()
* // pass the bean into the where() clause
* .exampleLike(example)
* // you can add other expressions to the same query
* .gt("id", 2)
* .findList();
*
* }</pre>
*
* Similarly you can create an ExampleExpression
*
* <pre>{@code
*
* Customer example = new Customer();
* example.setName("Rob%");
* example.setNotes("%something%");
*
* // create a ExampleExpression with more control
* ExampleExpression qbe = new ExampleExpression(example, true, LikeType.EQUAL_TO)
* .includeZeros();
*
* List<Customer> list =
* DB.find(Customer.class)
* .where()
* .add(qbe)
* .findList();
*
* }</pre>
*/
public interface ExampleExpression extends Expression {
/**
* By calling this method zero value properties are going to be included in
* the expression.
* <p>
* By default numeric zero values are excluded as they can result from
* primitive int and long types.
* </p>
*/
ExampleExpression includeZeros();
/**
* Set case insensitive to true.
*/
ExampleExpression caseInsensitive();
/**
* Use startsWith expression for string properties.
*/
ExampleExpression useStartsWith();
/**
* Use contains expression for string properties.
*/
ExampleExpression useContains();
/**
* Use endsWith expression for string properties.
*/
ExampleExpression useEndsWith();
/**
* Use equal to expression for string properties.
*/
ExampleExpression useEqualTo();
}
+366
View File
@@ -0,0 +1,366 @@
package io.ebean;
import java.util.Collection;
import java.util.Map;
/**
* Expression factory for creating standard expressions for WHERE and HAVING
* clauses.
* <p>
* Generally you will only need to use this object for creating OR, JUNCTION or
* CONJUNCTION expressions. To create simple expressions you will most likely
* just use the methods on the ExpressionList object that is returned via
* {@link Query#where()}.
* </p>
* <p>
* This provides a convenient way to create expressions for the default
* database.
* <p>
* See also {@link DB#getExpressionFactory()}
* </p>
* <p>
* Creates standard common expressions for using in a Query Where or Having
* clause.
* </p>
*
* @see Query#where()
*/
public class Expr {
private Expr() {
}
/**
* Equal To - property equal to the given value.
*/
public static Expression eq(String propertyName, Object value) {
return DB.getExpressionFactory().eq(propertyName, value);
}
/**
* Not Equal To - property not equal to the given value.
*/
public static Expression ne(String propertyName, Object value) {
return DB.getExpressionFactory().ne(propertyName, value);
}
/**
* Case Insensitive Equal To - property equal to the given value (typically
* using a lower() function to make it case insensitive).
*/
public static Expression ieq(String propertyName, String value) {
return DB.getExpressionFactory().ieq(propertyName, value);
}
/**
* In Range - {@code property >= value1 and property < value2}.
* <p>
* Unlike Between inRange is "half open" and usually more useful for use with dates or timestamps.
* </p>
*/
public static Expression inRange(String propertyName, Object value1, Object value2) {
return DB.getExpressionFactory().inRange(propertyName, value1, value2);
}
/**
* Between - property between the two given values.
*/
public static Expression between(String propertyName, Object value1, Object value2) {
return DB.getExpressionFactory().between(propertyName, value1, value2);
}
/**
* Between - value between two given properties.
*/
public static Expression between(String lowProperty, String highProperty, Object value) {
return DB.getExpressionFactory().betweenProperties(lowProperty, highProperty, value);
}
/**
* Greater Than - property greater than the given value.
*/
public static Expression gt(String propertyName, Object value) {
return DB.getExpressionFactory().gt(propertyName, value);
}
/**
* Greater Than or Equal to - property greater than or equal to the given
* value.
*/
public static Expression ge(String propertyName, Object value) {
return DB.getExpressionFactory().ge(propertyName, value);
}
/**
* Less Than - property less than the given value.
*/
public static Expression lt(String propertyName, Object value) {
return DB.getExpressionFactory().lt(propertyName, value);
}
/**
* Less Than or Equal to - property less than or equal to the given value.
*/
public static Expression le(String propertyName, Object value) {
return DB.getExpressionFactory().le(propertyName, value);
}
/**
* Is Null - property is null.
*/
public static Expression isNull(String propertyName) {
return DB.getExpressionFactory().isNull(propertyName);
}
/**
* Is Not Null - property is not null.
*/
public static Expression isNotNull(String propertyName) {
return DB.getExpressionFactory().isNotNull(propertyName);
}
/**
* Case insensitive {@link #exampleLike(Object)}
*/
public static ExampleExpression iexampleLike(Object example) {
return DB.getExpressionFactory().iexampleLike(example);
}
/**
* Create the query by Example expression which is case sensitive and using
* LikeType.RAW (you need to add you own wildcards % and _).
*/
public static ExampleExpression exampleLike(Object example) {
return DB.getExpressionFactory().exampleLike(example);
}
/**
* Create the query by Example expression specifying more options.
*/
public static ExampleExpression exampleLike(Object example, boolean caseInsensitive, LikeType likeType) {
return DB.getExpressionFactory().exampleLike(example, caseInsensitive, likeType);
}
/**
* Like - property like value where the value contains the SQL wild card
* characters % (percentage) and _ (underscore).
*/
public static Expression like(String propertyName, String value) {
return DB.getExpressionFactory().like(propertyName, value);
}
/**
* Case insensitive Like - property like value where the value contains the
* SQL wild card characters % (percentage) and _ (underscore). Typically uses
* a lower() function to make the expression case insensitive.
*/
public static Expression ilike(String propertyName, String value) {
return DB.getExpressionFactory().ilike(propertyName, value);
}
/**
* Starts With - property like value%.
*/
public static Expression startsWith(String propertyName, String value) {
return DB.getExpressionFactory().startsWith(propertyName, value);
}
/**
* Case insensitive Starts With - property like value%. Typically uses a
* lower() function to make the expression case insensitive.
*/
public static Expression istartsWith(String propertyName, String value) {
return DB.getExpressionFactory().istartsWith(propertyName, value);
}
/**
* Ends With - property like %value.
*/
public static Expression endsWith(String propertyName, String value) {
return DB.getExpressionFactory().endsWith(propertyName, value);
}
/**
* Case insensitive Ends With - property like %value. Typically uses a lower()
* function to make the expression case insensitive.
*/
public static Expression iendsWith(String propertyName, String value) {
return DB.getExpressionFactory().iendsWith(propertyName, value);
}
/**
* Contains - property like %value%.
*/
public static Expression contains(String propertyName, String value) {
return DB.getExpressionFactory().contains(propertyName, value);
}
/**
* Case insensitive Contains - property like %value%. Typically uses a lower()
* function to make the expression case insensitive.
*/
public static Expression icontains(String propertyName, String value) {
return DB.getExpressionFactory().icontains(propertyName, value);
}
/**
* For collection properties that are empty (have not existing elements).
*/
public static Expression isEmpty(String propertyName) {
return DB.getExpressionFactory().isEmpty(propertyName);
}
/**
* For collection properties that are not empty (have existing elements).
*/
public static Expression isNotEmpty(String propertyName) {
return DB.getExpressionFactory().isNotEmpty(propertyName);
}
/**
* In - property has a value in the array of values.
*/
public static Expression in(String propertyName, Object[] values) {
return DB.getExpressionFactory().in(propertyName, values);
}
/**
* In - using a subQuery.
*/
public static Expression in(String propertyName, Query<?> subQuery) {
return DB.getExpressionFactory().in(propertyName, subQuery);
}
/**
* In - property has a value in the collection of values.
*/
public static Expression in(String propertyName, Collection<?> values) {
return DB.getExpressionFactory().in(propertyName, values);
}
/**
* In where null or empty values means that no predicate is added to the query.
* <p>
* That is, only add the IN predicate if the values are not null or empty.
* <p>
* Without this we typically need to code an <code>if</code> block to only add
* the IN predicate if the collection is not empty like:
* </p>
*
* <h3>Without inOrEmpty()</h3>
* <pre>{@code
*
* query.where() // add some predicates
* .eq("status", Status.NEW);
*
* if (ids != null && !ids.isEmpty()) {
* query.where().in("customer.id", ids);
* }
*
* query.findList();
*
* }</pre>
*
* <h3>Using inOrEmpty()</h3>
* <pre>{@code
*
* query.where()
* .eq("status", Status.NEW)
* .inOrEmpty("customer.id", ids)
* .findList();
*
* }</pre>
*/
public static Expression inOrEmpty(String propertyName, Collection<?> values) {
return DB.getExpressionFactory().inOrEmpty(propertyName, values);
}
/**
* Id Equal to - ID property is equal to the value.
*/
public static Expression idEq(Object value) {
return DB.getExpressionFactory().idEq(value);
}
/**
* All Equal - Map containing property names and their values.
* <p>
* Expression where all the property names in the map are equal to the
* corresponding value.
* </p>
*
* @param propertyMap a map keyed by property names.
*/
public static Expression allEq(Map<String, Object> propertyMap) {
return DB.getExpressionFactory().allEq(propertyMap);
}
/**
* Add raw expression with a single parameter.
* <p>
* The raw expression should contain a single ? at the location of the
* parameter.
* </p>
*/
public static Expression raw(String raw, Object value) {
return DB.getExpressionFactory().raw(raw, value);
}
/**
* Add raw expression with an array of parameters.
* <p>
* The raw expression should contain the same number of ? as there are
* parameters.
* </p>
*/
public static Expression raw(String raw, Object[] values) {
return DB.getExpressionFactory().raw(raw, values);
}
/**
* Add raw expression with no parameters.
*/
public static Expression raw(String raw) {
return DB.getExpressionFactory().raw(raw);
}
/**
* And - join two expressions with a logical and.
*/
public static Expression and(Expression expOne, Expression expTwo) {
return DB.getExpressionFactory().and(expOne, expTwo);
}
/**
* Or - join two expressions with a logical or.
*/
public static Expression or(Expression expOne, Expression expTwo) {
return DB.getExpressionFactory().or(expOne, expTwo);
}
/**
* Negate the expression (prefix it with NOT).
*/
public static Expression not(Expression exp) {
return DB.getExpressionFactory().not(exp);
}
/**
* Return a list of expressions that will be joined by AND's.
*/
public static <T> Junction<T> conjunction(Query<T> query) {
return DB.getExpressionFactory().conjunction(query);
}
/**
* Return a list of expressions that will be joined by OR's.
*/
public static <T> Junction<T> disjunction(Query<T> query) {
return DB.getExpressionFactory().disjunction(query);
}
}
@@ -0,0 +1,8 @@
package io.ebean;
/**
* An expression that is part of a WHERE or HAVING clause.
*/
public interface Expression {
}
@@ -0,0 +1,563 @@
package io.ebean;
import io.ebean.search.Match;
import io.ebean.search.MultiMatch;
import io.ebean.search.TextCommonTerms;
import io.ebean.search.TextQueryString;
import io.ebean.search.TextSimple;
import java.util.Collection;
import java.util.Map;
/**
* Expression factory for creating standard expressions.
* <p>
* Creates standard common expressions for using in a Query Where or Having
* clause.
* </p>
* <p>
* You will often not use this class directly but instead just add expressions
* via the methods on ExpressionList such as
* {@link ExpressionList#gt(String, Object)}.
* </p>
* <p>
* The ExpressionList is returned from {@link Query#where()}.
* </p>
* <pre>{@code
* // Example: fetch orders where status equals new or orderDate > lastWeek.
*
* Expression newOrLastWeek =
* Expr.or(Expr.eq("status", Order.Status.NEW),
* Expr.gt("orderDate", lastWeek));
*
* List<Order> list = DB.find(Order.class)
* .where().add(newOrLastWeek)
* .findList();
* ...
* }</pre>
*
* @see Query#where()
*/
public interface ExpressionFactory {
/**
* Path exists - for the given path in a JSON document.
*/
Expression jsonExists(String propertyName, String path);
/**
* Path does not exist - for the given path in a JSON document.
*/
Expression jsonNotExists(String propertyName, String path);
/**
* Equal to - for the given path in a JSON document.
*/
Expression jsonEqualTo(String propertyName, String path, Object val);
/**
* Not Equal to - for the given path in a JSON document.
*/
Expression jsonNotEqualTo(String propertyName, String path, Object val);
/**
* Greater than - for the given path in a JSON document.
*/
Expression jsonGreaterThan(String propertyName, String path, Object val);
/**
* Greater than or equal to - for the given path in a JSON document.
*/
Expression jsonGreaterOrEqual(String propertyName, String path, Object val);
/**
* Less than - for the given path in a JSON document.
*/
Expression jsonLessThan(String propertyName, String path, Object val);
/**
* Less than or equal to - for the given path in a JSON document.
*/
Expression jsonLessOrEqualTo(String propertyName, String path, Object val);
/**
* Between - for the given path in a JSON document.
*/
Expression jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue);
/**
* Array contains all the given values.
* <p>
* Array support is effectively limited to Postgres at this time.
* </p>
*/
Expression arrayContains(String propertyName, Object... values);
/**
* Array does not contain the given values.
* <p>
* Array support is effectively limited to Postgres at this time.
* </p>
*/
Expression arrayNotContains(String propertyName, Object... values);
/**
* Array is empty - for the given array property.
* <p>
* Array support is effectively limited to Postgres at this time.
* </p>
*/
Expression arrayIsEmpty(String propertyName);
/**
* Array is not empty - for the given array property.
* <p>
* Array support is effectively limited to Postgres at this time.
* </p>
*/
Expression arrayIsNotEmpty(String propertyName);
/**
* Equal To - property equal to the given value.
*/
Expression eq(String propertyName, Object value);
/**
* Equal To or Null - property equal to the given value or null.
*/
Expression eqOrNull(String propertyName, Object value);
/**
* Not Equal To - property not equal to the given value.
*/
Expression ne(String propertyName, Object value);
/**
* Case Insensitive Equal To - property equal to the given value (typically
* using a lower() function to make it case insensitive).
*/
Expression ieq(String propertyName, String value);
/**
* Case Insensitive Not Equal To - property not equal to the given value (typically
* using a lower() function to make it case insensitive).
*/
Expression ine(String propertyName, String value);
/**
* Case Insensitive Equal To that allows for named parameter use.
*/
Expression ieqObject(String propertyName, Object value);
/**
* Case Insensitive Not Equal To that allows for named parameter use.
*/
Expression ineObject(String propertyName, Object value);
/**
* In Range - {@code property >= value1 and property < value2}.
* <p>
* Unlike Between inRange is "half open" and usually more useful for use with dates or timestamps.
* </p>
*/
Expression inRange(String propertyName, Object value1, Object value2);
/**
* Value in Range between 2 properties.
*
* <pre>{@code
*
* .startDate.inRangeWith(endDate, now)
*
* // which equates to
* startDate <= now and (endDate > now or endDate is null)
*
* }</pre>
*
* <p>
* This is a convenience expression combining a number of simple expressions.
* The most common use of this could be called "effective dating" where 2 date or
* timestamp columns represent the date range in which
*/
Expression inRangeWith(String lowProperty, String highProperty, Object value);
/**
* Between - property between the two given values.
*/
Expression between(String propertyName, Object value1, Object value2);
/**
* Between - value between two given properties.
*/
Expression betweenProperties(String lowProperty, String highProperty, Object value);
/**
* Greater Than Or Null - property greater than the given value or null.
* <p>
* A convenient expression combining GT and Is Null. Most often useful for range
* expressions where the top range value is nullable.
*/
Expression gtOrNull(String propertyName, Object value);
/**
* Greater than or Equal to OR Null <code> >= or null </code>
* <p>
* A convenient expression combining GE and Is Null. Most often useful for range
* expressions where the top range value is nullable.
*/
Expression geOrNull(String propertyName, Object value);
/**
* Greater Than - property greater than the given value.
*/
Expression gt(String propertyName, Object value);
/**
* Greater Than or Equal to - property greater than or equal to the given
* value.
*/
Expression ge(String propertyName, Object value);
/**
* Less Than or Null - property less than the given value or null.
* <p>
* A convenient expression combining LT and Is Null. Most often useful for range
* expressions where the bottom range value is nullable.
*/
Expression ltOrNull(String propertyName, Object value);
/**
* Less Than or Equal to OR Null <code> <= or null </code>
* <p>
* A convenient expression combining LE and Is Null. Most often useful for range
* expressions where the bottom range value is nullable.
*/
Expression leOrNull(String propertyName, Object value);
/**
* Less Than - property less than the given value.
*/
Expression lt(String propertyName, Object value);
/**
* Less Than or Equal to - property less than or equal to the given value.
*/
Expression le(String propertyName, Object value);
/**
* Is Null - property is null.
*/
Expression isNull(String propertyName);
/**
* Is Not Null - property is not null.
*/
Expression isNotNull(String propertyName);
/**
* Case insensitive {@link #exampleLike(Object)}
*/
ExampleExpression iexampleLike(Object example);
/**
* Create the query by Example expression which is case sensitive and using
* LikeType.RAW (you need to add you own wildcards % and _).
*/
ExampleExpression exampleLike(Object example);
/**
* Create the query by Example expression specifying more options.
*/
ExampleExpression exampleLike(Object example, boolean caseInsensitive, LikeType likeType);
/**
* Like with support for named parameters.
*/
Expression like(String propertyName, Object value, boolean caseInsensitive, LikeType likeType);
/**
* Like - property like value where the value contains the SQL wild card
* characters % (percentage) and _ (underscore).
*/
Expression like(String propertyName, String value);
/**
* Case insensitive Like - property like value where the value contains the
* SQL wild card characters % (percentage) and _ (underscore). Typically uses
* a lower() function to make the expression case insensitive.
*/
Expression ilike(String propertyName, String value);
/**
* Starts With - property like value%.
*/
Expression startsWith(String propertyName, String value);
/**
* Case insensitive Starts With - property like value%. Typically uses a
* lower() function to make the expression case insensitive.
*/
Expression istartsWith(String propertyName, String value);
/**
* Ends With - property like %value.
*/
Expression endsWith(String propertyName, String value);
/**
* Case insensitive Ends With - property like %value. Typically uses a lower()
* function to make the expression case insensitive.
*/
Expression iendsWith(String propertyName, String value);
/**
* Contains - property like %value%.
*/
Expression contains(String propertyName, String value);
/**
* Case insensitive Contains - property like %value%. Typically uses a lower()
* function to make the expression case insensitive.
*/
Expression icontains(String propertyName, String value);
/**
* In expression using pairs of value objects.
*/
Expression inPairs(Pairs pairs);
/**
* In - property has a value in the array of values.
*/
Expression in(String propertyName, Object[] values);
/**
* In - using a subQuery.
*/
Expression in(String propertyName, Query<?> subQuery);
/**
* In - property has a value in the collection of values.
*/
Expression in(String propertyName, Collection<?> values);
/**
* In where null or empty values means that no predicate is added to the query.
* <p>
* That is, only add the IN predicate if the values are not null or empty.
* <p>
* Without this we typically need to code an <code>if</code> block to only add
* the IN predicate if the collection is not empty like:
* </p>
*
* <h3>Without inOrEmpty()</h3>
* <pre>{@code
*
* query.where() // add some predicates
* .eq("status", Status.NEW);
*
* if (ids != null && !ids.isEmpty()) {
* query.where().in("customer.id", ids);
* }
*
* query.findList();
*
* }</pre>
*
* <h3>Using inOrEmpty()</h3>
* <pre>{@code
*
* query.where()
* .eq("status", Status.NEW)
* .inOrEmpty("customer.id", ids)
* .findList();
*
* }</pre>
*/
Expression inOrEmpty(String propertyName, Collection<?> values);
/**
* Not In - property has a value in the array of values.
*/
Expression notIn(String propertyName, Object[] values);
/**
* Not In - property has a value in the collection of values.
*/
Expression notIn(String propertyName, Collection<?> values);
/**
* Not In - using a subQuery.
*/
Expression notIn(String propertyName, Query<?> subQuery);
/**
* Exists expression
*/
Expression exists(Query<?> subQuery);
/**
* Not exists expression
*/
Expression notExists(Query<?> subQuery);
/**
* Is empty expression for collection properties.
*/
Expression isEmpty(String propertyName);
/**
* Is not empty expression for collection properties.
*/
Expression isNotEmpty(String propertyName);
/**
* Id Equal to - ID property is equal to the value.
*/
Expression idEq(Object value);
/**
* Id IN a list of Id values.
*/
Expression idIn(Object... idValues);
/**
* Id IN a collection of Id values.
*/
Expression idIn(Collection<?> idCollection);
/**
* All Equal - Map containing property names and their values.
* <p>
* Expression where all the property names in the map are equal to the
* corresponding value.
* </p>
*
* @param propertyMap a map keyed by property names.
*/
Expression allEq(Map<String, Object> propertyMap);
/**
* Add expression for ANY of the given bit flags to be set.
*
* @param propertyName The property that holds the flags value
* @param flags The flags we are looking for
*/
Expression bitwiseAny(String propertyName, long flags);
/**
* Add expression for ALL of the given bit flags to be set.
*
* @param propertyName The property that holds the flags value
* @param flags The flags we are looking for
*/
Expression bitwiseAll(String propertyName, long flags);
/**
* Add bitwise AND expression of the given bit flags to compare with the match/mask.
*
* @param propertyName The property that holds the flags value
* @param flags The flags we are looking for
*/
Expression bitwiseAnd(String propertyName, long flags, long match);
/**
* Add raw expression with a single parameter.
* <p>
* The raw expression should contain a single ? at the location of the
* parameter.
* </p>
*/
Expression raw(String raw, Object value);
/**
* Add raw expression with an array of parameters.
* <p>
* The raw expression should contain the same number of ? as there are
* parameters.
* </p>
*/
Expression raw(String raw, Object[] values);
/**
* Add raw expression with no parameters.
*/
Expression raw(String raw);
/**
* Create a Text Match expression (currently doc store/Elastic only).
*/
Expression textMatch(String propertyName, String search, Match options);
/**
* Create a Text Multi match expression (currently doc store/Elastic only).
*/
Expression textMultiMatch(String query, MultiMatch options);
/**
* Create a text simple query expression (currently doc store/Elastic only).
*/
Expression textSimple(String search, TextSimple options);
/**
* Create a text query string expression (currently doc store/Elastic only).
*/
Expression textQueryString(String search, TextQueryString options);
/**
* Create a text common terms expression (currently doc store/Elastic only).
*/
Expression textCommonTerms(String search, TextCommonTerms options);
/**
* And - join two expressions with a logical and.
*/
Expression and(Expression expOne, Expression expTwo);
/**
* Or - join two expressions with a logical or.
*/
Expression or(Expression expOne, Expression expTwo);
/**
* Negate the expression (prefix it with NOT).
*/
Expression not(Expression exp);
/**
* Return a list of expressions that will be joined by AND's.
*/
<T> Junction<T> conjunction(Query<T> query);
/**
* Return a list of expressions that will be joined by OR's.
*/
<T> Junction<T> disjunction(Query<T> query);
/**
* Return a list of expressions that will be joined by AND's.
*/
<T> Junction<T> conjunction(Query<T> query, ExpressionList<T> parent);
/**
* Return a list of expressions that will be joined by OR's.
*/
<T> Junction<T> disjunction(Query<T> query, ExpressionList<T> parent);
/**
* Return a Text query junction for MUST, SHOULD or MUST NOT.
* <p>
* This is doc store Elastic only.
* </p>
*/
<T> Junction<T> junction(Junction.Type type, Query<T> query, ExpressionList<T> parent);
/**
* Add the expressions to the given expression list.
*
* @param where The expression list to add the expressions to
* @param expressions The expressions that are parsed
* @param params Bind parameters to match ? or ?1 bind positions.
*/
<T> void where(ExpressionList<T> where, String expressions, Object[] params);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,501 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.persistence.NonUniqueResultException;
import java.time.Clock;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* The extended API for Database.
* <p>
* This provides the finder methods that take an explicit transaction rather than obtaining
* the transaction from the usual mechanism (which is ThreadLocal based).
* </p>
* <p>
* In general we only want to use this ExtendedServer API when we want to avoid / bypass
* the use of the mechanism to get the current transaction and instead explicitly supply
* the transaction to use.
* </p>
* <p>
* Note that in all cases the transaction supplied can be null and in this case the Database
* will use the normal mechanism to obtain the transaction to use.
* </p>
*/
public interface ExtendedServer {
/**
* Return the NOW time from the Clock.
*/
long clockNow();
/**
* Set the Clock to use for <code>@WhenCreated</code> and <code>@WhenModified</code>.
* <p>
* Note that we only expect to change the Clock for testing purposes.
* </p>
*/
void setClock(Clock clock);
/**
* Execute the query returning true if a row is found.
* <p>
* The query is executed using max rows of 1 and will only select the id property.
* This method is really just a convenient way to optimise a query to perform a
* 'does a row exist in the db' check.
* </p>
*
* <h2>Example:</h2>
* <pre>{@code
*
* boolean userExists = query().where().eq("email", "rob@foo.com").exists();
*
* }</pre>
*
* <h2>Example using a query bean:</h2>
* <pre>{@code
*
* boolean userExists = new QContact().email.equalTo("rob@foo.com").exists();
*
* }</pre>
*
* @return True if the query finds a matching row in the database
*/
<T> boolean exists(Query<?> ormQuery, Transaction transaction);
/**
* Return the number of 'top level' or 'root' entities this query should return.
*
* @see Query#findCount()
* @see Query#findFutureCount()
*/
<T> int findCount(Query<T> query, Transaction transaction);
/**
* Return the Id values of the query as a List.
*
* @see Query#findIds()
*/
@Nonnull
<A, T> List<A> findIds(Query<T> query, Transaction transaction);
/**
* Return a QueryIterator for the query.
* <p>
* Generally using {@link #findEach(Query, Consumer, Transaction)} or
* {@link #findEachWhile(Query, Predicate, Transaction)} is preferred
* to findIterate(). The reason is that those methods automatically take care of
* closing the queryIterator (and the underlying jdbc statement and resultSet).
* <p>
* This is similar to findEach in that not all the result beans need to be held
* in memory at the same time and as such is good for processing large queries.
*
* @see Query#findIterate()
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
@Nonnull
<T> QueryIterator<T> findIterate(Query<T> query, Transaction transaction);
/**
* Execute the query returning the result as a Stream.
* <p>
* Note that this can support very large queries iterating any number of results.
* To do so internally it can use multiple persistence contexts.
* <p>
* Note that the stream needs to be closed so use with try with resources.
* </p>
*/
@Nonnull
<T> Stream<T> findStream(Query<T> query, Transaction transaction);
/**
* Deprecated - migrate to findStream().
* <p>
* Execute the query returning the result as a Stream.
* <p>
* Note that this can support very large queries iterating any number of results.
* To do so internally it can use multiple persistence contexts.
* <p>
* Note that the stream needs to be closed so use with try with resources.
*/
@Nonnull
@Deprecated
<T> Stream<T> findLargeStream(Query<T> query, Transaction transaction);
/**
* Execute the query visiting the each bean one at a time.
* <p>
* Unlike findList() this is suitable for processing a query that will return
* a very large resultSet. The reason is that not all the result beans need to be
* held in memory at the same time and instead processed one at a time.
* </p>
* <p>
* Internally this query using a PersistenceContext scoped to each bean (and the
* beans associated object graph).
* </p>
* <p>
* <pre>{@code
*
* DB.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findEach((Order order) -> {
*
* // do something with the order bean
* System.out.println(" -- processing order ... " + order);
* });
*
* }</pre>
*
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
<T> void findEach(Query<T> query, Consumer<T> consumer, Transaction transaction);
/**
* Execute the query visiting the each bean one at a time.
* <p>
* Compared to findEach() this provides the ability to stop processing the query
* results early by returning false for the Predicate.
* </p>
* <p>
* Unlike findList() this is suitable for processing a query that will return
* a very large resultSet. The reason is that not all the result beans need to be
* held in memory at the same time and instead processed one at a time.
* </p>
* <p>
* Internally this query using a PersistenceContext scoped to each bean (and the
* beans associated object graph).
* </p>
* <p>
* <pre>{@code
*
* DB.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .findEachWhile((Order order) -> {
*
* // do something with the order bean
* System.out.println(" -- processing order ... " + order);
*
* boolean carryOnProcessing = ...
* return carryOnProcessing;
* });
*
* }</pre>
*
* @see Query#findEach(Consumer)
* @see Query#findEachWhile(Predicate)
*/
<T> void findEachWhile(Query<T> query, Predicate<T> consumer, Transaction transaction);
/**
* Return versions of a @History entity bean.
* <p>
* Generally this query is expected to be a find by id or unique predicates query.
* It will execute the query against the history returning the versions of the bean.
* </p>
*/
@Nonnull
<T> List<Version<T>> findVersions(Query<T> query, Transaction transaction);
/**
* Execute a query returning a list of beans.
* <p>
* Generally you are able to use {@link Query#findList()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
* <p>
* <pre>{@code
*
* List<Customer> customers = DB.find(Customer.class)
* .where().ilike("name", "rob%")
* .findList();
*
* }</pre>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched beans.
* @see Query#findList()
*/
@Nonnull
<T> List<T> findList(Query<T> query, Transaction transaction);
/**
* Execute find row count query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @param query the query to execute the row count on
* @param transaction the transaction (can be null).
* @return a Future object for the row count query
* @see Query#findFutureCount()
*/
@Nonnull
<T> FutureRowCount<T> findFutureCount(Query<T> query, Transaction transaction);
/**
* Execute find Id's query in a background thread.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a
* timeout).
* </p>
*
* @param query the query to execute the fetch Id's on
* @param transaction the transaction (can be null).
* @return a Future object for the list of Id's
* @see Query#findFutureIds()
*/
@Nonnull
<T> FutureIds<T> findFutureIds(Query<T> query, Transaction transaction);
/**
* Execute find list query in a background thread returning a FutureList object.
* <p>
* This returns a Future object which can be used to cancel, check the
* execution status (isDone etc) and get the value (with or without a timeout).
* <p>
* This query will execute in it's own PersistenceContext and using its own transaction.
* What that means is that it will not share any bean instances with other queries.
*
* @param query the query to execute in the background
* @param transaction the transaction (can be null).
* @return a Future object for the list result of the query
* @see Query#findFutureList()
*/
@Nonnull
<T> FutureList<T> findFutureList(Query<T> query, Transaction transaction);
/**
* Return a PagedList for this query using firstRow and maxRows.
* <p>
* The benefit of using this over findList() is that it provides functionality to get the
* total row count etc.
* </p>
* <p>
* If maxRows is not set on the query prior to calling findPagedList() then a
* PersistenceException is thrown.
* </p>
* <p>
* <pre>{@code
*
* PagedList<Order> pagedList = DB.find(Order.class)
* .setFirstRow(50)
* .setMaxRows(20)
* .findPagedList();
*
* // fetch the total row count in the background
* pagedList.loadRowCount();
*
* List<Order> orders = pagedList.getList();
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*
* @return The PagedList
* @see Query#findPagedList()
*/
@Nonnull
<T> PagedList<T> findPagedList(Query<T> query, Transaction transaction);
/**
* Execute the query returning a set of entity beans.
* <p>
* Generally you are able to use {@link Query#findSet()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
* <p>
* <pre>{@code
*
* Set<Customer> customers = DB.find(Customer.class)
* .where().ilike("name", "rob%")
* .findSet();
*
* }</pre>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute
* @param transaction the transaction to use (can be null).
* @return the set of fetched beans.
* @see Query#findSet()
*/
@Nonnull
<T> Set<T> findSet(Query<T> query, Transaction transaction);
/**
* Execute the query returning the entity beans in a Map.
* <p>
* Generally you are able to use {@link Query#findMap()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the map of fetched beans.
* @see Query#findMap()
*/
@Nonnull
<K, T> Map<K, T> findMap(Query<T> query, Transaction transaction);
/**
* Execute the query returning a list of values for a single property.
* <p>
* <h3>Example 1:</h3>
* <pre>{@code
*
* List<String> names =
* DB.find(Customer.class)
* .select("name")
* .order().asc("name")
* .findSingleAttributeList();
*
* }</pre>
* <h3>Example 2:</h3>
* <pre>{@code
*
* List<String> names =
* DB.find(Customer.class)
* .setDistinct(true)
* .select("name")
* .where().eq("status", Customer.Status.NEW)
* .order().asc("name")
* .setMaxRows(100)
* .findSingleAttributeList();
*
* }</pre>
*
* @return the list of values for the selected property
* @see Query#findSingleAttributeList()
*/
@Nonnull
<A, T> List<A> findSingleAttributeList(Query<T> query, Transaction transaction);
/**
* Execute the query returning at most one entity bean or null (if no matching
* bean is found).
* <p>
* This will throw a NonUniqueResultException if the query finds more than one result.
* </p>
* <p>
* Generally you are able to use {@link Query#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param <T> the type of entity bean to fetch.
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched beans.
* @throws NonUniqueResultException if more than one result was found
* @see Query#findOne()
*/
@Nullable
<T> T findOne(Query<T> query, Transaction transaction);
/**
* Similar to findOne() but returns an Optional (rather than nullable).
*/
@Nonnull
<T> Optional<T> findOneOrEmpty(Query<T> query, Transaction transaction);
/**
* Execute as a delete query deleting the 'root level' beans that match the predicates
* in the query.
* <p>
* Note that if the query includes joins then the generated delete statement may not be
* optimal depending on the database platform.
* </p>
*
* @param query the query used for the delete
* @param transaction the transaction to use (can be null)
* @param <T> the type of entity bean to fetch.
* @return the number of beans/rows that were deleted
*/
<T> int delete(Query<T> query, Transaction transaction);
/**
* Execute the update query returning the number of rows updated.
* <p>
* The update query must be created using {@link Database#update(Class)}.
* </p>
*
* @param query the update query to execute
* @param transaction the optional transaction to use for the update (can be null)
* @param <T> the type of entity bean
* @return The number of rows updated
*/
<T> int update(Query<T> query, Transaction transaction);
/**
* Execute the sql query returning a list of MapBean.
* <p>
* Generally you are able to use {@link SqlQuery#findList()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the list of fetched MapBean.
* @see SqlQuery#findList()
*/
@Nonnull
List<SqlRow> findList(SqlQuery query, Transaction transaction);
/**
* Execute the SqlQuery iterating a row at a time.
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEach(SqlQuery query, Consumer<SqlRow> consumer, Transaction transaction);
/**
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
* <p>
* Returning false after processing a row stops the iteration through the query results.
* </p>
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEachWhile(SqlQuery query, Predicate<SqlRow> consumer, Transaction transaction);
/**
* Execute the sql query returning a single MapBean or null.
* <p>
* This will throw a PersistenceException if the query found more than one
* result.
* </p>
* <p>
* Generally you are able to use {@link SqlQuery#findOne()} rather than
* explicitly calling this method. You could use this method if you wish to
* explicitly control the transaction used for the query.
* </p>
*
* @param query the query to execute.
* @param transaction the transaction to use (can be null).
* @return the fetched MapBean or null if none was found.
* @see SqlQuery#findOne()
*/
@Nullable
SqlRow findOne(SqlQuery query, Transaction transaction);
}
@@ -0,0 +1,290 @@
package io.ebean;
import java.io.Serializable;
/**
* Defines the configuration options for a "query fetch" or a
* "lazy loading fetch". This gives you the ability to use multiple smaller
* queries to populate an object graph as opposed to a single large query.
* <p>
* The primary goal is to provide efficient ways of loading complex object
* graphs avoiding SQL Cartesian product and issues around populating object
* graphs that have multiple *ToMany relationships.
* </p>
* <p>
* It also provides the ability to control the lazy loading queries (batch size,
* selected properties and fetches) to avoid N+1 queries etc.
* <p>
* There can also be cases loading across a single OneToMany where 2 SQL queries
* using Ebean FetchConfig.query() can be more efficient than one SQL query.
* When the "One" side is wide (lots of columns) and the cardinality difference
* is high (a lot of "Many" beans per "One" bean) then this can be more
* efficient loaded as 2 SQL queries.
* </p>
* <p>
* <pre>{@code
* // Normal fetch join results in a single SQL query
* List<Order> list = DB.find(Order.class).fetch("details").findList();
*
* // Find Orders join details using a single SQL query
* }</pre>
* <p>
* Example: Using a "query join" instead of a "fetch join" we instead use 2 SQL queries
* </p>
* <p>
* <pre>{@code
*
* // This will use 2 SQL queries to build this object graph
* List<Order> list =
* DB.find(Order.class)
* .fetch("details", new FetchConfig().query())
* .findList();
*
* // query 1) find order
* // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
*
* }</pre>
* <p>
* Example: Using 2 "query joins"
* </p>
* <p>
* <pre>{@code
*
* // This will use 3 SQL queries to build this object graph
* List<Order> list =
* DB.find(Order.class)
* .fetch("details", new FetchConfig().query())
* .fetch("customer", new FetchConfig().queryFirst(5))
* .findList();
*
* // query 1) find order
* // query 2) find orderDetails where order.id in (?,?...) // first 100 order id's
* // query 3) find customer where id in (?,?,?,?,?) // first 5 customers
*
* }</pre>
* <p>
* Example: Using "query joins" and partial objects
* </p>
* <p>
*
* <pre>{@code
* // This will use 3 SQL queries to build this object graph
* List<Order> list =
* DB.find(Order.class)
* .select("status, shipDate")
* .fetch("details", "quantity, price", new FetchConfig().query())
* .fetch("details.product", "sku, name")
* .fetch("customer", "name", new FetchConfig().queryFirst(5))
* .fetch("customer.contacts")
* .fetch("customer.shippingAddress")
* .findList();
*
* // query 1) find order (status, shipDate)
* // query 2) find orderDetail (quantity, price) fetch product (sku, name) where
* // order.id in (?,? ...)
* // query 3) find customer (name) fetch contacts (*) fetch shippingAddress (*)
* // where id in (?,?,?,?,?)
*
* // Note: the fetch of "details.product" is automatically included into the
* // fetch of "details"
* //
* // Note: the fetch of "customer.contacts" and "customer.shippingAddress"
* // are automatically included in the fetch of "customer"
* }</pre>
* <p>
* You can use query() and lazy together on a single join. The query is executed
* immediately and the lazy defines the batch size to use for further lazy
* loading (if lazy loading is invoked).
* </p>
* <p>
* <pre>{@code
*
* List<Order> list =
* DB.find(Order.class)
* .fetch("customer", new FetchConfig().query(10).lazy(5))
* .findList();
*
* // query 1) find order
* // query 2) find customer where id in (?,?,?,?,?,?,?,?,?,?) // first 10 customers
* // .. then if lazy loading of customers is invoked
* // .. use a batch size of 5 to load the customers
*
* }</pre>
* <p>
* <p>
* Example of controlling the lazy loading query:
* </p>
* <p>
* This gives us the ability to optimise the lazy loading query for a given use
* case.
* </p>
* <p>
* <pre>{@code
*
* List<Order> list = DB.find(Order.class)
* .fetch("customer","name", new FetchConfig().lazy(5))
* .fetch("customer.contacts","contactName, phone, email")
* .fetch("customer.shippingAddress")
* .where().eq("status",Order.Status.NEW)
* .findList();
*
* // query 1) find order where status = Order.Status.NEW
* //
* // .. if lazy loading of customers is invoked
* // .. use a batch size of 5 to load the customers
*
* }</pre>
*
* @author mario
* @author rbygrave
*/
public class FetchConfig implements Serializable {
private static final long serialVersionUID = 1L;
private int lazyBatchSize = -1;
private int queryBatchSize = -1;
private boolean queryAll;
private boolean cache;
/**
* Construct the fetch configuration object.
*/
public FetchConfig() {
}
/**
* Specify that this path should be lazy loaded using the default batch load
* size.
*/
public FetchConfig lazy() {
this.lazyBatchSize = 0;
this.queryAll = false;
return this;
}
/**
* Specify that this path should be lazy loaded with a specified batch size.
*
* @param lazyBatchSize the batch size for lazy loading
*/
public FetchConfig lazy(int lazyBatchSize) {
this.lazyBatchSize = lazyBatchSize;
this.queryAll = false;
return this;
}
/**
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
* <p>
* This will use the default batch size for separate query which is 100.
* </p>
*/
public FetchConfig query() {
this.queryBatchSize = 0;
this.queryAll = true;
return this;
}
/**
* Eagerly fetch the beans fetching the beans from the L2 bean cache
* and using the DB for beans not in the cache.
*/
public FetchConfig cache() {
this.cache = true;
this.queryBatchSize = 0;
this.queryAll = true;
return this;
}
/**
* Eagerly fetch the beans in this path as a separate query (rather than as
* part of the main query).
* <p>
* The queryBatchSize is the number of parent id's that this separate query
* will load per batch.
* </p>
* <p>
* This will load all beans on this path eagerly unless a {@link #lazy(int)}
* is also used.
* </p>
*
* @param queryBatchSize the batch size used to load beans on this path
*/
public FetchConfig query(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
// queryAll true as long as a lazy batch size has not already been set
this.queryAll = (lazyBatchSize == -1);
return this;
}
/**
* Eagerly fetch the first batch of beans on this path.
* This is similar to {@link #query(int)} but only fetches the first batch.
* <p>
* If there are more parent beans than the batch size then they will not be
* loaded eagerly but instead use lazy loading.
* </p>
*
* @param queryBatchSize the number of parent beans this path is populated for
*/
public FetchConfig queryFirst(int queryBatchSize) {
this.queryBatchSize = queryBatchSize;
this.queryAll = false;
return this;
}
/**
* Return the batch size for lazy loading.
*/
public int getLazyBatchSize() {
return lazyBatchSize;
}
/**
* Return the batch size for separate query load.
*/
public int getQueryBatchSize() {
return queryBatchSize;
}
/**
* Return true if the query fetch should fetch 'all' rather than just the
* 'first' batch.
*/
public boolean isQueryAll() {
return queryAll;
}
/**
* Return true if this uses L2 bean cache.
*/
public boolean isCache() {
return cache;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FetchConfig that = (FetchConfig) o;
if (lazyBatchSize != that.lazyBatchSize) return false;
if (queryBatchSize != that.queryBatchSize) return false;
if (cache != that.cache) return false;
return queryAll == that.queryAll;
}
@Override
public int hashCode() {
int result = lazyBatchSize;
result = 92821 * result + queryBatchSize;
result = 92821 * result + (queryAll ? 1 : 0);
result = 92821 * result + (cache ? 1 : 0);
return result;
}
}
@@ -0,0 +1,123 @@
package io.ebean;
import io.ebean.service.SpiFetchGroupQuery;
import javax.annotation.Nonnull;
/**
* Defines what part of the object graph to load (select and fetch clauses).
* <p>
* Using a FetchGroup effectively sets the select() and fetch() clauses for a query. It is alternative
* to specifying the select() and fetch() clauses on the query allowing for more re-use of "what to load"
* that can be defined separately from the query and combined with other FetchGroups.
* </p>
*
* <h3>Select example</h3>
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* <h3>Select and fetch example</h3>
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName, lastName, email")
* .build();
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* <h3>Combining FetchGroups</h3>
* <p>
* FetchGroups can be combined together to form another FetchGroup.
* </p>
* <pre>{@code
*
* FetchGroup<Address> FG_ADDRESS = FetchGroup.of(Address.class)
* .select("line1, line2, city")
* .fetch("country", "name")
* .build();
*
* FetchGroup<Customer> FG_CUSTOMER = FetchGroup.of(Customer.class)
* .select("name, version")
* .fetch("billingAddress", FG_ADDRESS)
* .build();
*
*
* Customer.query()
* .select(FG_CUSTOMER)
* .findList();
*
* }</pre>
*
* @param <T> The bean type the Fetch group can be applied to
*/
public interface FetchGroup<T> {
/**
* Return the FetchGroup with the given select clause.
* <p>
* We use this for simple FetchGroup that only select() properties and do not have additional fetch() clause.
* </p>
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class, "name, status");
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* @param select The select clause of the FetchGroup
*
* @return The FetchGroup with the given select clause
*/
@Nonnull
static <T> FetchGroup<T> of(Class<T> cls, String select) {
return XServiceProvider.fetchGroupOf(cls, select);
}
/**
* Return the FetchGroupBuilder with the given select clause that we can add fetch clauses to.
* <p>
* We chain select() with one or more fetch() clauses to define the object graph to load.
* </p>
* <pre>{@code
*
* FetchGroup<Customer> fetchGroup = FetchGroup.of(Customer.class)
* .select("name, status")
* .fetch("contacts", "firstName, lastName, email")
* .build();
*
* Customer.query()
* .select(fetchGroup)
* .findList();
*
* }</pre>
*
* @return The FetchGroupBuilder with the given select clause which we will add fetch clauses to
*/
@Nonnull
static <T> FetchGroupBuilder<T> of(Class<T> cls) {
return XServiceProvider.fetchGroupOf(cls);
}
/**
* Return a query to be used by query beans for constructing FetchGroup.
*/
static <T> SpiFetchGroupQuery<T> queryFor(Class<T> beanType) {
return XServiceProvider.fetchGroupQueryFor(beanType);
}
}
@@ -0,0 +1,107 @@
package io.ebean;
import javax.annotation.Nonnull;
/**
* Builds a FetchGroup by adding fetch clauses.
* <p>
* We add select() and fetch() clauses to define the object graph we want to load.
* </p>
*
* <pre>{@code
*
* FetchGroup fetchGroup = FetchGroup
* .select("name, status")
* .fetch("contacts", "firstName, lastName, email")
* .build();
*
* Customer.query()
* .select(fetchGroup)
* .where()
* ...
* .findList();
*
* }</pre>
*/
public interface FetchGroupBuilder<T> {
/**
* Specify specific properties to select (top level properties).
*/
@Nonnull
FetchGroupBuilder<T> select(String select);
/**
* Fetch all the properties at the given path.
*/
@Nonnull
FetchGroupBuilder<T> fetch(String path);
/**
* Fetch the path with the nested fetch group.
*/
@Nonnull
FetchGroupBuilder<T> fetch(String path, FetchGroup<?> nestedGroup);
/**
* Fetch the path using a query join with the nested fetch group.
*/
@Nonnull
FetchGroupBuilder<T> fetchQuery(String path, FetchGroup<?> nestedGroup);
/**
* Fetch the path lazily with the nested fetch group.
*/
@Nonnull
FetchGroupBuilder<T> fetchLazy(String path, FetchGroup<?> nestedGroup);
/**
* Fetch the path including specified properties.
*/
@Nonnull
FetchGroupBuilder<T> fetch(String path, String properties);
/**
* Fetch the path including all its properties using a query join.
*/
@Nonnull
FetchGroupBuilder<T> fetchQuery(String path);
/**
* Fetch the path including all its properties using L2 cache.
* Cache misses fallback to fetchQuery().
*/
@Nonnull
FetchGroupBuilder<T> fetchCache(String path);
/**
* Fetch the path including specified properties using a query join.
*/
@Nonnull
FetchGroupBuilder<T> fetchQuery(String path, String properties);
/**
* Fetch the path including specified properties using L2 cache.
* Cache misses fallback to fetchQuery().
*/
@Nonnull
FetchGroupBuilder<T> fetchCache(String path, String properties);
/**
* Fetch the path including all its properties lazily.
*/
@Nonnull
FetchGroupBuilder<T> fetchLazy(String path);
/**
* Fetch the path including specified properties lazily.
*/
@Nonnull
FetchGroupBuilder<T> fetchLazy(String path, String properties);
/**
* Build and return the FetchGroup.
*/
@Nonnull
FetchGroup<T> build();
}
@@ -0,0 +1,26 @@
package io.ebean;
import java.util.Set;
/**
* Provides paths and properties for an object graph that can be used to control what parts of the object graph
* is fetching (select and fetch clauses) and also can be used to control JSON marshalling (what parts of the object
* graph are included in the JSON).
*/
public interface FetchPath {
/**
* Return true if the path is included in this FetchPath.
*/
boolean hasPath(String path);
/**
* Return the properties at the given path.
*/
Set<String> getProperties(String path);
/**
* Apply the fetch path to the query.
*/
<T> void apply(Query<T> query);
}
@@ -0,0 +1,196 @@
package io.ebean;
import java.util.List;
import java.util.Set;
/**
* Provides support for filtering and sorting lists of entities without going
* back to the database.
* <p>
* That is, it uses local in-memory sorting and filtering of a list of entity
* beans. It is not used in a Database query or invoke a Database query.
* </p>
* <p>
* You can optionally specify a sortByClause and if so, the sort will always
* execute prior to the filter expressions. You can specify any number of filter
* expressions and they are effectively joined by logical "AND".
* </p>
* <p>
* The result of the filter method will leave the original list unmodified and
* return a new List instance.
* </p>
* <p>
* <pre>{@code
*
* // get a list of entities (query execution statistics in this case)
*
* List<MetaQueryStatistic> list =
* DB.find(MetaQueryStatistic.class).findList();
*
* long nowMinus24Hrs = System.currentTimeMillis() - 24 * (1000 * 60 * 60);
*
* // sort and filter the list returning a filtered list...
*
* List<MetaQueryStatistic> filteredList =
* DB.filter(MetaQueryStatistic.class)
* .sort("avgTimeMicros desc")
* .gt("executionCount", 0)
* .gt("lastQueryTime", nowMinus24Hrs)
* .eq("autoTuned", true)
* .maxRows(10)
* .filter(list);
*
* }</pre>
* <p>
* The propertyNames can traverse the object graph (e.g. customer.name) by using
* dot notation. If any point during the object graph traversal to get a
* property value is null then null is returned.
* </p>
* <p>
* <pre>{@code
*
* // examples of property names that
* // ... will traverse the object graph
* // ... where customer is a property of our bean
*
* customer.name
* customer.shippingAddress.city
*
* }</pre>
* <p>
* <pre>{@code
*
* // get a list of entities (query execution statistics)
*
* List<Order> orders =
* DB.find(Order.class).findList();
*
* // Apply a filter...
*
* List<Order> filteredOrders =
* DB.filter(Order.class)
* .startsWith("customer.name", "Rob")
* .eq("customer.shippingAddress.city", "Auckland")
* .filter(orders);
*
* }</pre>
*
* @param <T> the entity bean type
*/
public interface Filter<T> {
/**
* Specify a sortByClause.
* <p>
* The sort (if specified) will always execute first followed by the filter
* expressions.
* </p>
* <p>
* Refer to {@link DB#sort(List, String)} for more detail.
* </p>
*/
Filter<T> sort(String sortByClause);
/**
* Specify the maximum number of rows/elements to return.
*/
Filter<T> maxRows(int maxRows);
/**
* Equal To - property equal to the given value.
*/
Filter<T> eq(String prop, Object value);
/**
* Not Equal To - property not equal to the given value.
*/
Filter<T> ne(String propertyName, Object value);
/**
* Case Insensitive Equal To.
*/
Filter<T> ieq(String propertyName, String value);
/**
* Between - property between the two given values.
*/
Filter<T> between(String propertyName, Object value1, Object value2);
/**
* Greater Than - property greater than the given value.
*/
Filter<T> gt(String propertyName, Object value);
/**
* Greater Than or Equal to - property greater than or equal to the given
* value.
*/
Filter<T> ge(String propertyName, Object value);
/**
* Less Than - property less than the given value.
*/
Filter<T> lt(String propertyName, Object value);
/**
* Less Than or Equal to - property less than or equal to the given value.
*/
Filter<T> le(String propertyName, Object value);
/**
* Is Null - property is null.
*/
Filter<T> isNull(String propertyName);
/**
* Is Not Null - property is not null.
*/
Filter<T> isNotNull(String propertyName);
/**
* Starts With.
*/
Filter<T> startsWith(String propertyName, String value);
/**
* Case insensitive Starts With.
*/
Filter<T> istartsWith(String propertyName, String value);
/**
* Ends With.
*/
Filter<T> endsWith(String propertyName, String value);
/**
* Case insensitive Ends With.
*/
Filter<T> iendsWith(String propertyName, String value);
/**
* Contains - property contains the string "value".
*/
Filter<T> contains(String propertyName, String value);
/**
* Case insensitive Contains.
*/
Filter<T> icontains(String propertyName, String value);
/**
* In - property has a value contained in the set of values.
*/
Filter<T> in(String propertyName, Set<?> values);
/**
* Apply the filter to the list returning a new list of the matching elements
* in the sorted order.
* <p>
* The sourceList will remain unmodified.
* </p>
*
* @return Returns a new list with the sorting and filters applied.
*/
List<T> filter(List<T> sourceList);
}
@@ -0,0 +1,219 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
/**
* Intended to be used as a base class for 'Finder' implementations that can then
* be injected or used as public static fields on the associated entity bean.
* <p>
* These 'finders' are a place to organise all the finder methods for that bean type
* and specific finder methods are expected to be added (find by unique properties etc).
* </p>
* <h3>Testing</h3>
* <p>
* For testing the mocki-ebean project has the ability to replace the finder implementation.
* </p>
* <pre>{@code
*
* public class CustomerFinder extends Finder<Long,Customer> {
*
* public CustomerFinder() {
* super(Customer.class);
* }
*
* // Add finder methods ...
*
* public Customer byName(String name) {
* return query().eq("name", name).findOne();
* }
*
* public List<Customer> findNew() {
* return query().where()
* .eq("status", Customer.Status.NEW)
* .order("name")
* .findList()
* }
* }
*
* Entity
* public class Customer extends BaseModel {
*
* public static final CustomerFinder find = new CustomerFinder();
* ...
*
* }
* }</pre>
* <p>
* When the Finder is registered as a field on Customer it can then be used like:
* </p>
* <pre>{@code
*
* Customer rob = Customer.find.byName("Rob");
*
* }</pre>
*
*/
public class Finder<I, T> {
/**
* The entity bean type.
*/
private final Class<T> type;
/**
* The name of the database this finder will use, null for the default database.
*/
private final String _$dbName;
/**
* Create with the type of the entity bean.
* <pre>{@code
*
* public class CustomerFinder extends Finder<Customer> {
*
* public CustomerFinder() {
* super(Customer.class);
* }
*
* // ... add extra customer specific finder methods
* }
*
* Entity
* public class Customer extends BaseModel {
*
* public static final CustomerFinder find = new CustomerFinder();
* ...
*
* }
* }</pre>
*/
public Finder(Class<T> type) {
this.type = type;
this._$dbName = null;
}
/**
* Create with the type of the entity bean and specific database name.
*/
public Finder(Class<T> type, String databaseName) {
this.type = type;
this._$dbName = databaseName;
}
/**
* Return the current transaction.
*/
public Transaction currentTransaction() {
return db().currentTransaction();
}
/**
* Flush the JDBC batch on the current transaction.
*/
public void flush() {
db().flush();
}
/**
* Return the Database this finder will use.
*/
public Database db() {
return DB.byName(_$dbName);
}
/**
* Return typically a different Database to the default.
* <p>
* This is equivalent to {@link DB#byName(String)}
*
* @param databaseName The name of the Database. If this is null then the default database is returned.
*/
public Database db(String databaseName) {
return DB.byName(databaseName);
}
/**
* Creates an entity reference for this ID.
* <p>
* Equivalent to {@link Database#getReference(Class, Object)}
*/
@Nonnull
public T ref(I id) {
return db().getReference(type, id);
}
/**
* Retrieves an entity by ID.
* <p>
* Equivalent to {@link Database#find(Class, Object)}
*/
@Nullable
public T byId(I id) {
return db().find(type, id);
}
/**
* Delete a bean by Id.
* <p>
* Equivalent to {@link Database#delete(Class, Object)}
*/
public void deleteById(I id) {
db().delete(type, id);
}
/**
* Retrieves all entities of the given type.
*/
@Nonnull
public List<T> all() {
return query().findList();
}
/**
* Creates an update query.
*
* <pre>{@code
*
* int rows =
* finder.update()
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* <p>
* Equivalent to {@link Database#update(Class)}
*/
public UpdateQuery<T> update() {
return db().update(type);
}
/**
* Creates a query.
* <p>
* Equivalent to {@link Database#find(Class)}
*/
public Query<T> query() {
return db().find(type);
}
/**
* Creates a native sql query.
*/
public Query<T> nativeSql(String nativeSql) {
return db().findNative(type, nativeSql);
}
/**
* Creates a query using the ORM query language.
*/
public Query<T> query(String ormQuery) {
return db().createQuery(type, ormQuery);
}
}
@@ -0,0 +1,20 @@
package io.ebean;
import java.util.List;
import java.util.concurrent.Future;
/**
* FutureIds represents the result of a background query execution for the Id's.
* <p>
* It extends the java.util.concurrent.Future with the ability to get the Id's
* while the query is still executing in the background.
* </p>
*/
public interface FutureIds<T> extends Future<List<Object>> {
/**
* Returns the original query used to fetch the Id's.
*/
Query<T> getQuery();
}
@@ -0,0 +1,72 @@
package io.ebean;
import javax.persistence.PersistenceException;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* FutureList represents the result of a background query execution that will
* return a list of entities.
* <p>
* It extends the java.util.concurrent.Future with the ability to cancel the
* query, check if it is finished and get the resulting list waiting for the
* query to finish (ie. the standard features of java.util.concurrent.Future).
* </p>
* <p>
* A simple example:
* </p>
* <pre>{@code
*
* // create a query to find all orders
* Query<Order> query = DB.find(Order.class);
*
* // execute the query in a background thread
* // immediately returning the futureList
* FutureList<Order> futureList = query.findFutureList();
*
* // do something else ...
*
* if (!futureList.isDone()){
* // we can cancel the query execution. This will cancel
* // the underlying query if that is supported by the JDBC
* // driver and database
* futureList.cancel(true);
* }
*
* if (!futureList.isCancelled()){
* // wait for the query to finish and return the list
* List<Order> list = futureList.get();
* ...
* }
*
* }</pre>
*/
public interface FutureList<T> extends Future<List<T>> {
/**
* Return the query that is being executed by a background thread.
*/
Query<T> getQuery();
/**
* Same as {@link #get()} but wraps InterruptedException and ExecutionException in the
* unchecked PersistenceException.
*
* @return The query list result
* @throws PersistenceException when a InterruptedException or ExecutionException occurs.
*/
List<T> getUnchecked();
/**
* Same as {@link #get(long, java.util.concurrent.TimeUnit)} but wraps InterruptedException
* and ExecutionException in the unchecked PersistenceException.
*
* @return The query list result
* @throws TimeoutException if the wait timed out
* @throws PersistenceException if a InterruptedException or ExecutionException occurs.
*/
List<T> getUnchecked(long timeout, TimeUnit unit) throws TimeoutException;
}
@@ -0,0 +1,16 @@
package io.ebean;
import java.util.concurrent.Future;
/**
* Represents the result of a background query execution for the total row count
* for a query.
* <p>
* It extends the java.util.concurrent.Future.
* </p>
*
* @param <T> the BeanType
* @author rbygrave
*/
public interface FutureRowCount<T> extends Future<Integer> {
}
@@ -0,0 +1,149 @@
package io.ebean;
/**
* Represents a Conjunction or a Disjunction.
* <p>
* Basically with a Conjunction you join together many expressions with AND, and
* with a Disjunction you join together many expressions with OR.
* </p>
* <p>
* Note: where() always takes you to the top level WHERE expression list.
* </p>
* <pre>{@code
* Query q =
* DB.find(Person.class)
* .where()
* .or()
* .like("name", "Rob%")
* .eq("status", Status.NEW)
*
* // where() returns us to the top level expression list
* .where().gt("id", 10);
*
* // read as...
* // where ( ((name like Rob%) or (status = NEW)) AND (id &gt; 10) )
*
* }</pre>
* <p>
* Note: endJunction() takes you to the parent expression list
* </p>
* <pre>{@code
*
* Query q =
* DB.find(Person.class)
* .where()
* .or()
* .like("name", "Rob%")
* .eq("status", Status.NEW)
* .endJunction()
*
* // endJunction().. takes us to the 'parent' expression list
* // which in this case is the top level (same as where())
*
* .gt("id", 10);
*
* // read as...
* // where ( ((name like Rob%) or (status = NEW)) AND (id > 10) )
* }</pre>
* <p>
* Example of a nested disjunction.
* </p>
* <pre>{@code
* Query<Customer> q =
* DB.find(Customer.class)
* .where()
* .or()
* .and()
* .startsWith("name", "r")
* .eq("anniversary", onAfter)
* .endAnd()
* .and()
* .eq("status", Customer.Status.ACTIVE)
* .gt("id", 0)
* .endAnd()
* .order().asc("name");
*
* q.findList();
* String s = q.getGeneratedSql();
*
* // this produces an expression like:
* ( name like ? and c.anniversary = ? ) or (c.status = ? and c.id > ? )
*
* }</pre>
*/
public interface Junction<T> extends Expression, ExpressionList<T> {
/**
* The type of Junction used in full text expressions.
*/
enum Type {
/**
* AND group.
*/
AND(" and ", "", false),
/**
* OR group.
*/
OR(" or ", "", false),
/**
* NOT group.
*/
NOT(" and ", "not ", false),
/**
* FILTER group (for internal use only).
*/
FILTER("filter", "", true),
/**
* Text search AND group.
*/
MUST("must", "", true),
/**
* Text search NOT group.
*/
MUST_NOT("must_not", "", true),
/**
* Text search OR group.
*/
SHOULD("should", "", true);
private String prefix;
private String literal;
private boolean text;
Type(String literal, String prefix, boolean text) {
this.literal = literal;
this.prefix = prefix;
this.text = text;
}
/**
* Return the literal value for this type.
*/
public String literal() {
return literal;
}
/**
* Return the prefix value for this type.
*/
public String prefix() {
return prefix;
}
/**
* Return true if this is a text type.
*/
public boolean isText() {
return text;
}
}
}
@@ -0,0 +1,35 @@
package io.ebean;
/**
* Used to specify the type of like matching used.
*/
public enum LikeType {
/**
* You need to put in your own wildcards - no escaping is performed.
*/
RAW,
/**
* The % wildcard is added to the end of the search word and search word is escaped.
*/
STARTS_WITH,
/**
* The % wildcard is added to the beginning of the search word and search word is escaped.
*/
ENDS_WITH,
/**
* The % wildcard is added to the beginning and end of the search word and search word is escaped.
*/
CONTAINS,
/**
* Uses equal to rather than a LIKE with wildcards.
* <p>
* This is mainly here to be available for use with ExampleExpression.
* </p>
*/
EQUAL_TO
}
@@ -0,0 +1,29 @@
package io.ebean;
import java.util.Set;
/**
* Options used to control a merge. Use MergeOptionsBuilder to create an instance.
* <p>
* Instances of MergeOptions are thread safe and safe to share across threads.
*/
public interface MergeOptions {
/**
* Returns true if Id values are supplied by the client.
* <p>
* This would be the case when for example a mobile creates data in it's own local database
* and then sync's. In this case often the id values are UUID.
*/
boolean isClientGeneratedIds();
/**
* Return true if delete permanent should be used and false for 'normal' delete that allows soft deletes.
*/
boolean isDeletePermanent();
/**
* Return the paths included in the merge.
*/
Set<String> paths();
}
@@ -0,0 +1,96 @@
package io.ebean;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Builds a MergeOptions which is immutable and thread safe.
*/
public class MergeOptionsBuilder {
private static final MOptions DEFAULT_OPTIONS = new MOptions();
private Set<String> paths = new LinkedHashSet<>();
private boolean clientGeneratedIds;
private boolean deletePermanent;
/**
* Return the default options.
*/
public static MergeOptions defaultOptions() {
return DEFAULT_OPTIONS;
}
/**
* Add a path that will included in the merge.
*
* @param path The path relative to the root type.
* @return The builder to chain another addPath() or build().
*/
public MergeOptionsBuilder addPath(String path) {
paths.add(path);
return this;
}
/**
* Set to true if Id values are supplied by the client.
* <p>
* This would be the case when for example a mobile creates data in it's own local database
* and then sync's. In this case often the id values are UUID.
*/
public MergeOptionsBuilder setClientGeneratedIds() {
this.clientGeneratedIds = true;
return this;
}
/**
* Set that deletions should use delete permanent (rather than default which allows soft deletes).
*/
public MergeOptionsBuilder setDeletePermanent() {
this.deletePermanent = true;
return this;
}
/**
* Build and return the MergeOptions instance.
*/
public MergeOptions build() {
return new MOptions(paths, clientGeneratedIds, deletePermanent);
}
private static class MOptions implements MergeOptions {
private final boolean clientGeneratedIds;
private final boolean deletePermanent;
private final Set<String> paths;
private MOptions(){
this.clientGeneratedIds = false;
this.paths = new LinkedHashSet<>();
this.deletePermanent = false;
}
private MOptions(Set<String> paths, boolean clientGeneratedIds, boolean deletePermanent) {
this.paths = paths;
this.clientGeneratedIds = clientGeneratedIds;
this.deletePermanent = deletePermanent;
}
@Override
public Set<String> paths() {
return paths;
}
@Override
public boolean isClientGeneratedIds() {
return clientGeneratedIds;
}
@Override
public boolean isDeletePermanent() {
return deletePermanent;
}
}
}
+302
View File
@@ -0,0 +1,302 @@
package io.ebean;
import io.ebean.bean.EntityBean;
/**
* A MappedSuperclass base class that provides convenience methods for inserting, updating and
* deleting beans.
* <p>
* By having your entity beans extend this it provides a 'Active Record' style programming model for
* Ebean users.
* <p>
* Note that there is a ebean-mocker project that enables you to use Mockito or similar
* tools to still mock out the underlying 'default Database' for testing purposes.
* <p>
* You may choose not use this Model mapped superclass if you don't like the 'Active Record' style
* or if you believe it 'pollutes' your entity beans.
* <p>
* You can use Dependency Injection like Guice or Spring to construct and wire a Database instance
* and have that same instance used with this Model and Finder. The way that works is that when the
* DI container creates the Database instance it can be registered with DB. In this
* way the Database instance can be injected as per normal Guice / Spring dependency injection and
* that same instance also used to support the Model and Finder active record style.
* <p>
* If you choose to use the Model mapped superclass you will probably also chose to additionally add
* a {@link Finder} as a public static field to complete the active record pattern and provide a
* relatively nice clean way to write queries.
* <p>
* <h3>Typical common @MappedSuperclass</h3>
* <pre>{@code
*
* // Typically there is a common base model that has some
* // common properties like the ones below
*
* MappedSuperclass
* public class BaseModel extends Model {
*
* Id Long id;
*
* Version Long version;
*
* WhenCreated Timestamp whenCreated;
*
* WhenUpdated Timestamp whenUpdated;
*
* ...
* }
* }</pre>
* <p>
* <h3>Extend the Model</h3>
* <pre>{@code
*
* // Extend the mappedSuperclass
*
* Entity Table(name="o_account")
* public class Customer extends BaseModel {
*
* String name;
* ...
* }
*
* }</pre>
* <p>
* <h3>Modal: save()</h3>
* <pre>{@code
*
* // Active record style ... save(), delete() etc
* Customer customer = new Customer();
* customer.setName("AC234");
*
* // save() method inherited from Model
* customer.save();
*
* }</pre>
*/
public abstract class Model {
/**
* The name of the database this entity will use, null for the default database.
*/
private final String _$dbName;
/**
* Create using the default database.
*/
public Model() {
this._$dbName = null;
}
/**
* Create with a named database (typically not the default database).
*/
public Model(String dbName) {
this._$dbName = dbName;
}
/**
* Return the underlying 'default' Database.
* <p>
* This provides full access to the API such as explicit transaction demarcation etc.
* <p>
* Example:
* <pre>{@code
*
* try (Transaction transaction = Customer.db().beginTransaction()) {
*
* // turn off cascade persist for this transaction
* transaction.setPersistCascade(false);
*
* // extra control over jdbc batching for this transaction
* transaction.setBatchGetGeneratedKeys(false);
* transaction.setBatchMode(true);
* transaction.setBatchSize(20);
*
* Customer customer = new Customer();
* customer.setName(&quot;Roberto&quot;);
* customer.save();
*
* Customer otherCustomer = new Customer();
* otherCustomer.setName("Franko");
* otherCustomer.save();
*
* transaction.commit();
*
* }
*
* }</pre>
*/
public Database db() {
return DB.byName(_$dbName);
}
/**
* Marks the entity bean as dirty.
* <p>
* This is used so that when a bean that is otherwise unmodified is updated the version
* property is updated.
* <p>
* An unmodified bean that is saved or updated is normally skipped and this marks the bean as
* dirty so that it is not skipped.
* <p>
* <pre>{@code
*
* Customer customer = Customer.find.byId(id);
*
* // mark the bean as dirty so that a save() or update() will
* // increment the version property
* customer.markAsDirty();
* customer.save();
*
* }</pre>
*
* @see Database#markAsDirty(Object)
*/
public void markAsDirty() {
db().markAsDirty(this);
}
/**
* Mark the property as unset or 'not loaded'.
* <p>
* This would be used to specify a property that we did not wish to include in a stateless update.
* </p>
* <pre>{@code
*
* // populate an entity bean from JSON or whatever
* User user = ...;
*
* // mark the email property as 'unset' so that it is not
* // included in a 'stateless update'
* user.markPropertyUnset("email");
*
* user.update();
*
* }</pre>
*
* @param propertyName the name of the property on the bean to be marked as 'unset'
*/
public void markPropertyUnset(String propertyName) {
((EntityBean) this)._ebean_getIntercept().setPropertyLoaded(propertyName, false);
}
/**
* Insert or update this entity depending on its state.
* <p>
* Ebean will detect if this is a new bean or a previously fetched bean and perform either an
* insert or an update based on that.
*
* @see Database#save(Object)
*/
public void save() {
db().save(this);
}
/**
* Save this entity with an explicit transaction.
*/
public void save(Transaction transaction) {
db().save(this, transaction);
}
/**
* Flush any batched changes to the database.
* <p>
* When using JDBC batch flushing occurs automatically at commit() time or when the batch size
* is reached. This provides the ability to manually flush the batch.
* </p>
*/
public void flush() {
db().flush();
}
/**
* Update this entity.
*
* @see Database#update(Object)
*/
public void update() {
db().update(this);
}
/**
* Update this entity with an explicit transaction.
*/
public void update(Transaction transaction) {
db().update(this, transaction);
}
/**
* Insert this entity.
*
* @see Database#insert(Object)
*/
public void insert() {
db().insert(this);
}
/**
* Insert with an explicit transaction.
*/
public void insert(Transaction transaction) {
db().insert(this, transaction);
}
/**
* Delete this bean.
* <p>
* This will return true if the bean was deleted successfully or JDBC batch is being used.
* </p>
* <p>
* If there is no current transaction one will be created and committed for
* you automatically.
* </p>
* <p>
* If the Bean does not have a version property (or loaded version property) and
* the bean does not exist then this returns false indicating that nothing was
* deleted. Note that, if JDBC batch mode is used then this always returns true.
* </p>
*
* @see Database#delete(Object)
*/
public boolean delete() {
return db().delete(this);
}
/**
* Delete this entity with an explicit transaction.
*/
public boolean delete(Transaction transaction) {
return db().delete(this, transaction);
}
/**
* Delete a bean permanently without soft delete.
* <p>
* This is used when the bean contains a <code>@SoftDelete</code> property and we
* want to perform a hard/permanent delete.
* </p>
*
* @see Database#deletePermanent(Object)
*/
public boolean deletePermanent() {
return db().deletePermanent(this);
}
/**
* Delete a bean permanently without soft delete using an explicit transaction.
*/
public boolean deletePermanent(Transaction transaction) {
return db().deletePermanent(this, transaction);
}
/**
* Refreshes this entity from the database.
*
* @see Database#refresh(Object)
*/
public void refresh() {
db().refresh(this);
}
}
@@ -0,0 +1,477 @@
package io.ebean;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* Represents an Order By for a Query.
* <p>
* Is a ordered list of OrderBy.Property objects each specifying a property and
* whether it is ascending or descending order.
* </p>
* <p>
* Typically you will not construct an OrderBy yourself but use one that exists
* on the Query object.
* </p>
*/
public final class OrderBy<T> implements Serializable {
private static final long serialVersionUID = 9157089257745730539L;
private transient Query<T> query;
private final List<Property> list;
/**
* Create an empty OrderBy with no associated query.
*/
public OrderBy() {
this.list = new ArrayList<>(3);
}
private OrderBy(List<Property> list) {
this.list = list;
}
/**
* Create an orderBy parsing the order by clause.
* <p>
* The order by clause follows SQL order by clause with comma's between each
* property and optionally "asc" or "desc" to represent ascending or
* descending order respectively.
* </p>
*/
public OrderBy(String orderByClause) {
this(null, orderByClause);
}
/**
* Construct with a given query and order by clause.
*/
public OrderBy(Query<T> query, String orderByClause) {
this.query = query;
this.list = new ArrayList<>(3);
parse(orderByClause);
}
/**
* Reverse the ascending/descending order on all the properties.
*/
public void reverse() {
for (Property aList : list) {
aList.reverse();
}
}
/**
* Add a property with ascending order to this OrderBy.
*/
public Query<T> asc(String propertyName) {
list.add(new Property(propertyName, true));
return query;
}
/**
* Add a property with ascending order to this OrderBy.
*/
public Query<T> asc(String propertyName, String collation) {
list.add(new Property(propertyName, true, collation));
return query;
}
/**
* Add a property with descending order to this OrderBy.
*/
public Query<T> desc(String propertyName) {
list.add(new Property(propertyName, false));
return query;
}
/**
* Add a property with descending order to this OrderBy.
*/
public Query<T> desc(String propertyName, String collation) {
list.add(new Property(propertyName, false, collation));
return query;
}
/**
* Return true if the property is known to be contained in the order by clause.
*/
public boolean containsProperty(String propertyName) {
for (Property aList : list) {
if (propertyName.equals(aList.getProperty())) {
return true;
}
}
return false;
}
/**
* Return a copy of this OrderBy with the path trimmed.
*/
public OrderBy<T> copyWithTrim(String path) {
List<Property> newList = new ArrayList<>(list.size());
for (Property aList : list) {
newList.add(aList.copyWithTrim(path));
}
return new OrderBy<>(newList);
}
/**
* Return the properties for this OrderBy.
*/
public List<Property> getProperties() {
// not returning an Immutable list at this point
return list;
}
/**
* Return true if this OrderBy does not have any properties.
*/
public boolean isEmpty() {
return list.isEmpty();
}
/**
* Return the associated query if there is one.
*/
public Query<T> getQuery() {
return query;
}
/**
* Associate this OrderBy with a query.
*/
public void setQuery(Query<T> query) {
this.query = query;
}
/**
* Return a copy of the OrderBy.
*/
public OrderBy<T> copy() {
OrderBy<T> copy = new OrderBy<>();
for (Property property : list) {
copy.add(property.copy());
}
return copy;
}
/**
* Add to the order by by parsing a raw expression.
*/
public void add(String rawExpression) {
parse(rawExpression);
}
/**
* Add a property to the order by.
*/
public void add(Property p) {
list.add(p);
}
@Override
public String toString() {
return list.toString();
}
/**
* Returns the OrderBy in string format.
*/
public String toStringFormat() {
if (list.isEmpty()) {
return null;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
Property property = list.get(i);
if (i > 0) {
sb.append(", ");
}
sb.append(property.toStringFormat());
}
return sb.toString();
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof OrderBy<?>)) {
return false;
}
OrderBy<?> e = (OrderBy<?>) obj;
return e.list.equals(list);
}
/**
* Return a hash value for this OrderBy. This can be to determine logical
* equality for OrderBy clauses.
*/
@Override
public int hashCode() {
return list.hashCode();
}
/**
* Clear the orderBy removing any current order by properties.
* <p>
* This is intended to be used when some code creates a query with a
* 'default' order by clause and some other code may clear the 'default'
* order by clause and replace.
* </p>
*/
public OrderBy<T> clear() {
list.clear();
return this;
}
/**
* Return true if this order by can be used in select clause.
*/
public boolean supportsSelect() {
for (Property property : list) {
if (!property.supportsSelect()) {
return false;
}
}
return true;
}
/**
* A property and its ascending descending order.
*/
public static final class Property implements Serializable {
private static final long serialVersionUID = 1546009780322478077L;
private String property;
private boolean ascending;
private String collation;
private String nulls;
private String highLow;
public Property(String property, boolean ascending) {
this.property = property;
this.ascending = ascending;
}
public Property(String property, boolean ascending, String nulls, String highLow) {
this.property = property;
this.ascending = ascending;
this.nulls = nulls;
this.highLow = highLow;
}
public Property(String property, boolean ascending, String collation) {
this.property = property;
this.ascending = ascending;
this.collation = collation;
}
public Property(String property, boolean ascending, String collation, String nulls, String highLow) {
this.property = property;
this.ascending = ascending;
this.collation = collation;
this.nulls = nulls;
this.highLow = highLow;
}
/**
* Return a copy of this Property with the path trimmed.
*/
public Property copyWithTrim(String path) {
return new Property(property.substring(path.length() + 1), ascending, collation, nulls, highLow);
}
@Override
public int hashCode() {
int hc = property.hashCode();
hc = hc * 92821 + (ascending ? 0 : 1);
hc = hc * 92821 + (collation == null ? 0 : collation.hashCode());
hc = hc * 92821 + (nulls == null ? 0 : nulls.hashCode());
hc = hc * 92821 + (highLow == null ? 0 : highLow.hashCode());
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Property)) {
return false;
}
Property e = (Property) obj;
if (ascending != e.ascending) return false;
if (!property.equals(e.property)) return false;
if (!Objects.equals(collation, e.collation)) return false;
if (!Objects.equals(nulls, e.nulls)) return false;
return Objects.equals(highLow, e.highLow);
}
@Override
public String toString() {
return toStringFormat();
}
public String toStringFormat() {
if (nulls == null && collation == null) {
if (ascending) {
return property;
} else {
return property + " desc";
}
} else {
StringBuilder sb = new StringBuilder();
if (collation != null) {
if (collation.contains("${}")) {
// this is a complex collation, e.g. DB2 - we must replace the property
sb.append(collation.replace("${}", property));
} else {
sb.append(property);
sb.append(" collate ").append(collation);
}
} else {
sb.append(property);
}
if (!ascending) {
sb.append(" ").append("desc");
}
if (nulls != null) {
sb.append(" ").append(nulls).append(" ").append(highLow);
}
return sb.toString();
}
}
/**
* Reverse the ascending/descending order for this property.
*/
public void reverse() {
this.ascending = !ascending;
}
/**
* Trim off the pathPrefix.
*/
public void trim(String pathPrefix) {
property = property.substring(pathPrefix.length() + 1);
}
/**
* Return a copy of this property.
*/
public Property copy() {
return new Property(property, ascending, collation, nulls, highLow);
}
/**
* Return the property name.
*/
public String getProperty() {
return property;
}
/**
* Set the property name.
*/
public void setProperty(String property) {
this.property = property;
}
/**
* Return true if the order is ascending.
*/
public boolean isAscending() {
return ascending;
}
/**
* Set to true if the order is ascending.
*/
public void setAscending(boolean ascending) {
this.ascending = ascending;
}
/**
* Support use in select clause if no collation or nulls ordering.
*/
boolean supportsSelect() {
return nulls == null;
}
}
private void parse(String orderByClause) {
if (orderByClause == null) {
return;
}
String[] chunks = orderByClause.split(",");
for (String chunk : chunks) {
Property p = parseProperty(chunk);
if (p != null) {
list.add(p);
}
}
}
private Property parseProperty(String chunk) {
String[] pairs = chunk.split(" ");
if (pairs.length == 0) {
return null;
}
ArrayList<String> wordList = new ArrayList<>(pairs.length);
for (String pair : pairs) {
if (!isEmptyString(pair)) {
wordList.add(pair);
}
}
if (wordList.isEmpty()) {
return null;
}
if (wordList.size() == 1) {
return new Property(wordList.get(0), true);
}
if (wordList.size() == 2) {
boolean asc = isAscending(wordList.get(1));
return new Property(wordList.get(0), asc);
}
if (wordList.size() == 4) {
// nulls high or nulls low as 3rd and 4th
boolean asc = isAscending(wordList.get(1));
return new Property(wordList.get(0), asc, wordList.get(2), wordList.get(3));
}
return new Property(chunk.trim(), true);
}
private boolean isAscending(String s) {
s = s.toLowerCase();
if (s.startsWith("asc")) {
return true;
}
if (s.startsWith("desc")) {
return false;
}
String m = "Expecting [" + s + "] to be asc or desc?";
throw new RuntimeException(m);
}
private boolean isEmptyString(String s) {
return s == null || s.isEmpty();
}
}
@@ -0,0 +1,204 @@
package io.ebean;
import javax.annotation.Nonnull;
import java.util.List;
import java.util.concurrent.Future;
/**
* Represents a page of results.
* <p>
* The benefit of using PagedList over just using the normal Query with
* {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} is that it additionally wraps
* functionality that can call {@link Query#findFutureCount()} to determine total row count,
* total page count etc.
* </p>
* <p>
* Internally this works using {@link Query#setFirstRow(int)} and {@link Query#setMaxRows(int)} on
* the query. This translates into SQL that uses limit offset, rownum or row_number function to
* limit the result set.
* </p>
* <p>
* <h4>Example: typical use including total row count</h4>
* <pre>{@code
*
* // We want to find the first 50 new orders
* // ... so we don't really need setFirstRow(0)
*
* PagedList<Order> pagedList = DB.find(Order.class)
* .where().eq("status", Order.Status.NEW)
* .order().asc("id")
* .setFirstRow(0)
* .setMaxRows(50)
* .findPagedList();
*
* // Optional: initiate the loading of the total
* // row count in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
* <p>
* <h4>Example: No total row count required</h4>
* <pre>{@code
*
* // If you are not getting the 'first page' often
* // you do not bother getting the total row count again
* // so instead just get the page list of data
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* }</pre>
*
* @param <T> the entity bean type
* @see Query#findPagedList()
*/
public interface PagedList<T> {
/**
* Return an empty PagedList.
*/
static <B> PagedList<B> emptyList() {
return new EmptyPagedList<>();
}
/**
* Initiate the loading of the total row count in the background.
* <pre>{@code
*
* // initiate the loading of the total row count
* // in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (from the future)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
* <p>
* Also note that using loadRowCount() and getTotalRowCount() rather than getFutureRowCount()
* means that exceptions ExecutionException, InterruptedException, TimeoutException are instead
* wrapped in the unchecked PersistenceException (which might be preferrable).
* </p>
*/
void loadCount();
/**
* Return the Future row count. You might get this if you wish to cancel the total row count query
* or specify a timeout for the row count query.
* <p>
* The loadRowCount() and getTotalRowCount() methods internally make use of this getFutureRowCount() method.
* Generally I expect people to prefer loadRowCount() and getTotalRowCount() over getFutureRowCount().
* </p>
* <pre>{@code
*
* // initiate the row count query in the background thread
* Future<Integer> rowCount = pagedList.getFutureRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // now get the total count with a timeout
* Integer totalRowCount = rowCount.get(30, TimeUnit.SECONDS);
*
* // or ge the total count without a timeout
* Integer totalRowCountViaFuture = rowCount.get();
*
* // which is actually the same as ...
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*/
@Nonnull
Future<Integer> getFutureCount();
/**
* Return the list of entities for this page.
*/
@Nonnull
List<T> getList();
/**
* Return the total row count for all pages.
* <p>
* If loadRowCount() has already been called then the row count query is already executing in a background thread
* and this gets the associated Future and gets the value waiting for the future to finish.
* </p>
* <p>
* If loadRowCount() has not been called then this executes the find row count query and returns the result and this
* will just occur in the current thread and not use a background thread.
* </p>
* <pre>{@code
*
* // Optional: initiate the loading of the total
* // row count in a background thread
* pagedList.loadRowCount();
*
* // fetch and return the list in the foreground thread
* List<Order> orders = pagedList.getList();
*
* // get the total row count (which was being executed
* // in a background thread if loadRowCount() was used)
* int totalRowCount = pagedList.getTotalRowCount();
*
* }</pre>
*/
int getTotalCount();
/**
* Return the total number of pages based on the page size and total row count.
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*/
int getTotalPageCount();
/**
* Return the page size used for this query. This is the same value as maxRows used by the query.
*/
int getPageSize();
/**
* Return the index position of this page (Zero based).
* <p>
* This is a calculated value based on firstRow/maxRows.
* </p>
*/
int getPageIndex();
/**
* Return true if there is a next page.
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*/
boolean hasNext();
/**
* Return true if there is a previous page.
*/
boolean hasPrev();
/**
* Helper method to return a "X to Y of Z" string for this page where X is the first row, Y the
* last row and Z the total row count.
* <p>
* This method requires that the total row count has been fetched and will invoke
* the total row count query if it has not already been invoked.
* </p>
*
* @param to String to put between the first and last row
* @param of String to put between the last row and the total row count
* @return String of the format XtoYofZ.
*/
String getDisplayXtoYofZ(String to, String of);
}
+223
View File
@@ -0,0 +1,223 @@
package io.ebean;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Holds a list of value object pairs.
* <p>
* This feature is to enable use of L2 cache with complex natural keys with findList() queries in cases where the
* IN clause is not a single property but instead a pair of properties.
* </p>
* <p>
* These queries can have predicates that can be translated into a list of complex natural keys such that the L2
* cache can be hit with these keys to obtain some or all of the beans from L2 cache rather than the DB.
* </p>
* <pre>{@code
*
* // where a bean is annotated with a complex
* // natural key made of several properties
* Cache(naturalKey = {"store","code","sku"})
*
*
* Pairs pairs = new Pairs("sku", "code");
* pairs.add("sj2", 1000);
* pairs.add("sj2", 1001);
* pairs.add("pf3", 1000);
*
* List<OCachedNatKeyBean3> list = DB.find(OCachedNatKeyBean3.class)
* .where()
* .eq("store", "def")
* .inPairs(pairs) // IN clause with 'pairs' of values
* .order("sku desc")
*
* // query expressions cover the natural key properties
* // so we can choose to hit the L2 bean cache if we want
* .setUseCache(true)
* .findList();
*
* }</pre>
* <h3>Important implementation Note</h3>
* <p>
* When binding many pairs of values we want to be able to utilise a DB index (as this type of query usually means the
* pairs are a unique key/index or part of a unique key/index and highly selective). Currently we know we can do this
* on any DB that supports expression/formula based indexes.
* using a DB string concatenation formula
* </p>
* <p>
* This means, the implementation converts the list of pairs into a list of strings via concatenation and we use a
* DB concatenation formula to match. We see SQL like:
* </p>
* <pre>{@code sql
*
* ...
* where t0.store = ? and (t0.sku||'-'||t0.code) in (?, ? )
*
* // bind values like: "sj2-1000", "pf3-1000"
*
* }</pre>
* <p>
* We often create a DB expression index to match the DB concat formula like:
* </p>
* <pre>{@code sql
*
* create index ix_name on table_name ((sku || '-' || code));
*
* }</pre>
*/
public class Pairs {
private final String property0;
private final String property1;
private final List<Entry> entries = new ArrayList<>();
/**
* Character between the values when combined via DB varchar concatenation.
*/
private String concatSeparator = "-";
/**
* Optional suffix added to DB varchar concatenation formula.
*/
private String concatSuffix;
/**
* Create with 2 property names.
*
* @param property0 The property of the first value
* @param property1 The property of the second value
*/
public Pairs(String property0, String property1) {
this.property0 = property0;
this.property1 = property1;
}
/**
* Add a pair of value objects.
* <p>
* Both values are expected to be immutable with equals and hashCode implementations.
* </p>
*
* @param a Value of the first property
* @param b Value of the second property
*/
public Pairs add(Object a, Object b) {
entries.add(new Entry(a, b));
return this;
}
/**
* Return the first property name.
*/
public String getProperty0() {
return property0;
}
/**
* Return the second property name.
*/
public String getProperty1() {
return property1;
}
/**
* Return all the value pairs.
*/
public List<Entry> getEntries() {
return Collections.unmodifiableList(entries);
}
/**
* Return the separator character used with DB varchar concatenation to combine the 2 values.
*/
public String getConcatSeparator() {
return concatSeparator;
}
/**
* Set the separator character used with DB varchar concatenation to combine the 2 values.
*/
public Pairs setConcatSeparator(String concatSeparator) {
this.concatSeparator = concatSeparator;
return this;
}
/**
* Return a suffix used with DB varchar concatenation to combine the 2 values.
*/
public String getConcatSuffix() {
return concatSuffix;
}
/**
* Add a suffix used with DB varchar concatenation to combine the 2 values.
*/
public Pairs setConcatSuffix(String concatSuffix) {
this.concatSuffix = concatSuffix;
return this;
}
@Override
public String toString() {
return "p0:" + property0 + " p1:" + property1 + " entries:" + entries;
}
/**
* A pair of 2 value objects.
* <p>
* Used to support inPairs() expression.
*/
public static class Entry {
private final Object a;
private final Object b;
/**
* Create with values for property0 and property1 respectively.
*
* @param a Value of the first property
* @param b Value of the second property
*/
public Entry(Object a, Object b) {
this.a = a;
this.b = b;
}
@Override
public String toString() {
return "{" + a + "," + b + "}";
}
/**
* Return the value for the first property.
*/
public Object getA() {
return a;
}
/**
* Return the value for the second property.
*/
public Object getB() {
return b;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entry that = (Entry) o;
return a.equals(that.a) && b.equals(that.b);
}
@Override
public int hashCode() {
int result = a.hashCode();
result = 92821 * result + b.hashCode();
return result;
}
}
}
@@ -0,0 +1,35 @@
package io.ebean;
/**
* Defines the scope for PersistenceContext.
* <p/>
* Ebean has traditionally used Transaction scope for the PersistenceContext. This is used to change the scope to
* use (by default) and explicitly set the scope to use for an individual query.
*
* @see io.ebean.config.DatabaseConfig#setPersistenceContextScope(PersistenceContextScope)
* @see Query#setPersistenceContextScope(PersistenceContextScope)
*/
public enum PersistenceContextScope {
/**
* PersistenceContext is scoped to the transaction.
* <p/>
* If a transaction spans 2 or more queries that fetch the same bean in terms of same type
* and same Id value then they share the same bean instance.
* <p/>
* You may want to change to use QUERY scope when you want a query executing in a transaction to effectively
* ignore beans that have already been loaded (by other queries in the same transaction) and instead get a
* 'fresh copy' of the bean.
*/
TRANSACTION,
/**
* PersistenceContext is scoped to the query.
* <p/>
* This means that for this query running in an existing transaction then it will effectively ignore any beans
* that have already been queried/loaded by prior queries in the same transaction.
* <p/>
* You may use QUERY scope on a query that is executed in a transaction and you want to get a 'fresh copy' of the bean.
*/
QUERY
}
@@ -0,0 +1,20 @@
package io.ebean;
import javax.persistence.PersistenceException;
/**
* Captures and wraps IOException's occurring during ElasticSearch processing etc.
*/
public class PersistenceIOException extends PersistenceException {
private static final long serialVersionUID = -7630050437148176148L;
public PersistenceIOException(String msg, Exception cause) {
super(msg, cause);
}
public PersistenceIOException(Exception cause) {
super(cause);
}
}
@@ -0,0 +1,67 @@
package io.ebean;
/**
* A location for profiling transactions and queries.
* <p>
* Typically represents a class method in the form of class file and line of code that started
* the transaction or invoked the query.
* </p>
*/
public interface ProfileLocation {
/**
* Create and return a new ProfileLocation.
*/
static ProfileLocation create() {
return XServiceProvider.profileLocationFactory().create();
}
/**
* Create and return a new ProfileLocation with a given lineNumber and label.
*/
static ProfileLocation create(int lineNumber, String label) {
return XServiceProvider.profileLocationFactory().create(lineNumber, label);
}
/**
* Create and return a new ProfileLocation with a given location.
*/
static ProfileLocation createAt(String location) {
return XServiceProvider.profileLocationFactory().createAt(location);
}
/**
* Obtain the description returning true if this is the initial call.
*/
boolean obtain();
/**
* Return a short version of the location description.
*/
String location();
/**
* Return the short label.
*/
String label();
/**
* Return the full location.
*/
String fullLocation();
/**
* Add execution time.
*/
void add(long executionTime);
/**
* Return true if this request should be traced.
*/
boolean trace();
/**
* Set the number of times to trace the transactions for this profile location.
*/
void setTraceCount(int traceCount);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,84 @@
package io.ebean;
import java.util.Iterator;
/**
* Used to provide iteration over query results.
* <p>
* This can be used when you want to process a very large number of results and
* means that you don't have to hold all the results in memory at once (unlike
* findList(), findSet() etc where all the beans are held in the List or Set
* etc).
* </p>
* <p>
* Note that findIterate (and findEach and findEachWhile) uses a "per graph"
* persistence context scope and adjusts jdbc fetch buffer size for large
* queries. As such it is better to use findList for small queries.
* </p>
* <p>
* Remember that with {@link QueryIterator} you must call {@link QueryIterator#close()}
* when you have finished iterating the results. Use "try with resources" or ensure it
* is closed in a finally block.
* </p>
* <h3>Try finally style</h3>
* <pre>{@code
*
* Query<Customer> query = database.find(Customer.class)
* .where().gt("id", 0)
* .order("id")
* .setMaxRows(2);
*
* QueryIterator<Customer> it = query.findIterate();
* try {
* while (it.hasNext()) {
* Customer customer = it.next();
* // do something with customer ...
* }
* } finally {
* // close the underlying resources
* it.close();
* }
*
* }</pre>
* <p>
* <h3>Try with resources style</h3>
* <pre>{@code
*
* // try with resources
* try (QueryIterator<Customer> it = query.findIterate()) {
* while (it.hasNext()) {
* Customer customer = it.next();
* // do something with customer ...
* }
* }
*
* }</pre>
*
* @param <T> the type of entity bean in the iteration
*/
public interface QueryIterator<T> extends Iterator<T>, java.io.Closeable {
/**
* Returns <tt>true</tt> if the iteration has more elements.
*/
@Override
boolean hasNext();
/**
* Returns the next element in the iteration.
*/
@Override
T next();
/**
* Remove is not allowed.
*/
@Override
void remove();
/**
* Close the underlying resources held by this iterator.
*/
@Override
void close();
}
@@ -0,0 +1,22 @@
package io.ebean;
/**
* The type of the query being executed.
*/
public enum QueryType {
/**
* A find query.
*/
FIND,
/**
* An update query.
*/
UPDATE,
/**
* A delete query.
*/
DELETE
}
@@ -0,0 +1,150 @@
package io.ebean;
/**
* Used to build object graphs based on a raw SQL statement (rather than
* generated by Ebean).
* <p>
* If you don't want to build object graphs you can use {@link SqlQuery} instead
* which returns {@link SqlRow} objects rather than entity beans.
* <p>
* <b>Unparsed RawSql:</b>
* <p>
* When RawSql is created via {@link RawSqlBuilder#unparsed(String)} then Ebean can not
* modify the SQL at all. It can't add any extra expressions into the SQL.
* <p>
* <b>Parsed RawSql:</b>
* <p>
* When RawSql is created via {@link RawSqlBuilder#parse(String)} then Ebean will parse the
* SQL and find places in the SQL where it can add extra where expressions, add
* extra having expressions or replace the order by clause. If you want to
* explicitly tell Ebean where these insertion points are you can place special
* strings into your SQL ({@code ${where}} or {@code ${andWhere}} and {@code ${having}} or
* {@code ${andHaving})}.
* <p>
* If the SQL already includes a WHERE clause put in {@code ${andWhere}} in the location
* you want Ebean to add any extra where expressions. If the SQL doesn't have a
* WHERE clause put {@code ${where}} in instead. Similarly you can put in {@code ${having}} or
* {@code ${andHaving}} where you want Ebean put add extra having expressions.
* <p>
* <b>Aggregates:</b>
* <p>
* Often RawSql will be used with Aggregate functions (sum, avg, max etc). The
* follow example shows an example based on Total Order Amount -
* sum(d.order_qty*d.unit_price).
* </p>
* <p>
* We can use a OrderAggregate bean that has a &#064;Sql to indicate it is based
* on RawSql and not based on a real DB Table or DB View. It has some properties
* to hold the values for the aggregate functions (sum etc) and a &#064;OneToOne
* to Order.
* <p>
* <h3>Example OrderAggregate</h3>
* <pre>{@code
* ...
* // Sql indicates to that this bean
* // is based on RawSql rather than a table
*
* Entity
* Sql
* public class OrderAggregate {
*
* OneToOne
* Order order;
*
* Double totalAmount;
*
* Double totalItems;
*
* // getters and setters
* ...
* }
* }</pre>
*
* <h3>Example 1:</h3>
*
* <pre>{@code
*
* String sql = " select order_id, o.status, c.id, c.name, sum(l.order_qty*l.unit_price) as totalAmount"
* + " from order o"
* + " join customer c on c.id = o.customer_id "
* + " join order_line l on l.order_id = o.id " + " group by order_id, o.status ";
*
* RawSql rawSql = RawSqlBuilder.parse(sql)
* // map the sql result columns to bean properties
* .columnMapping("order_id", "order.id")
* .columnMapping("o.status", "order.status")
* .columnMapping("c.id", "order.customer.id")
* .columnMapping("c.name", "order.customer.name")
* // we don't need to map this one due to the sql column alias
* // .columnMapping("sum(d.order_qty*d.unit_price)", "totalAmount")
* .create();
*
* List<OrderAggregate> list = DB.find(OrderAggregate.class)
* .setRawSql(rawSql)
* .where().gt("order.id", 0)
* .having().gt("totalAmount", 20)
* .findList();
*
*
* }</pre>
*
* <h3>Example 2:</h3>
* <p>
* The following example uses a FetchConfig().query() so that after the initial
* RawSql query is executed Ebean executes a secondary query to fetch the
* associated order status, orderDate along with the customer name.
*
* <pre>{@code
*
* String sql = " select order_id, 'ignoreMe', sum(l.order_qty*l.unit_price) as totalAmount "
* + " from order_line l"
* + " group by order_id ";
*
* RawSql rawSql = RawSqlBuilder.parse(sql)
* .columnMapping("order_id", "order.id")
* .columnMappingIgnore("'ignoreMe'")
* .create();
*
* List<OrderAggregate> orders = DB.find(OrderAggregate.class)
* .setRawSql(rawSql)
* .fetch("order", "status,orderDate", new FetchConfig().query())
* .fetch("order.customer", "name")
* .where().gt("order.id", 0)
* .having().gt("totalAmount", 20)
* .order().desc("totalAmount")
* .setMaxRows(10)
* .findList();
*
* }</pre>
* <h3>Example 3: tableAliasMapping</h3>
* <p>
* Instead of mapping each column you can map each table alias to a path using tableAliasMapping().
* <pre>{@code
*
* String rs = "select o.id, o.status, c.id, c.name, "+
* " l.id, l.order_qty, p.id, p.name " +
* "from orders o join o_customer c on c.id = o.customer_id " +
* "join order_line l on l.order_id = o.id " +
* "join product p on p.id = l.product_id " +
* "where o.id <= :maxOrderId and p.id = :productId "+
* "order by o.id, l.id asc";
*
* RawSql rawSql = RawSqlBuilder.parse(rs)
* .tableAliasMapping("c", "customer")
* .tableAliasMapping("l", "lines")
* .tableAliasMapping("p", "lines.product")
* .create();
*
* List<Order> ordersFromRaw = DB.find(Order.class)
* .setRawSql(rawSql)
* .setParameter("maxOrderId", 2)
* .setParameter("productId", 1)
* .findList();
*
* }</pre>
* <p>
* Note that lazy loading also works with object graphs built with RawSql.
*/
public interface RawSql {
}
@@ -0,0 +1,95 @@
package io.ebean;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Builds RawSql instances from a SQL string and column mappings.
* <p>
* Note that RawSql can also be defined in ebean-orm.xml files and be used as a
* named query.
* </p>
*
* @see RawSql
*/
public interface RawSqlBuilder {
/**
* Create and return a RawSql object based on the resultSet and list of properties the columns in
* the resultSet map to.
* <p>
* The properties listed in the propertyNames must be in the same order as the columns in the
* resultSet.
*/
static RawSql resultSet(ResultSet resultSet, String... propertyNames) {
return XServiceProvider.rawSql().resultSet(resultSet, propertyNames);
}
/**
* Create and return a SqlRow based on the resultSet with dbTrueValue and binaryOptimizedUUID options.
*/
static SqlRow sqlRow(ResultSet resultSet, final String dbTrueValue, boolean binaryOptimizedUUID) throws SQLException {
return XServiceProvider.rawSql().sqlRow(resultSet, dbTrueValue, binaryOptimizedUUID);
}
/**
* Return an unparsed RawSqlBuilder. Unlike a parsed one this query can not be
* modified - so no additional WHERE or HAVING expressions can be added to
* this query.
*/
static RawSqlBuilder unparsed(String sql) {
return XServiceProvider.rawSql().unparsed(sql);
}
/**
* Return a RawSqlBuilder parsing the sql.
* <p>
* The sql statement will be parsed so that Ebean can determine where it can
* insert additional WHERE or HAVING expressions.
* </p>
* <p>
* Additionally the selected columns are parsed to determine the column
* ordering. This also means additional checks can be made with the column
* mapping - specifically we can check that all columns are mapped and that
* correct column names are entered into the mapping.
* </p>
*/
static RawSqlBuilder parse(String sql) {
return XServiceProvider.rawSql().parsed(sql);
}
/**
* Set the mapping of a DB Column to a bean property.
* <p>
* For Unparsed SQL the columnMapping MUST be defined in the same order that
* the columns appear in the SQL statement.
* </p>
*
* @param dbColumn the DB column that we are mapping to a bean property
* @param propertyName the bean property that we are mapping the DB column to.
*/
RawSqlBuilder columnMapping(String dbColumn, String propertyName);
/**
* Ignore this DB column. It is not mapped to any bean property.
*/
RawSqlBuilder columnMappingIgnore(String dbColumn);
/**
* Modify any column mappings with the given table alias to have the path prefix.
* <p>
* For example modify all mappings with table alias "c" to have the path prefix "customer".
* </p>
* <p>
* For the "Root type" you don't need to specify a tableAliasMapping.
* </p>
*/
RawSqlBuilder tableAliasMapping(String tableAlias, String path);
/**
* Create the immutable RawSql object. Do this after all the column mapping
* has been defined.
*/
RawSql create();
}
@@ -0,0 +1,40 @@
package io.ebean;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Used with SqlQuery to process potentially large queries reading directly from the JDBC ResultSet.
* <p>
* This provides a low level option that reads directly from the JDBC ResultSet.
* </p>
*
* <pre>{@code
*
* String sql = "select id, name, status from o_customer order by name desc";
*
* DB.sqlQuery(sql)
* .findEachRow((resultSet, rowNum) -> {
*
* // read directly from ResultSet
*
* long id = resultSet.getLong(1);
* String name = resultSet.getString(2);
*
* // do something interesting with the data
*
* });
*
* }</pre>
*/
@FunctionalInterface
public interface RowConsumer {
/**
* Read the data from the ResultSet and process it.
*
* @param resultSet The JDBC ResultSet positioned to the current row
* @param rowNum The number of the current row being mapped.
*/
void accept(ResultSet resultSet, int rowNum) throws SQLException;
}
@@ -0,0 +1,63 @@
package io.ebean;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Used with SqlQuery to map raw JDBC ResultSet to objects.
* <p>
* This provides a low level mapping option with direct use of JDBC ResultSet
* with the option of having logic in the mapping. For example, only map some
* columns depending on the values read from other columns.
* </p>
* <p>
* For straight mapping into beans then DtoQuery would be the first choice as
* it can automatically map the ResultSet into beans.
* </p>
*
* <pre>{@code
*
* //
* // Map from ResultSet to CustomerDto bean
* //
* class CustomerMapper implements RowMapper<CustomerDto> {
*
* Override
* public CustomerDto map(ResultSet rset, int rowNum) throws SQLException {
*
* long id = rset.getLong(1);
* String name = rset.getString(2);
* String status = rset.getString(3);
*
* return new CustomerDto(id, name, status);
* }
* }
*
*
* //
* // Then use the mapper
* //
*
* String sql = "select id, name, status from o_customer where name = ?";
*
* CustomerDto rob = DB.sqlQuery(sql)
* .setParameter(1, "Rob")
* .mapTo(CUSTOMER_MAPPER)
* .findOne();
*
*
* }</pre>
*
* @param <T> The type the row data is mapped into.
*/
@FunctionalInterface
public interface RowMapper<T> {
/**
* Read the data from the ResultSet and map to the return type.
*
* @param resultSet The JDBC ResultSet positioned to the current row
* @param rowNum The number of the current row being mapped.
*/
T map(ResultSet resultSet, int rowNum) throws SQLException;
}
@@ -0,0 +1,62 @@
package io.ebean;
import java.net.URL;
import java.util.Map;
/**
* Runs DDL and SQL scripts.
* <p/>
* Typically these are scripts used for testing such as seed SQL scripts or truncate SQL scripts.
* <p/>
* Scripts are executed in their own transaction and committed on successful completion.
*
* <h3>Example of simple use</h3>
* <pre>{@code
*
* Database database = DB.getDefault();
* database.script().run("/scripts/test-script.sql");
*
* }</pre>
*/
public interface ScriptRunner {
/**
* Run a script given the resource path (that should start with "/").
*/
void run(String path);
/**
* Run a script given the resource path (that should start with "/") and place holders.
*
* <pre>{@code
*
* Map<String,String> placeholders = new HashMap<>();
* placeholders.put("tableName", "e_basic");
*
* Database database = DB.getDefault();
* database.script().run("/scripts/test-script.sql", placeholders);
*
* }</pre>
*/
void run(String path, Map<String, String> placeholderMap);
/**
* Run a DDL or SQL script given the resource.
*/
void run(URL resource);
/**
* Run a DDL or SQL script given the resource and place holders.
*/
void run(URL resource, Map<String, String> placeholderMap);
/**
* Run the raw provided DDL or SQL script.
*
* @param name The name of the script for logging purposes
* @param content The SQL content
* @param useAutoCommit Set to true to use auto commit true and continue when any errors occur
*/
void runScript(String name, String content, boolean useAutoCommit);
}
@@ -0,0 +1,18 @@
package io.ebean;
import javax.persistence.OptimisticLockException;
/**
* Thrown at SERIALIZABLE isolation level for non-recoverable concurrent conflict.
*/
public class SerializableConflictException extends OptimisticLockException {
private static final long serialVersionUID = 1L;
/**
* Create with a message and cause.
*/
public SerializableConflictException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,369 @@
package io.ebean;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* Query object for performing native SQL queries that return SqlRow or directly read
* ResultSet using a RowMapper.
* <p>
* The returned SqlRow objects are similar to a LinkedHashMap with some type
* conversion support added.
* </p>
* <p>
* Refer to {@link DtoQuery} for native sql queries returning DTO beans.
* </p>
* <p>
* Refer to {@link Database#findNative(Class, String)} for native sql queries returning entity beans.
* </p>
*
* <pre>{@code
*
* // example using named parameters
*
* String sql = "select id, name from customer where name like :name and status_code = :status";
*
* List<SqlRow> list =
* DB.sqlQuery(sql)
* .setParameter("name", "Acme%")
* .setParameter("status", "ACTIVE")
* .findList();
*
* }</pre>
*/
public interface SqlQuery extends Serializable {
/**
* Execute the query returning a list.
*/
@Nonnull
List<SqlRow> findList();
/**
* Execute the SqlQuery iterating a row at a time.
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEach(Consumer<SqlRow> consumer);
/**
* Execute the SqlQuery iterating a row at a time with the ability to stop consuming part way through.
* <p>
* Returning false after processing a row stops the iteration through the query results.
* </p>
* <p>
* This streaming type query is useful for large query execution as only 1 row needs to be held in memory.
* </p>
*/
void findEachWhile(Predicate<SqlRow> consumer);
/**
* Execute the query returning a single row or null.
* <p>
* If this query finds 2 or more rows then it will throw a
* PersistenceException.
* </p>
*/
@Nullable
SqlRow findOne();
/**
* Deprecated migrate to use {@link #mapTo(RowMapper)}
*/
@Deprecated
<T> T findOne(RowMapper<T> mapper);
/**
* Deprecated migrate to use {@link #mapTo(RowMapper)}
*/
@Deprecated
<T> List<T> findList(RowMapper<T> mapper);
/**
* Execute the query reading each row from ResultSet using the RowConsumer.
* <p>
* This provides a low level option that reads directly from the JDBC ResultSet
* and is good for processing very large results where (unlike findList) we don't
* hold all the results in memory but instead can process row by row.
* </p>
*
* <pre>{@code
*
* String sql = "select id, name, status from customer order by name desc";
*
* DB.sqlQuery(sql)
* .findEachRow((resultSet, rowNum) -> {
*
* // read directly from ResultSet
*
* long id = resultSet.getLong(1);
* String name = resultSet.getString(2);
*
* // do something interesting with the data
*
* });
*
* }</pre>
*
* @param consumer Used to read and process each ResultSet row.
*/
void findEachRow(RowConsumer consumer);
/**
* Execute the query returning an optional row.
*/
@Nonnull
Optional<SqlRow> findOneOrEmpty();
/**
* Deprecated - migrate to <code>.mapToScalar(attributeType).findOne()</code>.
* <pre>{@code
*
* .mapToScalar(BigDecimal.class)
* .findOne();
* }
*/
@Deprecated
<T> T findSingleAttribute(Class<T> attributeType);
/**
* Deprecated - migrate to <code>.mapToScalar(BigDecimal.class).findOne()</code>.
* <pre>{@code
*
* .mapToScalar(BigDecimal.class)
* .findOne();
* }
*/
@Deprecated
BigDecimal findSingleDecimal();
/**
* Deprecated - migrate to <code>.mapToScalar(Long.class).findOne()</code>.
* <pre>{@code
*
* .mapToScalar(Long.class)
* .findOne();
* }
*/
@Deprecated
Long findSingleLong();
/**
* Deprecated - migrate to <code>.mapToScalar(Long.class).findList()</code>.
* <pre>{@code
*
* .mapToScalar(Long.class)
* .findList();
* }
*/
@Deprecated
<T> List<T> findSingleAttributeList(Class<T> attributeType);
/**
* Set one of more positioned parameters.
* <p>
* This is a convenient alternative to multiple calls to {@link #setParameter(Object)}.
*
* <pre>{@code
*
* String sql = "select id, name from customer where name like ? and status = ?";
*
* List<SqlRow> list =
* DB.sqlQuery(sql)
* .setParameters("Rob", Status.NEW)
* .findList();
*
*
* // effectively the same as ...
*
* .setParameter("Rob")
* .setParameter("Status.NEW)
*
* // and ...
*
* .setParameter(1, "Rob")
* .setParameter(2, "Status.NEW)
*
* }</pre>
*/
SqlQuery setParameters(Object... values);
/**
* Deprecated migrate to setParameters(Object... values)
*/
@Deprecated
SqlQuery setParams(Object... values);
/**
* Set the next bind parameter by position.
* <pre>{@code
*
* String sql = "select id, name from customer where name like ? and status = ?";
*
* List<SqlRow> list =
* DB.sqlQuery(sql)
* .setParameter("Rob")
* .setParameter("Status.NEW)
* .findList();
*
* // the same as ...
*
* .setParameters("Rob", Status.NEW)
*
* // and ...
*
* .setParameter(1, "Rob")
* .setParameter(2, "Status.NEW)
*
* }</pre>
*
* <p>
* When binding a collection of values into a IN expression we should use
* indexed parameters like ?1, ?2, ?3 etc rather than just ?.
* </p>
*
* <pre>{@code
*
* String sql = "select c.id, c.name from customer c where c.name in (?1)";
*
* List<SqlRow> rows = DB.sqlQuery(sql)
* .setParameter(asList("Rob", "Fiona", "Jack"))
* .findList();
*
*
* List<SqlRow> rows = DB.sqlQuery(sql)
* .setParameter(1, asList("Rob", "Fiona", "Jack"))
* .findList();
* }</pre>
*
* @param value The value to bind
*/
SqlQuery setParameter(Object value);
/**
* Bind the parameter by its index position (1 based like JDBC).
* <p>
* When binding a collection of values into a IN expression we should use
* indexed parameters like ?1, ?2, ?3 etc rather than just ?.
* </p>
*
* <pre>{@code
*
* String sql = "select c.id, c.name from customer c where c.name in (?1)";
*
* List<SqlRow> rows = DB.sqlQuery(sql)
* .setParameter(asList("Rob", "Fiona", "Jack"))
* .findList();
*
*
* List<SqlRow> rows = DB.sqlQuery(sql)
* .setParameter(1, asList("Rob", "Fiona", "Jack"))
* .findList();
* }</pre>
*/
SqlQuery setParameter(int position, Object value);
/**
* Bind the named parameter value.
*/
SqlQuery setParameter(String name, Object value);
/**
* Set the index of the first row of the results to return.
*/
SqlQuery setFirstRow(int firstRow);
/**
* Set the maximum number of query results to return.
*/
SqlQuery setMaxRows(int maxRows);
/**
* Set a timeout on this query.
* <p>
* This will typically result in a call to setQueryTimeout() on a
* preparedStatement. If the timeout occurs an exception will be thrown - this
* will be a SQLException wrapped up in a PersistenceException.
* </p>
*
* @param secs the query timeout limit in seconds. Zero means there is no limit.
*/
SqlQuery setTimeout(int secs);
/**
* Set a label that can be put on performance metrics that are collected.
*/
SqlQuery setLabel(String label);
/**
* A hint which for JDBC translates to the Statement.fetchSize().
* <p>
* Gives the JDBC driver a hint as to the number of rows that should be
* fetched from the database when more rows are needed for ResultSet.
* </p>
*/
SqlQuery setBufferFetchSizeHint(int bufferFetchSizeHint);
/**
* The query result maps to a single scalar value like Long, BigDecimal,
* String, UUID, OffsetDateTime etc.
* <p>
* Any scalar type Ebean is aware of can be used including java time
* types like Instant, LocalDate, OffsetDateTime, UUID, Inet, Cdir etc.
*
* <pre>{@code
*
* String sql = " select min(updtime) from o_order_detail " +
* " where unit_price > ? and updtime is not null ";
*
* OffsetDateTime minCreated = DB.sqlQuery(sql)
* .setParameter(42)
* .mapToScalar(OffsetDateTime.class)
* .findOne();
*
* }</pre>
*
* @param attributeType The type the result is returned as
* @return The query to execute via findOne() findList() etc
*/
<T> TypeQuery<T> mapToScalar(Class<T> attributeType);
/**
* Use a RowMapper to map the result to beans.
*
* @param mapper Maps rows to beans
* @param <T> The type of beans mapped to
* @return The query to execute by findOne() findList() etc
*/
<T> TypeQuery<T> mapTo(RowMapper<T> mapper);
/**
* Query mapping to single scalar values.
*
* @param <T> The type of the scalar values
*/
interface TypeQuery<T> {
/**
* Return the single value.
*/
T findOne();
/**
* Return the single value that is optional.
*/
Optional<T> findOneOrEmpty();
/**
* Return the list of values.
*/
List<T> findList();
}
}
@@ -0,0 +1,181 @@
package io.ebean;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
/**
* Used to return raw SQL query results.
* <p>
* Refer to {@link SqlQuery} for examples.
* </p>
* <p>
* There are convenience methods such as getInteger(), getBigDecimal() etc. The
* reason for these methods is that the values put into this map often come
* straight from the JDBC resultSet. Depending on the JDBC driver it may put a
* different type into a given property. For example an Integer, BigDecimal,
* Double could all be put into a property depending on the JDBC driver used.
* These convenience methods automatically convert the value as required
* returning the type you expect.
* </p>
*/
public interface SqlRow extends Serializable, Map<String, Object> {
/**
* Return the property names (String).
* <p>
* Internally this uses LinkedHashMap and so the order of the property names
* should be predictable and ordered by the use of LinkedHashMap.
* </p>
*/
Iterator<String> keys();
/**
* Remove a property from the map. Returns the value of the removed property.
*/
@Override
Object remove(Object name);
/**
* Return a property value by its name.
*/
@Override
Object get(Object name);
/**
* Set a value to a property.
*/
@Override
Object put(String name, Object value);
/**
* Exactly the same as the put method.
* <p>
* I added this method because it seems more bean like to have get and set
* methods.
* </p>
*/
Object set(String name, Object value);
/**
* Return a property as a Boolean.
*/
Boolean getBoolean(String name);
/**
* Return a property as a UUID.
*/
UUID getUUID(String name);
/**
* Return a property as an Integer.
*/
Integer getInteger(String name);
/**
* Return a property value as a BigDecimal.
*/
BigDecimal getBigDecimal(String name);
/**
* Return a property value as a Long.
*/
Long getLong(String name);
/**
* Return the property value as a Double.
*/
Double getDouble(String name);
/**
* Return the property value as a Float.
*/
Float getFloat(String name);
/**
* Return a property as a String.
*/
String getString(String name);
/**
* Return the property as a java.util.Date.
*/
java.util.Date getUtilDate(String name);
/**
* Return the property as a sql date.
*/
Date getDate(String name);
/**
* Return the property as a sql timestamp.
*/
Timestamp getTimestamp(String name);
/**
* String description of the underlying map.
*/
@Override
String toString();
/**
* Clear the map.
*/
@Override
void clear();
/**
* Returns true if the map contains the property.
*/
@Override
boolean containsKey(Object key);
/**
* Returns true if the map contains the value.
*/
@Override
boolean containsValue(Object value);
/**
* Returns the entrySet of the map.
*/
@Override
Set<Map.Entry<String, Object>> entrySet();
/**
* Returns true if the map is empty.
*/
@Override
boolean isEmpty();
/**
* Returns the key set of the map.
*/
@Override
Set<String> keySet();
/**
* Put all the values from t into this map.
*/
@Override
void putAll(Map<? extends String, ?> t);
/**
* Return the size of the map.
*/
@Override
int size();
/**
* Return the values from this map.
*/
@Override
Collection<Object> values();
}
@@ -0,0 +1,338 @@
package io.ebean;
/**
* A SqlUpdate for executing insert update or delete statements.
* <p>
* Provides a simple way to execute raw SQL insert update or delete statements
* without having to resort to JDBC.
* <p>
* Supports the use of positioned or named parameters and can automatically
* notify Ebean of the table modified so that Ebean can maintain its cache.
* <p>
* Note that {@link #setAutoTableMod(boolean)} and
* Ebean#externalModification(String, boolean, boolean, boolean)} can be to
* notify Ebean of external changes and enable Ebean to maintain it's "L2"
* server cache.
*
* <h2>Positioned parameter example</h2>
* <pre>{@code
*
* // example using 'positioned' parameters
*
* String sql = "insert into audit_log (group, title, description) values (?, ?, ?);
*
* int rows =
* DB.sqlUpdate(sql)
* .setParams("login", "new user", "user rob was created")
* .executeNow();
*
* }</pre>
*
* <h2>Named parameter example</h2>
* <pre>{@code
*
* // example using 'named' parameters
*
* String sql = "update topic set post_count = :count where id = :id";
*
* int rows =
* DB.sqlUpdate(sql)
* .setParameter("id", 1)
* .setParameter("count", 50)
* .execute();
*
* String msg = "There were " + rows + " rows updated";
*
* }</pre>
*
* <h2>Index parameter examples (e.g. ?1, ?2, ?3 ...)</h2>
* <p>
* We can use index parameters like ?1, ?2, ?3 etc when binding arrays/collections
* of values into an IN expression.
* </p>
* <pre>{@code
*
* // Binding a list of 3 values (9991, 9992, 9993) into an IN expression
*
* DB.sqlUpdate("delete from o_customer where name = ? and id in (?2)")
* .setParameter(1, "Foo")
* .setParameter(2, asList(9991, 9992, 9993))
* .execute();
*
* // note this effectively is the same as
*
* DB.sqlUpdate("delete from o_customer where name = ? and id in (?2)")
* .setParameter("Foo")
* .setParameter(asList(9991, 9992, 9993))
* .execute();
*
* }</pre>
*
* <h3>Example: Using setParameter()</h3>
* <pre>{@code
*
* String sql = "insert into audit_log (id, description, modified_description) values (?,?,?)";
*
* SqlUpdate insert = DB.sqlUpdate(sql);
*
* try (Transaction txn = DB.beginTransaction()) {
* txn.setBatchMode(true);
*
* insert.setParameter(10000);
* insert.setParameter("hello");
* insert.setParameter("rob");
* insert.execute();
*
* insert.setParameter(10001);
* insert.setParameter("goodbye");
* insert.setParameter("rob");
* insert.execute();
*
* insert.setParameter(10002);
* insert.setParameter("chow");
* insert.setParameter("bob");
* insert.execute();
*
* txn.commit();
* }
* }</pre>
* <p>
* An alternative to the batch mode on the transaction is to use addBatch() and executeBatch() like:
* </p>
* <pre>{@code
*
* try (Transaction txn = DB.beginTransaction()) {
*
* insert.setParameter(10000);
* insert.setParameter("hello");
* insert.setParameter("rob");
* insert.addBatch();
*
* insert.setParameter(10001);
* insert.setParameter("goodbye");
* insert.setParameter("rob");
* insert.addBatch();
*
* insert.setParameter(10002);
* insert.setParameter("chow");
* insert.setParameter("bob");
* insert.addBatch();
*
* int[] rows = insert.executeBatch();
*
* txn.commit();
* }
*
* }</pre>
*
* @see Update
* @see SqlQuery
* @see CallableSql
*/
public interface SqlUpdate {
/**
* Execute the update returning the number of rows modified.
* <p>
* Note that if the transaction has batch mode on then this update will use JDBC batch and may not execute until
* later - at commit time or a transaction flush. In this case this method returns -1 indicating that the
* update has been batched for later execution.
* </p>
* <p>
* After you have executed the SqlUpdate you can bind new variables using
* {@link #setParameter(String, Object)} etc and then execute the SqlUpdate
* again.
* </p>
* <p>
* For JDBC batch processing refer to
* {@link Transaction#setBatchMode(boolean)} and
* {@link Transaction#setBatchSize(int)}.
* </p>
*/
int execute();
/**
* Execute the statement now regardless of the JDBC batch mode of the transaction.
*/
int executeNow();
/**
* Execute when addBatch() has been used to batch multiple bind executions.
*
* @return The row counts for each of the batched statements.
*/
int[] executeBatch();
/**
* Add the statement to batch processing to then later execute via executeBatch().
*/
void addBatch();
/**
* Return the generated key value.
*/
Object getGeneratedKey();
/**
* Execute and return the generated key. This is effectively a short cut for:
* <p>
* <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.
* <p>
* If this is true then cache invalidation and text index management are aware
* of the modification.
* </p>
*/
boolean isAutoTableMod();
/**
* Set this to false if you don't want eBean to automatically deduce the table
* modification information and process it.
* <p>
* Set this to false if you don't want any cache invalidation or text index
* management to occur. You may do this when say you update only one column
* and you know that it is not important for cached objects or text indexes.
* </p>
*/
SqlUpdate setAutoTableMod(boolean isAutoTableMod);
/**
* Return the label that can be seen in the transaction logs.
*/
String getLabel();
/**
* Set a descriptive text that can be put into the transaction log.
* <p>
* Useful when identifying the statement in the transaction log.
* </p>
*/
SqlUpdate setLabel(String label);
/**
* Set to true when we want to use getGeneratedKeys with this statement.
*/
SqlUpdate setGetGeneratedKeys(boolean getGeneratedKeys);
/**
* Return the sql statement.
*/
String getSql();
/**
* Return the generated sql that has named parameters converted to positioned parameters.
*/
String getGeneratedSql();
/**
* Return the timeout used to execute this statement.
*/
int getTimeout();
/**
* Set the timeout in seconds. Zero implies no limit.
* <p>
* This will set the query timeout on the underlying PreparedStatement. If the
* timeout expires a SQLException will be throw and wrapped in a
* PersistenceException.
* </p>
*/
SqlUpdate setTimeout(int secs);
/**
* Set one of more positioned parameters.
* <p>
* This is a convenient alternative to multiple setParameter() calls.
*
* <pre>{@code
*
* String sql = "insert into audit_log (id, name, version) values (?,?,?)";
*
* DB.sqlUpdate(sql)
* .setParameters(UUID.randomUUID(), "Hello", 1)
* .executeNow();
*
*
* // is the same as ...
*
* DB.sqlUpdate(sql)
* .setParameter(UUID.randomUUID())
* .setParameter("Hello")
* .setParameter(1)
* .executeNow();
*
* // which is the same as ...
*
* DB.sqlUpdate(sql)
* .setParameter(1, UUID.randomUUID())
* .setParameter(2, "Hello")
* .setParameter(3, 1)
* .executeNow();
*
* }</pre>
*/
SqlUpdate setParameters(Object... values);
/**
* Deprecated migrate to setParameters(Object... values).
*/
@Deprecated
SqlUpdate setParams(Object... values);
/**
* Set the next bind parameter by position.
*
* @param value The value to bind
*/
SqlUpdate setParameter(Object value);
/**
* Deprecated migrate to setParameter(value).
*/
@Deprecated
SqlUpdate setNextParameter(Object value);
/**
* Set a parameter via its index position.
*/
SqlUpdate setParameter(int position, Object value);
/**
* Set a null parameter via its index position.
*/
SqlUpdate setNull(int position, int jdbcType);
/**
* Set a null valued parameter using its index position.
*/
SqlUpdate setNullParameter(int position, int jdbcType);
/**
* Set a named parameter value.
*/
SqlUpdate setParameter(String name, Object param);
/**
* Set a named parameter that has a null value. Exactly the same as
* {@link #setNullParameter(String, int)}.
*/
SqlUpdate setNull(String name, int jdbcType);
/**
* Set a named parameter that has a null value.
*/
SqlUpdate setNullParameter(String name, int jdbcType);
}
@@ -0,0 +1,573 @@
package io.ebean;
import io.ebean.annotation.DocStoreMode;
import io.ebean.annotation.PersistBatch;
import io.ebean.config.DocStoreConfig;
import io.ebean.config.DatabaseConfig;
import javax.persistence.PersistenceException;
import java.sql.Connection;
/**
* The Transaction object. Typically representing a JDBC or JTA transaction.
*/
public interface Transaction extends AutoCloseable {
/**
* Return the current transaction (of the default database) or null if there is
* no current transaction in scope.
* <p>
* This is the same as <code>DB.currentTransaction()</code>
* </p>
* <p>
* This returns the current transaction for the default database.
* </p>
*
* @see DB#currentTransaction()
* @see Database#currentTransaction()
*/
static Transaction current() {
return DB.currentTransaction();
}
/**
* Read Committed transaction isolation. Same as
* java.sql.Connection.TRANSACTION_READ_COMMITTED.
*/
int READ_COMMITTED = java.sql.Connection.TRANSACTION_READ_COMMITTED;
/**
* Read Uncommitted transaction isolation. Same as
* java.sql.Connection.TRANSACTION_READ_UNCOMMITTED.
*/
int READ_UNCOMMITTED = java.sql.Connection.TRANSACTION_READ_UNCOMMITTED;
/**
* Repeatable read transaction isolation. Same as
* java.sql.Connection.TRANSACTION_REPEATABLE_READ.
*/
int REPEATABLE_READ = java.sql.Connection.TRANSACTION_REPEATABLE_READ;
/**
* Serializable transaction isolation. Same as
* java.sql.Connection.TRANSACTION_SERIALIZABLE.
*/
int SERIALIZABLE = java.sql.Connection.TRANSACTION_SERIALIZABLE;
/**
* Register a TransactionCallback with this transaction.
*/
void register(TransactionCallback callback);
/**
* Set a label on the transaction.
* <p>
* This label is used to group transaction execution times for performance metrics reporting.
* </p>
*/
void setLabel(String label);
/**
* Return true if this transaction is read only.
*/
boolean isReadOnly();
/**
* Set whether this transaction should be readOnly.
*/
void setReadOnly(boolean readOnly);
/**
* Commits the transaction at this point with the expectation that another
* commit (or rollback or end) will occur later to complete the transaction.
* <p>
* This is similar to commit() but leaves the transaction "Active".
* </p>
* <h3>Functions</h3>
* <ul>
* <li>Flush the JDBC batch buffer</li>
* <li>Call commit on the underlying JDBC connection</li>
* <li>Trigger any registered TransactionCallbacks</li>
* <li>Perform post-commit processing updating L2 cache, ElasticSearch etc</li>
* </ul>
*/
void commitAndContinue();
/**
* Commit the transaction.
* <p>
* This performs commit and completes the transaction closing underlying resources and
* marking the transaction as "In active".
* </p>
* <h3>Functions</h3>
* <ul>
* <li>Flush the JDBC batch buffer</li>
* <li>Call commit on the underlying JDBC connection</li>
* <li>Trigger any registered TransactionCallbacks</li>
* <li>Perform post-commit processing updating L2 cache, ElasticSearch etc</li>
* <li>Close any underlying resources, closing the underlying JDBC connection</li>
* <li>Mark the transaction as "Inactive"</li>
* </ul>
*/
void commit();
/**
* Rollback the transaction.
* <p>
* This performs rollback, closes underlying resources and marks the transaction as "In active".
* </p>
* <h3>Functions</h3>
* <ul>
* <li>Call rollback on the underlying JDBC connection</li>
* <li>Trigger any registered TransactionCallbacks</li>
* <li>Close any underlying resources, closing the underlying JDBC connection</li>
* <li>Mark the transaction as "Inactive"</li>
* </ul>
*/
void rollback() throws PersistenceException;
/**
* Rollback the transaction specifying a throwable that caused the rollback to
* occur.
* <p>
* If you are using transaction logging this will log the throwable in the
* transaction logs.
* </p>
*/
void rollback(Throwable e) throws PersistenceException;
/**
* Set when we want nested transactions to use Savepoint's.
* <p>
* This means that for a nested transaction:
* <ul>
* <li>begin transaction maps to creating a savepoint</li>
* <li>commit transaction maps to releasing a savepoint</li>
* <li>rollback transaction maps to rollback a savepoint</li>
* </ul>
*/
void setNestedUseSavepoint();
/**
* Mark the transaction for rollback only.
*/
void setRollbackOnly();
/**
* Return true if the transaction is marked as rollback only.
*/
boolean isRollbackOnly();
/**
* If the transaction is active then perform rollback. Otherwise do nothing.
*/
void end();
/**
* Synonym for end() to support AutoClosable.
*/
@Override
void close();
/**
* Return true if the transaction is active.
*/
boolean isActive();
/**
* Set the behavior for document store updates on this transaction.
* <p>
* For example, set the mode to DocStoreEvent.IGNORE for this transaction and
* then any changes via this transaction are not sent to the doc store. This
* would be used when doing large bulk inserts into the database and we want
* to control how that is sent to the document store.
* </p>
*/
void setDocStoreMode(DocStoreMode mode);
/**
* Set the batch size to use for sending messages to the document store.
* <p>
* You might set this if you know the changes in this transaction result in especially large or
* especially small payloads and want to adjust the batch size to match.
* </p>
* <p>
* Setting this overrides the default of {@link DocStoreConfig#getBulkBatchSize()}
* </p>
*/
void setDocStoreBatchSize(int batchSize);
/**
* Explicitly turn off or on the cascading nature of save() and delete(). This
* gives the developer exact control over what beans are saved and deleted
* rather than Ebean cascading detecting 'dirty/modified' beans etc.
* <p>
* This is useful if you can getting back entity beans from a layer of code
* (potentially remote) and you prefer to have exact control.
* </p>
* <p>
* This may also be useful if you are using jdbc batching with jdbc drivers
* that do not support getGeneratedKeys.
* </p>
*/
void setPersistCascade(boolean persistCascade);
/**
* Set to true when you want all loaded properties to be included in the update
* (rather than just the changed properties).
* <p>
* You might set this when using JDBC batch in order to get multiple updates
* with slightly different sets of changed properties into the same statement
* and hence better JDBC batch performance.
* </p>
*/
void setUpdateAllLoadedProperties(boolean updateAllLoadedProperties);
/**
* Set if the L2 cache should be skipped for "find by id" and "find by natural key" queries.
* <p>
* By default {@link DatabaseConfig#isSkipCacheAfterWrite()} is true and that means that for
* "find by id" and "find by natural key" queries which normally hit L2 bean cache automatically
* - will not do so after a persist/write on the transaction.
* </p>
* <p>
* This method provides explicit control over whether "find by id" and "find by natural key"
* will skip the L2 bean cache or not (regardless of whether the transaction is considered "read only").
* </p>
* <p>
* Refer to {@link DatabaseConfig#setSkipCacheAfterWrite(boolean)} for configuring the default behavior
* for using the L2 bean cache in transactions spanning multiple query/persist requests.
* </p>
*
* <pre>{@code
*
* // assume Customer has L2 bean caching enabled ...
*
* try (Transaction transaction = DB.beginTransaction()) {
*
* // this uses L2 bean cache as the transaction
* // ... is considered "query only" at this point
* Customer.find.byId(42);
*
* // transaction no longer "query only" once
* // ... a bean has been saved etc
* someBean.save();
*
* // will NOT use L2 bean cache as the transaction
* // ... is no longer considered "query only"
* Customer.find.byId(55);
*
*
*
* // explicit control - please use L2 bean cache
*
* transaction.setSkipCache(false);
* Customer.find.byId(77); // hit the l2 bean cache
*
*
* // explicit control - please don't use L2 bean cache
*
* transaction.setSkipCache(true);
* Customer.find.byId(99); // skips l2 bean cache
*
*
* transaction.commit();
* }
*
* }</pre>
*
* @see DatabaseConfig#isSkipCacheAfterWrite()
*/
void setSkipCache(boolean skipCache);
/**
* Return true if the L2 cache should be skipped. More accurately if true then find by id
* and find by natural key queries should NOT automatically use the L2 bean cache.
*/
boolean isSkipCache();
/**
* Turn on or off use of JDBC statement batching.
* <p>
* Calls to save(), delete(), insert() and execute() all support batch
* processing. This includes normal beans, CallableSql and UpdateSql.
* </p>
*
* <pre>{@code
*
* try (Transaction transaction = database.beginTransaction()) {
*
* // turn on JDBC batch
* transaction.setBatchMode(true);
*
* // tune the batch size
* transaction.setBatchSize(50);
*
* ...
*
* transaction.commit();
* }
*
* }</pre>
*
* <h3>getGeneratedKeys</h3>
* <p>
* Often with large batch inserts we want to turn off getGeneratedKeys. We do
* this via {@link #setGetGeneratedKeys(boolean)}.
* Also note that some JDBC drivers do not support getGeneratedKeys in JDBC batch mode.
* </p>
* <pre>{@code
*
* try (Transaction transaction = database.beginTransaction()) {
*
* transaction.setBatchMode(true);
* transaction.setBatchSize(100);
* // insert but don't bother getting back the generated keys
* transaction.setBatchGetGeneratedKeys(false);
*
*
* // perform lots of inserts ...
* ...
*
* transaction.commit();
* }
*
* }</pre>
*
* <h3>Flush</h3>
* <p>
* The batch is automatically flushed when it hits the batch size and also when we
* execute queries or when we mix UpdateSql and CallableSql with save and delete of
* beans.
* </p>
* <p>
* We use {@link #flush()} to explicitly flush the batch and we can use
* {@link #setFlushOnQuery(boolean)} and {@link #setFlushOnMixed(boolean)}
* to control the automatic flushing behaviour.
* </p>
* <p>
* Example: batch processing of CallableSql executing every 10 rows
* </p>
*
* <pre>{@code
*
* String data = "This is a simple test of the batch processing"
* + " mode and the transaction execute batch method";
*
* String[] da = data.split(" ");
*
* String sql = "{call sp_t3(?,?)}";
*
* CallableSql cs = new CallableSql(sql);
* cs.registerOut(2, Types.INTEGER);
*
* // (optional) inform Ebean this stored procedure
* // inserts into a table called sp_test
* cs.addModification("sp_test", true, false, false);
*
* try (Transaction txn = DB.beginTransaction()) {
* txn.setBatchMode(true);
* txn.setBatchSize(10);
*
* for (int i = 0; i < da.length;) {
* cs.setParameter(1, da[i]);
* DB.execute(cs);
* }
*
* // Note: commit implicitly flushes
* txn.commit();
* }
*
* }</pre>
*/
void setBatchMode(boolean useBatch);
/**
* Return the batch mode at the transaction level.
*/
boolean isBatchMode();
/**
* Set the JDBC batch mode to use for a save() or delete() when cascading to children.
* <p>
* This only takes effect when batch mode on the transaction has not already meant that
* JDBC batch mode is being used.
* </p>
* <p>
* This is useful when the single save() or delete() cascades. For example, inserting a 'master' cascades
* and inserts a collection of 'detail' beans. The detail beans can be inserted using JDBC batch.
* </p>
* <p>
* This is effectively already turned on for all platforms apart from older Sql Server.
* </p>
*
* @param batchMode the batch mode to use per save(), insert(), update() or delete()
* @see io.ebean.config.DatabaseConfig#setPersistBatchOnCascade(PersistBatch)
*/
void setBatchOnCascade(boolean batchMode);
/**
* Return the batch mode at the request level.
*/
boolean isBatchOnCascade();
/**
* Specify the number of statements before a batch is flushed automatically.
*/
void setBatchSize(int batchSize);
/**
* Return the current batch size.
*/
int getBatchSize();
/**
* Specify if you want batched inserts to use getGeneratedKeys.
* <p>
* By default batched inserts will try to use getGeneratedKeys if it is
* supported by the underlying jdbc driver and database.
* </p>
* <p>
* You may want to turn getGeneratedKeys off when you are inserting a large
* number of objects and you don't care about getting back the ids.
* </p>
*/
void setGetGeneratedKeys(boolean getGeneratedKeys);
/**
* Deprecated renamed to setGetGeneratedKeys().
*/
@Deprecated
default void setBatchGetGeneratedKeys(boolean getGeneratedKeys) {
setGetGeneratedKeys(getGeneratedKeys);
}
/**
* By default when mixing UpdateSql (or CallableSql) with Beans the batch is
* automatically flushed when you change (between persisting beans and
* executing UpdateSql or CallableSql).
* <p>
* If you want to execute both WITHOUT having the batch automatically flush
* you need to call this with batchFlushOnMixed = false.
* </p>
* <p>
* Note that UpdateSql and CallableSql are ALWAYS executed first (before the
* beans are executed). This is because the UpdateSql and CallableSql have
* already been bound to their PreparedStatements. The beans on the other hand
* have a 2 step process (delayed binding).
* </p>
*/
void setFlushOnMixed(boolean batchFlushOnMixed);
/**
* Deprecated renamed to setFlushOnMixed().
*/
@Deprecated
default void setBatchFlushOnMixed(boolean batchFlushOnMixed) {
setFlushOnMixed(batchFlushOnMixed);
}
/**
* By default executing a query will automatically flush any batched
* statements (persisted beans, executed UpdateSql etc).
* <p>
* Calling this method with batchFlushOnQuery = false means that you can
* execute a query and the batch will not be automatically flushed.
* </p>
*/
void setFlushOnQuery(boolean batchFlushOnQuery);
/**
* Deprecated renamed to setFlushOnQuery().
*/
@Deprecated
default void setBatchFlushOnQuery(boolean batchFlushOnQuery) {
setFlushOnQuery(batchFlushOnQuery);
}
/**
* Return true if the batch (of persisted beans or executed UpdateSql etc)
* should be flushed prior to executing a query.
* <p>
* The default is for this to be true.
* </p>
*/
boolean isFlushOnQuery();
/**
* Deprecated renamed to isFlushOnQuery().
*/
@Deprecated
default boolean isBatchFlushOnQuery() {
return isFlushOnQuery();
}
/**
* The batch will be flushing automatically but you can use this to explicitly
* flush the batch if you like.
* <p>
* Flushing occurs automatically when:
* </p>
* <ul>
* <li>the batch size is reached</li>
* <li>A query is executed on the same transaction</li>
* <li>UpdateSql or CallableSql are mixed with bean save and delete</li>
* <li>Transaction commit occurs</li>
* <li>A getter method is called on a batched bean</li>
* </ul>
*/
void flush() throws PersistenceException;
/**
* This is a synonym for flush() and will be deprecated.
* <p>
* flush() is preferred as it matches the JPA flush() method.
* </p>
*/
void flushBatch() throws PersistenceException;
/**
* Return the underlying Connection object.
* <p>
* Useful where a Developer wishes to use the JDBC API directly. Note that the
* commit() rollback() and end() methods on the Transaction should still be
* used. Calling these methods on the Connection would be a big no no unless
* you know what you are doing.
* </p>
* <p>
* Examples of when a developer may wish to use the connection directly are:
* Savepoints, advanced CLOB BLOB use and advanced stored procedure calls.
* </p>
*/
Connection getConnection();
/**
* Add table modification information to the TransactionEvent.
* <p>
* Use this in conjunction with getConnection() and raw JDBC.
* </p>
* <p>
* This effectively informs Ebean of the data that has been changed by the
* transaction and this information is normally automatically handled by Ebean
* when you save entity beans or use UpdateSql etc.
* </p>
* <p>
* If you use raw JDBC then you can use this method to inform Ebean for the
* tables that have been modified. Ebean uses this information to keep its
* caches in synch and maintain text indexes.
* </p>
*/
void addModification(String tableName, boolean inserts, boolean updates, boolean deletes);
/**
* Add an arbitrary user object to the transaction. The objects added have no
* impact on any internals of ebean and are solely meant as a convenient
* method push user information (although somewhat replaced by TransactionCallback).
*/
void putUserObject(String name, Object value);
/**
* Get an object added with {@link #putUserObject(String, Object)}.
*/
Object getUserObject(String name);
}
@@ -0,0 +1,34 @@
package io.ebean;
/**
* Provides a callback that can be registered with a Transaction.
* <p/>
* The callback methods are called just prior to and after the transaction performs a commit or rollback.
* <p/>
* A typical use of TransactionCallback would be to clean up non-transactional resources like files. For example,
* when processing files on postCommit/postRollback clean up the associated files. As another example when
* on postCommit of a delete remove associated resources from the file system or remote service.
*/
public interface TransactionCallback {
/**
* Perform processing just prior to the transaction commit.
*/
void preCommit();
/**
* Perform processing just after the transaction commit.
*/
void postCommit();
/**
* Perform processing just prior to the transaction rollback.
*/
void preRollback();
/**
* Perform processing just after the transaction rollback.
*/
void postRollback();
}
@@ -0,0 +1,42 @@
package io.ebean;
/**
* Adapter that can be extended for easier implementation of TransactionCallback.
* <p/>
* Provides 'no operation' implementation for each of the TransactionCallback methods. It is expected that this
* class is extended and override the methods you need to.
*/
public abstract class TransactionCallbackAdapter implements TransactionCallback {
/**
* Perform processing just prior to the transaction commit.
*/
@Override
public void preCommit() {
// do nothing - override as necessary
}
/**
* Perform processing just after the transaction commit.
*/
@Override
public void postCommit() {
// do nothing - override as necessary
}
/**
* Perform processing just prior to the transaction rollback.
*/
@Override
public void preRollback() {
// do nothing - override as necessary
}
/**
* Perform processing just after the transaction rollback.
*/
@Override
public void postRollback() {
// do nothing - override as necessary
}
}
@@ -0,0 +1,442 @@
package io.ebean;
import io.ebean.annotation.PersistBatch;
import io.ebean.annotation.TxIsolation;
import io.ebean.annotation.TxType;
import java.util.ArrayList;
import java.util.concurrent.Callable;
/**
* Holds the definition of how a transactional method should run.
* <p>
* This information matches the features of the Transactional annotation. You
* can use it directly with Runnable or Callable via
* {@link DB#execute(TxScope, Runnable)} or
* {@link DB#executeCall(TxScope, Callable)}.
* </p>
* <p>
* This object is used internally with the enhancement of a method with
* Transactional annotation.
* </p>
*
* @see DB#execute(TxScope, Runnable)
* @see DB#executeCall(TxScope, Callable)
*/
public final class TxScope {
private int profileId;
private TxType type;
private String serverName;
private TxIsolation isolation;
private PersistBatch batch;
private PersistBatch batchOnCascade;
private int batchSize;
private boolean skipGeneratedKeys;
private boolean readOnly;
/**
* Set this to false if the JDBC batch should not be automatically be flushed when a query is executed.
*/
private boolean flushOnQuery = true;
private boolean skipCache;
private String label;
private ArrayList<Class<? extends Throwable>> rollbackFor;
private ArrayList<Class<? extends Throwable>> noRollbackFor;
private ProfileLocation profileLocation;
/**
* Helper method to create a TxScope with REQUIRES.
*/
public static TxScope required() {
return new TxScope(TxType.REQUIRED);
}
/**
* Helper method to create a TxScope with REQUIRES_NEW.
*/
public static TxScope requiresNew() {
return new TxScope(TxType.REQUIRES_NEW);
}
/**
* Helper method to create a TxScope with MANDATORY.
*/
public static TxScope mandatory() {
return new TxScope(TxType.MANDATORY);
}
/**
* Helper method to create a TxScope with SUPPORTS.
*/
public static TxScope supports() {
return new TxScope(TxType.SUPPORTS);
}
/**
* Helper method to create a TxScope with NOT_SUPPORTED.
*/
public static TxScope notSupported() {
return new TxScope(TxType.NOT_SUPPORTED);
}
/**
* Helper method to create a TxScope with NEVER.
*/
public static TxScope never() {
return new TxScope(TxType.NEVER);
}
/**
* Create a REQUIRED transaction scope.
*/
public TxScope() {
this.type = TxType.REQUIRED;
}
/**
* Create with a given transaction scope type.
*/
public TxScope(TxType type) {
this.type = type;
}
/**
* Describes this TxScope instance.
*/
@Override
public String toString() {
return "TxScope[" + type + "] readOnly[" + readOnly + "] isolation[" + isolation
+ "] serverName[" + serverName + "] rollbackFor[" + rollbackFor + "] noRollbackFor[" + noRollbackFor + "]";
}
/**
* Return true if PersistBatch has been set.
*/
public boolean isBatchSet() {
return batch != null && batch != PersistBatch.INHERIT;
}
/**
* Return true if batch on cascade has been set.
*/
public boolean isBatchOnCascadeSet() {
return batchOnCascade != null && batchOnCascade != PersistBatch.INHERIT;
}
/**
* Return true if batch size has been set.
*/
public boolean isBatchSizeSet() {
return batchSize > 0;
}
/**
* Check for batchSize being set without batch mode and use this to imply PersistBatch.ALL.
*/
public void checkBatchMode() {
if (batchSize > 0 && notSet(batch) && notSet(batchOnCascade)) {
// Use setting the batchSize as implying PersistBatch.ALL for @Transactional
batch = PersistBatch.ALL;
}
}
/**
* Return true if the mode is considered not set.
*/
private boolean notSet(PersistBatch batchMode) {
return batchMode == null || batchMode == PersistBatch.INHERIT;
}
/**
* Return the transaction type.
*/
public TxType getType() {
return type;
}
/**
* Set the transaction type.
*/
public TxScope setType(TxType type) {
this.type = type;
return this;
}
/**
* Return the transaction profile id.
*/
public int getProfileId() {
return profileId;
}
/**
* Set the transaction profile id.
*/
public TxScope setProfileId(int profileId) {
this.profileId = profileId;
return this;
}
/**
* Return the profile location.
*/
public ProfileLocation getProfileLocation() {
return profileLocation;
}
/**
* Set the profile location.
*/
public TxScope setProfileLocation(ProfileLocation profileLocation) {
this.profileLocation = profileLocation;
return this;
}
/**
* Return true if the L2 cache should be skipped for this transaction.
*/
public boolean isSkipCache() {
return skipCache;
}
/**
* Set to true if the transaction should skip L2 cache access.
*/
public TxScope setSkipCache(boolean skipCache) {
this.skipCache = skipCache;
return this;
}
/**
* Return the label for the transaction.
*/
public String getLabel() {
return label;
}
/**
* Set a label for the transaction.
*/
public TxScope setLabel(String label) {
this.label = label;
return this;
}
/**
* Return the batch mode.
*/
public PersistBatch getBatch() {
return batch;
}
/**
* Set the batch mode to use.
*/
public TxScope setBatch(PersistBatch batch) {
this.batch = batch;
return this;
}
/**
* Return the batch on cascade mode.
*/
public PersistBatch getBatchOnCascade() {
return batchOnCascade;
}
/**
* Set the batch on cascade mode.
*/
public TxScope setBatchOnCascade(PersistBatch batchOnCascade) {
this.batchOnCascade = batchOnCascade;
return this;
}
/**
* Return the batch size. 0 means use the default value.
*/
public int getBatchSize() {
return batchSize;
}
/**
* Set the batch size to use.
*/
public TxScope setBatchSize(int batchSize) {
this.batchSize = batchSize;
return this;
}
/**
* Set if the transaction should skip reading generated keys for inserts.
*/
public TxScope setSkipGeneratedKeys() {
this.skipGeneratedKeys = true;
return this;
}
/**
* Return true if getGeneratedKeys should be skipped for this transaction.
*/
public boolean isSkipGeneratedKeys() {
return skipGeneratedKeys;
}
/**
* Return if the transaction should be treated as read only.
*/
public boolean isReadonly() {
return readOnly;
}
/**
* Set if the transaction should be treated as read only.
*/
public TxScope setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
return this;
}
/**
* Return false if the JDBC batch buffer should not be flushed automatically when a query is executed.
*/
public boolean isFlushOnQuery() {
return flushOnQuery;
}
/**
* Set flushOnQuery to be false to stop automatically flushing the JDBC batch buffer when a query is executed.
*/
public TxScope setFlushOnQuery(boolean flushOnQuery) {
this.flushOnQuery = flushOnQuery;
return this;
}
/**
* Return the isolation level.
*/
public int getIsolationLevel() {
return isolation != null ? isolation.getLevel() : -1;
}
/**
* Return the Isolation level this transaction should run with.
*/
public TxIsolation getIsolation() {
return isolation;
}
/**
* Set the transaction isolation level this transaction should run with.
*/
public TxScope setIsolation(TxIsolation isolation) {
this.isolation = isolation;
return this;
}
/**
* Return the serverName for this transaction. If this is null then the
* default server (default DataSource) will be used.
*/
public String getServerName() {
return serverName;
}
/**
* Set the serverName (DataSource name) for which this transaction will be. If
* the serverName is not specified (left null) then the default server will be
* used.
*/
public TxScope setServerName(String serverName) {
this.serverName = serverName;
return this;
}
/**
* Return the throwable's that should cause a rollback.
*/
public ArrayList<Class<? extends Throwable>> getRollbackFor() {
return rollbackFor;
}
/**
* Set a Throwable that should explicitly cause a rollback.
*/
public TxScope setRollbackFor(Class<? extends Throwable> rollbackThrowable) {
if (rollbackFor == null) {
rollbackFor = new ArrayList<>(2);
}
rollbackFor.add(rollbackThrowable);
return this;
}
/**
* Set multiple throwable's that will cause a rollback.
*/
@SuppressWarnings("unchecked")
public TxScope setRollbackFor(Class<?>[] rollbackThrowables) {
if (rollbackFor == null) {
rollbackFor = new ArrayList<>(rollbackThrowables.length);
}
for (Class<?> rollbackThrowable : rollbackThrowables) {
rollbackFor.add((Class<? extends Throwable>) rollbackThrowable);
}
return this;
}
/**
* Return the throwable's that should NOT cause a rollback.
*/
public ArrayList<Class<? extends Throwable>> getNoRollbackFor() {
return noRollbackFor;
}
/**
* Add a Throwable to a list that will NOT cause a rollback. You are able to
* call this method multiple times with different throwable's and they will
* added to a list.
*/
public TxScope setNoRollbackFor(Class<? extends Throwable> noRollback) {
if (noRollbackFor == null) {
noRollbackFor = new ArrayList<>(2);
}
this.noRollbackFor.add(noRollback);
return this;
}
/**
* Set multiple throwable's that will NOT cause a rollback.
*/
@SuppressWarnings("unchecked")
public TxScope setNoRollbackFor(Class<?>[] noRollbacks) {
if (noRollbackFor == null) {
noRollbackFor = new ArrayList<>(noRollbacks.length);
}
for (Class<?> noRollback : noRollbacks) {
noRollbackFor.add((Class<? extends Throwable>) noRollback);
}
return this;
}
public boolean isBatchMode() {
return PersistBatch.ALL.equals(batch);
}
public boolean isBatchOnCascade() {
return PersistBatch.ALL.equals(batchOnCascade);
}
}
@@ -0,0 +1,166 @@
package io.ebean;
/**
* An Insert Update or Delete statement.
* <p>
* Generally a named update will be defined on the entity bean. This will take
* the form of either an actual sql insert update delete statement or a similar
* statement with bean name and property names in place of database table and
* column names. The statement will likely include named parameters.
* </p>
* <p>
* The following is an example of named updates on an entity bean.
* </p>
* <pre>{@code
* ...
* NamedUpdates(value = {
* NamedUpdate(
* name = "setTitle",
* notifyCache = false,
* update = "update topic set title = :title, postCount = :count where id = :id"),
* NamedUpdate(
* name = "setPostCount",
* notifyCache = false,
* update = "update f_topic set post_count = :postCount where id = :id"),
* NamedUpdate(
* name = "incrementPostCount",
* notifyCache = false,
* update = "update Topic set postCount = postCount + 1 where id = :id")
* //update = "update f_topic set post_count = post_count + 1 where id = :id")
* })
* Entity
* Table(name = "f_topic")
* public class Topic {
* ...
* }
* }</pre>
*
* <p>
* The following show code that would use a named update on the Topic entity
* bean.
* </p>
* <p>
* <pre>{@code
*
* Update<Topic> update = DB.createUpdate(Topic.class, "incrementPostCount");
* update.setParameter("id", 1);
* int rows = update.execute();
*
* }</pre>
*
* @param <T> the type of entity beans inserted updated or deleted
*/
public interface Update<T> {
/**
* Return the name if it is a named update.
*/
String getName();
/**
* Set this to false if you do not want the cache to invalidate related
* objects.
* <p>
* If you don't set this Ebean will automatically invalidate the appropriate
* parts of the "L2" server cache.
* </p>
*/
Update<T> setNotifyCache(boolean notifyCache);
/**
* Set a timeout for statement execution.
* <p>
* This will typically result in a call to setQueryTimeout() on a
* preparedStatement. If the timeout occurs an exception will be thrown - this
* will be a SQLException wrapped up in a PersistenceException.
* </p>
*
* @param secs the timeout in seconds. Zero implies unlimited.
*/
Update<T> setTimeout(int secs);
/**
* Execute the statement returning the number of rows modified.
*/
int execute();
/**
* Set an ordered bind parameter.
* <p>
* position starts at value 1 (not 0) to be consistent with PreparedStatement.
* </p>
* <p>
* Set a value for each ? you have in the sql.
* </p>
*
* @param position the index position of the parameter starting with 1.
* @param value the parameter value to bind.
*/
Update<T> set(int position, Object value);
/**
* Set and ordered bind parameter (same as bind).
*
* @param position the index position of the parameter starting with 1.
* @param value the parameter value to bind.
*/
Update<T> setParameter(int position, Object value);
/**
* Set an ordered parameter that is null. The JDBC type of the null must be
* specified.
* <p>
* position starts at value 1 (not 0) to be consistent with PreparedStatement.
* </p>
*/
Update<T> setNull(int position, int jdbcType);
/**
* Set an ordered parameter that is null (same as bind).
*/
Update<T> setNullParameter(int position, int jdbcType);
/**
* Set a named parameter. Named parameters have a colon to prefix the name.
* <p>
* A more succinct version of setParameter() to be consistent with Query.
* </p>
*
* @param name the parameter name.
* @param value the parameter value.
*/
Update<T> set(String name, Object value);
/**
* Bind a named parameter (same as bind).
*/
Update<T> setParameter(String name, Object param);
/**
* Set a named parameter that is null. The JDBC type of the null must be
* specified.
* <p>
* A more succinct version of setNullParameter().
* </p>
*
* @param name the parameter name.
* @param jdbcType the type of the property being bound.
*/
Update<T> setNull(String name, int jdbcType);
/**
* Bind a named parameter that is null (same as bind).
*/
Update<T> setNullParameter(String name, int jdbcType);
/**
* Set a label meaning performance metrics will be collected for the execution of this update.
*/
Update<T> setLabel(String label);
/**
* Return the sql that is actually executed.
*/
String getGeneratedSql();
}
@@ -0,0 +1,166 @@
package io.ebean;
/**
* An update query typically intended to perform a bulk update of many rows that match the query.
* <p>
* Also note that you can also just use a raw SQL update via {@link SqlUpdate} which is pretty light and simple.
* This UpdateQuery is more for the cases where we want to build the where expression of the update using the
* {@link ExpressionList} "Criteria API" that is used with a normal ORM query.
* </p>
* <p>
* <h4>Example: Simple update</h4>
* <p>
* <pre>{@code
*
* int rows = DB.update(Customer.class)
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
* <pre>{@code sql
*
* update o_customer set status=?, updtime=? where id > ?
*
* }</pre>
* <p>
* Note that if the where() clause contains a join then the SQL update changes to use a
* <code> WHERE ID IN () </code> form.
* </p>
* <p>
* <h4>Example: Update with a JOIN</h4>
* <p>
* In this example the expression <code>.eq("billingAddress.country", nz)</code> requires a join
* to the address table.
* </p>
* <p>
* <pre>{@code
*
* int rows = DB.update(Customer.class)
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .eq("status", Customer.Status.NEW)
* .eq("billingAddress.country", nz)
* .gt("id", 1000)
* .update();
* }</pre>
* <p>
* <pre>{@code sql
*
* update o_customer set status=?, updtime=?
* where id in (
* select t0.id c0
* from o_customer t0
* left join o_address t1 on t1.id = t0.billing_address_id
* where t0.status = ?
* and t1.country_code = ?
* and t0.id > ? )
*
* }</pre>
*
* @param <T> The type of entity bean being updated
* @see SqlUpdate
*/
public interface UpdateQuery<T> {
/**
* Set the value of a property.
* <p>
* <pre>{@code
*
* int rows = DB.update(Customer.class)
* .set("status", Customer.Status.ACTIVE)
* .set("updtime", new Timestamp(System.currentTimeMillis()))
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* @param property The bean property to be set
* @param value The value to set the property to
*/
UpdateQuery<T> set(String property, Object value);
/**
* Set the property to be null.
* <p>
* <pre>{@code
*
* int rows = DB.update(Customer.class)
* .setNull("notes")
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* @param property The property to be set to null.
*/
UpdateQuery<T> setNull(String property);
/**
* Set using a property expression that does not need any bind values.
* <p>
* The property expression typically contains database functions.
* </p>
* <p>
* <pre>{@code
*
* int rows = DB.update(Customer.class)
* .setRaw("status = coalesce(status, 'A')")
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* @param propertyExpression A property expression
*/
UpdateQuery<T> setRaw(String propertyExpression);
/**
* Set using a property expression that can contain <code>?</code> bind value placeholders.
* <p>
* For each <code>?</code> in the property expression there should be a matching bind value supplied.
* </p>
* <pre>{@code
*
* int rows = DB.update(Customer.class)
* .setRaw("status = coalesce(status, ?)", Customer.Status.ACTIVE)
* .where()
* .gt("id", 1000)
* .update();
*
* }</pre>
*
* @param propertyExpression A raw property expression
* @param values The values to bind with the property expression
*/
UpdateQuery<T> setRaw(String propertyExpression, Object... values);
/**
* Set the profile location of this update query. This is used to relate query execution metrics
* back to a location like a specific line of code.
*/
UpdateQuery<T> setProfileLocation(ProfileLocation profileLocation);
/**
* Set the label on the update query.
*/
UpdateQuery<T> setLabel(String label);
/**
* Return the query expression list to add predicates to.
*/
ExpressionList<T> where();
/**
* Execute the update returning the number of rows updated.
*/
int update();
}
@@ -0,0 +1,58 @@
package io.ebean;
/**
* Holds two values as the result of a difference comparison.
*/
public class ValuePair {
protected Object newValue;
protected Object oldValue;
/**
* Default constructor for JSON tools.
*/
public ValuePair() {
}
/**
* Construct with the pair of new and old values.
*/
public ValuePair(Object newValue, Object oldValue) {
this.newValue = newValue;
this.oldValue = oldValue;
}
/**
* Return the new value.
*/
public Object getNewValue() {
return newValue;
}
/**
* Return the old value.
*/
public Object getOldValue() {
return oldValue;
}
/**
* Set the new value.
*/
public void setNewValue(Object newValue) {
this.newValue = newValue;
}
/**
* Set the old value.
*/
public void setOldValue(Object oldValue) {
this.oldValue = oldValue;
}
@Override
public String toString() {
return newValue + "," + oldValue;
}
}
@@ -0,0 +1,101 @@
package io.ebean;
import java.sql.Timestamp;
import java.util.Map;
/**
* Wraps a version of a @History bean.
*/
public class Version<T> {
/**
* The version of the bean.
*/
protected T bean;
/**
* The effective start date time of this version.
*/
protected Timestamp start;
/**
* The effective end date time of this version.
*/
protected Timestamp end;
/**
* The map of changed properties.
*/
protected Map<String, ValuePair> diff;
/**
* Construct with bean and an effective date time range.
*/
public Version(T bean, Timestamp start, Timestamp end) {
this.bean = bean;
this.start = start;
this.end = end;
}
/**
* Default constructor - useful for JSON tools such as Jackson.
*/
public Version() {
}
/**
* Return the bean instance for this version.
*/
public T getBean() {
return bean;
}
/**
* Set the bean instance for this version.
*/
public void setBean(T bean) {
this.bean = bean;
}
/**
* Return the effective start date time of this version.
*/
public Timestamp getStart() {
return start;
}
/**
* Set the effective start date time of this version.
*/
public void setStart(Timestamp start) {
this.start = start;
}
/**
* Return the effective end date time of this version.
*/
public Timestamp getEnd() {
return end;
}
/**
* Set the effective end date time of this version.
*/
public void setEnd(Timestamp end) {
this.end = end;
}
/**
* Set the map of differences from this bean to the prior version.
*/
public void setDiff(Map<String, ValuePair> diff) {
this.diff = diff;
}
/**
* Return the map of differences from this bean to the prior version.
*/
public Map<String, ValuePair> getDiff() {
return diff;
}
}
@@ -0,0 +1,76 @@
package io.ebean;
import io.ebean.service.SpiFetchGroupQuery;
import io.ebean.service.SpiFetchGroupService;
import io.ebean.service.SpiProfileLocationFactory;
import io.ebean.service.SpiRawSqlService;
import java.util.Iterator;
import java.util.ServiceLoader;
/**
* Lookup internal services.
*/
class XServiceProvider {
private static SpiRawSqlService rawSqlService = initRawSql();
private static SpiProfileLocationFactory profileLocationFactory = initProfileLocation();
private static SpiFetchGroupService fetchGroupService = initSpiFetchGroupService();
private static SpiFetchGroupService initSpiFetchGroupService() {
return loadFirstService(SpiFetchGroupService.class);
}
private static SpiRawSqlService initRawSql() {
return loadFirstService(SpiRawSqlService.class);
}
private static SpiProfileLocationFactory initProfileLocation() {
return loadFirstService(SpiProfileLocationFactory.class);
}
private static <T> T loadFirstService(Class<T> cls) {
Iterator<T> loader = ServiceLoader.load(cls).iterator();
if (loader.hasNext()) {
return loader.next();
}
throw new IllegalStateException("No service implementation found for " + cls);
}
/**
* Return the RawSqlService implementation.
*/
static SpiRawSqlService rawSql() {
return rawSqlService;
}
/**
* Return the RawSqlService implementation.
*/
static SpiProfileLocationFactory profileLocationFactory() {
return profileLocationFactory;
}
/**
* Return the FetchGroup with the given select clause.
*/
static <T> FetchGroup<T> fetchGroupOf(Class<T> cls, String select) {
return fetchGroupService.of(cls, select);
}
/**
* Return the FetchGroupBuilder with the given select clause.
*/
static <T> FetchGroupBuilder<T> fetchGroupOf(Class<T> cls) {
return fetchGroupService.of(cls);
}
/**
* Return the FetchGroup Query for building fetch groups via query beans.
*/
static <T> SpiFetchGroupQuery<T> fetchGroupQueryFor(Class<T> cls) {
return fetchGroupService.queryFor(cls);
}
}
@@ -0,0 +1,249 @@
package io.ebean.bean;
import io.ebean.ExpressionList;
import java.io.Serializable;
import java.util.Collection;
import java.util.Set;
/**
* Lazy loading capable Maps, Lists and Sets.
* <p>
* This also includes the ability to listen for additions and removals to or
* from the Map Set or List. The purpose of gathering the additions and removals
* is to support persisting ManyToMany objects. The additions and removals
* become inserts and deletes from the intersection table.
* </p>
* <p>
* Technically this is <em>NOT</em> an extension of
* <em>java.util.Collection</em>. The reason being that java.util.Map is not a
* Collection. I realise this makes this name confusing so I apologise for that.
* </p>
*/
public interface BeanCollection<E> extends Serializable {
enum ModifyListenMode {
/**
* The common mode
*/
NONE,
/**
* Mode used for PrivateOwned
*/
REMOVALS,
/**
* Mode used for ManyToMany relationships
*/
ALL
}
/**
* Set the disableLazyLoad state.
*/
void setDisableLazyLoad(boolean disableLazyLoad);
/**
* Load bean from another collection.
*/
void loadFrom(BeanCollection<?> other);
/**
* Add a bean to the list/set with modifyListen notification.
*/
void addBean(E bean);
/**
* Remove a bean to the list/set with modifyListen notification.
*/
void removeBean(E bean);
/**
* Reset the collection back to an empty state ready for reloading.
* <p>
* This is done as part of bean refresh.
*/
void reset(EntityBean ownerBean, String propertyName);
/**
* Return true if the collection is uninitialised or is empty without any held modifications.
* <p>
* Returning true means can safely skip cascade save for this bean collection.
* </p>
*/
boolean isSkipSave();
/**
* Return true if the collection holds modifications.
*/
boolean holdsModifications();
/**
* Return the bean that owns this collection.
*/
EntityBean getOwnerBean();
/**
* Return the bean property name this collection represents.
*/
String getPropertyName();
/**
* Check after the lazy load that the underlying collection is not null
* (handle case where join to many not outer).
* <p>
* That is, if the collection was not loaded due to filterMany predicates etc
* then make sure the collection is set to empty.
* </p>
*/
boolean checkEmptyLazyLoad();
/**
* Return the filter (if any) that was used in building this collection.
* <p>
* This is so that the filter can be applied on refresh.
* </p>
*/
ExpressionList<?> getFilterMany();
/**
* Set the filter that was used in building this collection.
*/
void setFilterMany(ExpressionList<?> filterMany);
/**
* Return true if the collection has been registered with the batch loading context.
*/
boolean isRegisteredWithLoadContext();
/**
* Set the loader that will be used to lazy/query load this collection.
* <p>
* This is effectively the batch loading context this collection is registered with.
* </p>
*/
void setLoader(BeanCollectionLoader beanLoader);
/**
* Set to true if you want the BeanCollection to be treated as read only. This
* means no elements can be added or removed etc.
*/
void setReadOnly(boolean readOnly);
/**
* Return true if the collection should be treated as readOnly and no elements
* can be added or removed etc.
*/
boolean isReadOnly();
/**
* Add the bean to the collection.
* <p>
* This is disallowed for BeanMap.
* </p>
*/
void internalAdd(Object bean);
/**
* Add the bean with a check to see if it is already contained.
*/
void internalAddWithCheck(Object bean);
/**
* Return the number of elements in the List Set or Map.
*/
int size();
/**
* Return true if the List Set or Map is empty.
*/
boolean isEmpty();
/**
* Returns the underlying collection of beans from the Set, Map or List.
*/
Collection<E> getActualDetails();
/**
* Returns the underlying entries so for Maps this is a collection of
* Map.Entry.
* <p>
* For maps this returns the entrySet as we need the keys of the map.
* </p>
*/
Collection<?> getActualEntries();
/**
* return true if there are real rows held. Return false is this is using
* Deferred fetch to lazy load the rows and the rows have not yet been
* fetched.
*/
boolean isPopulated();
/**
* Return true if this is a reference (lazy loading) bean collection. This is
* the same as !isPopulated();
*/
boolean isReference();
/**
* Set modify listening on or off. This is used to keep track of objects that
* have been added to or removed from the list set or map.
* <p>
* This is required only for ManyToMany collections. The additions and
* deletions are used to insert or delete entries from the intersection table.
* Otherwise modifyListening is false.
* </p>
*/
void setModifyListening(ModifyListenMode modifyListenMode);
/**
* Return the current modify listening mode. Can be null for on newly created beans.
*/
ModifyListenMode getModifyListening();
/**
* Add an object to the additions list.
* <p>
* This will potentially end up as an insert into a intersection table for a
* ManyToMany.
* </p>
*/
void modifyAddition(E bean);
/**
* Add an object to the deletions list.
* <p>
* This will potentially end up as an delete from an intersection table for a
* ManyToMany.
* </p>
*/
void modifyRemoval(Object bean);
/**
* Return the list of objects added to the list set or map. These will used to
* insert rows into the intersection table of a ManyToMany.
*/
Set<E> getModifyAdditions();
/**
* Return the list of objects removed from the list set or map. These will
* used to delete rows from the intersection table of a ManyToMany.
*/
Set<E> getModifyRemovals();
/**
* Reset the set of additions and deletions. This is called after the
* additions and removals have been processed.
*/
void modifyReset();
/**
* Has been modified by an addition or removal.
*/
boolean wasTouched();
/**
* Return a shallow copy of this collection that is modifiable.
*/
BeanCollection<E> getShallowCopy();
}
@@ -0,0 +1,16 @@
package io.ebean.bean;
/**
* Interface to define the addition of a bean to the underlying collection.
* <p>
* For maps this takes into account the map key. For List and Set this simply
* adds the bean to the underlying list or set.
* </p>
*/
public interface BeanCollectionAdd {
/**
* Add a loaded bean to the collection.
*/
void addEntityBean(EntityBean bean);
}
@@ -0,0 +1,21 @@
package io.ebean.bean;
/**
* Loads a entity bean collection.
* <p>
* Typically invokes lazy loading for a single or batch of collections.
* </p>
*/
public interface BeanCollectionLoader {
/**
* Return the name of the associated Database.
*/
String getName();
/**
* Invoke the lazy loading for this bean collection.
*/
void loadMany(BeanCollection<?> collection, boolean onlyIds);
}
@@ -0,0 +1,22 @@
package io.ebean.bean;
/**
* Visitor for collecting new/old values for a bean update.
*/
public interface BeanDiffVisitor {
/**
* Collect a new/old value pair.
*/
void visit(int position, Object newVal, Object oldVal);
/**
* Start processing an associated bean.
*/
void visitPush(int position);
/**
* Stop processing an associated bean.
*/
void visitPop();
}
@@ -0,0 +1,21 @@
package io.ebean.bean;
/**
* Loads a entity bean.
* <p>
* Typically invokes lazy loading for a single or batch of entity beans.
* </p>
*/
public interface BeanLoader {
/**
* Return the name of the associated Database.
*/
String getName();
/**
* Invoke the lazy loading for this bean.
*/
void loadBean(EntityBeanIntercept ebi);
}
@@ -0,0 +1,22 @@
package io.ebean.bean;
/**
* A call origin for query execution profiling to collect graph use (for query tuning).
*/
public interface CallOrigin {
/**
* Return the top element. Typically the top stack element with class and line.
*/
String getTopElement();
/**
* Return the full description of the call origin.
*/
String getFullDescription();
/**
* Compute and return an origin key based on the query hash.
*/
String getOriginKey(int queryHash);
}
@@ -0,0 +1,98 @@
package io.ebean.bean;
import java.io.Serializable;
import java.util.Arrays;
import static io.ebean.util.EncodeB64.enc;
/**
* Represent the call stack (stack trace elements).
* <p>
* Used with a query to identify a CallStackQuery for AutoTune automatic query
* tuning.
* </p>
* <p>
* This is used so that a single query called from different methods can be
* tuned for each different call stack.
* </p>
* <p>
* Note the call stack is trimmed to remove the common ebean internal elements.
* </p>
*/
public final class CallStack implements Serializable, CallOrigin {
private static final long serialVersionUID = -8590644046907438579L;
private static final String NEWLINE = "\n";
private final String zeroHash;
private final String pathHash;
private final StackTraceElement[] callStack;
private final int hc;
public CallStack(StackTraceElement[] callStack, int zeroHash, int pathHash) {
this.callStack = callStack;
this.zeroHash = enc(zeroHash);
this.pathHash = enc(pathHash);
this.hc = computeHashCode();
}
private int computeHashCode() {
int hc = 0;
for (StackTraceElement element : callStack) {
hc = 92821 * hc + element.hashCode();
}
return hc;
}
@Override
public String toString() {
return zeroHash + ":" + pathHash + ":" + callStack[0];
}
@Override
public int hashCode() {
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof CallStack)) {
return false;
}
CallStack e = (CallStack) obj;
return Arrays.equals(callStack, e.callStack);
}
/**
* Return the first element of the call stack.
*/
@Override
public String getTopElement() {
return callStack[0].toString();
}
/**
* Return the call stack lines appended with the given newLine string.
*/
@Override
public String getFullDescription() {
StringBuilder sb = new StringBuilder(400);
for (int i = 0; i < callStack.length; i++) {
if (i > 0) {
sb.append(NEWLINE);
}
sb.append(callStack[i].toString());
}
return sb.toString();
}
@Override
public String getOriginKey(int queryHash) {
return enc(queryHash) + "." + zeroHash + "." + pathHash;
}
}
@@ -0,0 +1,8 @@
package io.ebean.bean;
/**
* Marker interface for classes enhanced to support Transactional methods.
*/
public interface EnhancedTransactional {
}
@@ -0,0 +1,123 @@
package io.ebean.bean;
import java.io.Serializable;
/**
* Bean that is aware of EntityBeanIntercept.
* <p>
* This interface and implementation of these methods is added to Entity Beans
* via instrumentation. These methods have a funny _ebean_ prefix to avoid any
* clash with normal methods these beans would have. These methods are not for
* general application consumption.
* </p>
*/
public interface EntityBean extends Serializable {
/**
* Return all the property names in defined order.
*/
default String[] _ebean_getPropertyNames() {
throw new NotEnhancedException();
}
/**
* Return the property name at the given position.
*/
default String _ebean_getPropertyName(int pos) {
throw new NotEnhancedException();
}
/**
* Return the enhancement marker value.
* <p>
* This is the class name of the enhanced class and used to check that all
* entity classes are enhanced (specifically not just a super class).
* </p>
*/
default String _ebean_getMarker() {
throw new NotEnhancedException();
}
/**
* Create and return a new entity bean instance.
*/
default Object _ebean_newInstance() {
throw new NotEnhancedException();
}
/**
* Generated method that sets the loaded state on all the embedded beans on
* this entity bean by using EntityBeanIntercept.setEmbeddedLoaded(Object o);
*/
default void _ebean_setEmbeddedLoaded() {
throw new NotEnhancedException();
}
/**
* Return true if any embedded beans are new or dirty.
*/
default boolean _ebean_isEmbeddedNewOrDirty() {
throw new NotEnhancedException();
}
/**
* Return the intercept for this object.
*/
default EntityBeanIntercept _ebean_getIntercept() {
throw new NotEnhancedException();
}
/**
* Similar to _ebean_getIntercept() except it checks to see if the intercept
* field is null and will create it if required.
* <p>
* This is really only required when transientInternalFields=true as an
* enhancement option. In this case the intercept field is transient and will
* be null after a bean has been deserialised.
* </p>
* <p>
* This transientInternalFields=true option was to support some serialization
* frameworks that can't take into account our ebean fields.
* </p>
*/
default EntityBeanIntercept _ebean_intercept() {
throw new NotEnhancedException();
}
/**
* Set the value of a field of an entity bean of this type.
* <p>
* Note that using this method bypasses any interception that otherwise occurs
* on entity beans. That means lazy loading and oldValues creation.
* </p>
*/
default void _ebean_setField(int fieldIndex, Object value) {
throw new NotEnhancedException();
}
/**
* Set the field value with interception.
*/
default void _ebean_setFieldIntercept(int fieldIndex, Object value) {
throw new NotEnhancedException();
}
/**
* Return the value of a field from an entity bean of this type.
* <p>
* Note that using this method bypasses any interception that otherwise occurs
* on entity beans. That means lazy loading.
* </p>
*/
default Object _ebean_getField(int fieldIndex) {
throw new NotEnhancedException();
}
/**
* Return the field value with interception.
*/
default Object _ebean_getFieldIntercept(int fieldIndex) {
throw new NotEnhancedException();
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,127 @@
package io.ebean.bean;
import java.lang.ref.WeakReference;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* Collects profile information for a bean (or reference/proxy bean) at a given node.
* <p>
* The node identifies the location of the bean in the object graph.
* </p>
* <p>
* It has to use a weak reference so as to ensure that it does not stop the
* associated bean from being garbage collected.
* </p>
*/
public final class NodeUsageCollector {
/**
* The point in the object graph for a specific query and call stack point.
*/
private final ObjectGraphNode node;
/**
* Weak to allow garbage collection.
*/
private final WeakReference<NodeUsageListener> managerRef;
/**
* The properties used at this profile point.
*/
private final Set<String> used = new LinkedHashSet<>();
/**
* set to true if the bean is modified (setter called)
*/
private boolean modified;
/**
* The property that cause a reference to lazy load.
*/
private String loadProperty;
public NodeUsageCollector(ObjectGraphNode node, WeakReference<NodeUsageListener> managerRef) {
this.node = node;
// weak to allow garbage collection.
this.managerRef = managerRef;
}
/**
* The bean has been modified by a setter method.
*/
public void setModified() {
modified = true;
}
/**
* Add the name of a property that has been used.
*/
public void addUsed(String property) {
used.add(property);
}
/**
* The property that invoked a lazy load.
*/
public void setLoadProperty(String loadProperty) {
this.loadProperty = loadProperty;
}
/**
* Publish the usage info to the manager.
*/
private void publishUsageInfo() {
NodeUsageListener manager = managerRef.get();
if (manager != null) {
manager.collectNodeUsage(this);
}
}
/**
* publish the collected usage information when garbage collection occurs.
*/
@Override
protected void finalize() throws Throwable {
publishUsageInfo();
super.finalize();
}
/**
* Return the associated node which identifies the location in the object
* graph of the bean/reference.
*/
public ObjectGraphNode getNode() {
return node;
}
/**
* Return true if no properties where used.
*/
public boolean isEmpty() {
return used.isEmpty();
}
/**
* Return the set of used properties.
*/
public Set<String> getUsed() {
return used;
}
/**
* Return true if the bean was modified by a setter.
*/
public boolean isModified() {
return modified;
}
public String getLoadProperty() {
return loadProperty;
}
@Override
public String toString() {
return node + " read:" + used + " modified:" + modified;
}
}
@@ -0,0 +1,16 @@
package io.ebean.bean;
/**
* Collects the profile information.
*/
public interface NodeUsageListener {
/**
* Collect node usage "profiling" information.
* <p>
* This is the properties that are used for a given bean in the object graph.
* This information is used by autoTune to tune queries.
* </p>
*/
void collectNodeUsage(NodeUsageCollector collector);
}
@@ -0,0 +1,12 @@
package io.ebean.bean;
/**
* This exception is thrown, when the default implementation of EntityBean is not or not properly overwritten by enhancer.
*
* @author Roland Praml, FOCONIS AG
*
*/
public class NotEnhancedException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
@@ -0,0 +1,92 @@
package io.ebean.bean;
import java.io.Serializable;
import java.util.Objects;
/**
* Identifies a unique node of an object graph.
* <p>
* It represents a location relative to the root of an object graph and specific
* to a query and call stack hash.
* </p>
*/
public final class ObjectGraphNode implements Serializable {
private static final long serialVersionUID = 2087081778650228996L;
/**
* Identifies the origin.
*/
private final ObjectGraphOrigin originQueryPoint;
/**
* The path relative to the root.
*/
private final String path;
/**
* Create at a sub level.
*/
public ObjectGraphNode(ObjectGraphNode parent, String path) {
this.originQueryPoint = parent.getOriginQueryPoint();
this.path = parent.getChildPath(path);
}
/**
* Create an the root level.
*/
public ObjectGraphNode(ObjectGraphOrigin originQueryPoint, String path) {
this.originQueryPoint = originQueryPoint;
this.path = path;
}
/**
* Return the origin query point.
*/
public ObjectGraphOrigin getOriginQueryPoint() {
return originQueryPoint;
}
private String getChildPath(String childPath) {
if (path == null) {
return childPath;
} else if (childPath == null) {
return path;
} else {
return path + "." + childPath;
}
}
/**
* Return the path relative to the root.
*/
public String getPath() {
return path;
}
@Override
public String toString() {
return "origin:" + originQueryPoint + " path[" + path + "]";
}
@Override
public int hashCode() {
int hc = 92821 * originQueryPoint.hashCode();
hc = 92821 * hc + (path == null ? 0 : path.hashCode());
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ObjectGraphNode)) {
return false;
}
ObjectGraphNode e = (ObjectGraphNode) obj;
return (Objects.equals(e.path, path))
&& e.originQueryPoint.equals(originQueryPoint);
}
}
@@ -0,0 +1,87 @@
package io.ebean.bean;
import java.io.Serializable;
/**
* Represents a "origin" of an ORM object graph. This combines the call stack
* and query plan hash.
* <p>
* The call stack is included so that the query can have different tuned fetches
* for each unique call stack. For example, a query to fetch a customer could be
* called by three different methods and each can be treated as a separate
* origin point (and autoTune can tune each one separately).
* </p>
*/
public final class ObjectGraphOrigin implements Serializable {
private static final long serialVersionUID = 410937765287968708L;
private final CallOrigin callOrigin;
private final String beanType;
private final int queryHash;
private final String key;
public ObjectGraphOrigin(int queryHash, CallOrigin callOrigin, String beanType) {
this.callOrigin = callOrigin;
this.beanType = beanType;
this.queryHash = queryHash;
this.key = callOrigin.getOriginKey(queryHash);
}
/**
* The key includes the queryPlan hash and the callStack hash. This becomes
* the unique identifier for a query point.
*/
public String getKey() {
return key;
}
/**
* The type of bean the query is fetching.
*/
public String getBeanType() {
return beanType;
}
/**
* The call stack involved.
*/
public CallOrigin getCallOrigin() {
return callOrigin;
}
public String getTopElement() {
return callOrigin.getTopElement();
}
@Override
public String toString() {
return "key[" + key + "] type[" + beanType + "] " + callOrigin.getTopElement();
}
@Override
public int hashCode() {
int hc = 92821 * callOrigin.hashCode();
hc = 92821 * hc + beanType.hashCode();
hc = 92821 * hc + queryHash;
return hc;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ObjectGraphOrigin)) {
return false;
}
ObjectGraphOrigin e = (ObjectGraphOrigin) obj;
return e.queryHash == queryHash
&& e.beanType.equals(beanType)
&& e.callOrigin.equals(callOrigin);
}
}
@@ -0,0 +1,126 @@
package io.ebean.bean;
/**
* Holds entity beans by there type and id.
* <p>
* This is used to ensure only one instance for a given entity type and id is
* used to build object graphs from queries and lazy loading.
* </p>
*/
public interface PersistenceContext {
/**
* Put the entity bean into the PersistenceContext.
*/
void put(Class<?> rootType, Object id, Object bean);
/**
* Put the entity bean into the PersistenceContext if one is not already
* present (for this id).
* <p>
* Returns an existing entity bean (if one is already there) and otherwise
* returns null.
* </p>
*/
Object putIfAbsent(Class<?> rootType, Object id, Object bean);
/**
* Return an object given its type and unique id.
*/
Object get(Class<?> rootType, Object uid);
/**
* Get the bean from the persistence context also checked to see if it had
* been previously deleted (if so then you also can't hit the L2 cache to
* fetch the bean for this particular persistence context).
*/
WithOption getWithOption(Class<?> rootType, Object uid);
/**
* Clear all the references.
*/
void clear();
/**
* Clear all the references for a given type of entity bean.
*/
void clear(Class<?> rootType);
/**
* Clear the reference to a specific entity bean.
*/
void clear(Class<?> rootType, Object uid);
/**
* Clear the reference as a result of an entity being deleted.
*/
void deleted(Class<?> rootType, Object id);
/**
* Return the number of beans of the given type in the persistence context.
*/
int size(Class<?> rootType);
/**
* Return a copy of the Persistence context to use for large query iteration.
*/
PersistenceContext forIterate();
/**
* Return a new Persistence context during iteration of large query result.
*/
PersistenceContext forIterateReset();
/**
* Return true if the persistence context has grown and hit the 'reset limit'
* during large query iteration.
*/
boolean resetLimit();
/**
* Wrapper on a bean to also indicate if a bean has been deleted.
* <p>
* If a bean has been deleted then for the same persistence context is should
* not be able to be fetched from persistence context or L2 cache.
* </p>
*/
class WithOption {
/**
* The bean was previously deleted from this persistence context (can't hit
* L2 cache).
*/
public static final WithOption DELETED = new WithOption();
private final boolean deleted;
private final Object bean;
private WithOption() {
this.deleted = true;
this.bean = null;
}
/**
* The bean exists in the persistence context (and not been previously deleted).
*/
public WithOption(Object bean) {
this.deleted = false;
this.bean = bean;
}
/**
* Return true if the bean was deleted. This means you can't hit the L2
* cache.
*/
public boolean isDeleted() {
return deleted;
}
/**
* Return the bean (from the persistence context).
*/
public Object getBean() {
return bean;
}
}
}
@@ -0,0 +1,15 @@
package io.ebean.bean;
/**
* A callback that can be registered to fire on getter method calls.
* It's primary purpose is to automatically flush JDBC batch buffer.
*/
public interface PreGetterCallback {
/**
* Trigger the callback based on a getter on a property.
*
* @param propertyIndex The index of the property
*/
void preGetterTrigger(int propertyIndex);
}
@@ -0,0 +1,48 @@
package io.ebean.bean;
import io.ebean.Database;
/**
* BeanLoader used when single beans are loaded (which is usually not ideal / N+1).
*/
public abstract class SingleBeanLoader implements BeanLoader {
protected final Database database;
SingleBeanLoader(Database database) {
this.database = database;
}
@Override
public String getName() {
return database.getName();
}
/**
* Single bean lazy loaded when bean from L2 cache.
*/
public static class L2 extends SingleBeanLoader {
public L2(Database database) {
super(database);
}
@Override
public void loadBean(EntityBeanIntercept ebi) {
database.getPluginApi().loadBeanL2(ebi);
}
}
/**
* Single bean lazy loaded when a reference bean.
*/
public static class Ref extends SingleBeanLoader {
public Ref(Database database) {
super(database);
}
@Override
public void loadBean(EntityBeanIntercept ebi) {
database.getPluginApi().loadBeanRef(ebi);
}
}
}
@@ -0,0 +1,11 @@
<HTML>
<HEAD>
<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
<TITLE>Enhanced beans API and Support objects</TITLE>
</HEAD>
<Body BGCOLOR="#ffffff">
Enhanced beans API and Support objects
</Body>
</HTML>
@@ -0,0 +1,54 @@
package io.ebean.cache;
import java.util.Set;
/**
* For query cache entries we additionally hold the dependent tables and timestamp for the query result.
* <p>
* We use the dependent tables and timestamp to validate that tables the query joins to have not been
* modified since the query cache entry was cached. If any dependent tables have since been modified
* the query cache entry is treated as invalid.
* </p>
*/
public class QueryCacheEntry {
private final Object value;
private final Set<String> dependentTables;
private final long timestamp;
/**
* Create with dependent tables and timestamp.
*
* @param value The query result being cached
* @param dependentTables The extra tables the query is dependent on (joins to)
* @param timestamp The timestamp that the query uses to check for modifications
*/
public QueryCacheEntry(Object value, Set<String> dependentTables, long timestamp) {
this.value = value;
this.dependentTables = dependentTables;
this.timestamp = timestamp;
}
/**
* Return the actual query result.
*/
public Object getValue() {
return value;
}
/**
* Return the tables the query result is dependent on.
*/
public Set<String> getDependentTables() {
return dependentTables;
}
/**
* Return the timestamp used to check for modifications on the dependent tables.
*/
public long getTimestamp() {
return timestamp;
}
}
@@ -0,0 +1,12 @@
package io.ebean.cache;
/**
* Used to validate that a query cache entry is still valid based on dependent tables.
*/
public interface QueryCacheEntryValidate {
/**
* Return true if the entry is still valid based on dependent tables.
*/
boolean isValid(QueryCacheEntry queryCacheEntry);
}
+91
View File
@@ -0,0 +1,91 @@
package io.ebean.cache;
import io.ebean.meta.MetricVisitor;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* Represents part of the "L2" server side cache.
* <p>
* This is used to cache beans or query results (bean collections).
* </p>
* <p>
* There are 2 ServerCache's for each bean type. One is used as the 'bean cache'
* which holds beans of a given type. The other is the 'query cache' holding
* query results for a given type.
* </p>
*/
public interface ServerCache {
default Map<Object, Object> getAll(Set<Object> keys) {
Map<Object, Object> map = new LinkedHashMap<>();
for (Object key : keys) {
Object value = get(key);
if (value != null) {
map.put(key, value);
}
}
return map;
}
/**
* Return the value given the key.
*/
Object get(Object id);
/**
* Put all the values in the cache.
*/
default void putAll(Map<Object, Object> keyValues) {
keyValues.forEach(this::put);
}
/**
* Put the value in the cache with a given id.
*/
void put(Object id, Object value);
/**
* Remove the entries from the cache given the id values.
*/
default void removeAll(Set<Object> keys) {
keys.forEach(this::remove);
}
/**
* Remove a entry from the cache given its id.
*/
void remove(Object id);
/**
* Clear all entries from the cache.
*/
void clear();
/**
* Return the number of entries in the cache.
*/
int size();
/**
* Return the hit ratio the cache is currently getting.
*/
int getHitRatio();
/**
* Return statistics for the cache.
*
* @param reset if true the statistics are reset.
*/
ServerCacheStatistics getStatistics(boolean reset);
/**
* Visit the metrics for the cache.
*/
default void visit(MetricVisitor visitor) {
// do nothing by default
}
}
@@ -0,0 +1,74 @@
package io.ebean.cache;
import io.ebean.config.CurrentTenantProvider;
/**
* Configuration used to create ServerCache instances.
*/
public class ServerCacheConfig {
private final ServerCacheType type;
private final String cacheKey;
private final String shortName;
private final ServerCacheOptions cacheOptions;
private final CurrentTenantProvider tenantProvider;
private final QueryCacheEntryValidate queryCacheEntryValidate;
public ServerCacheConfig(ServerCacheType type, String cacheKey, String shortName, ServerCacheOptions cacheOptions, CurrentTenantProvider tenantProvider, QueryCacheEntryValidate queryCacheEntryValidate) {
this.type = type;
this.cacheKey = cacheKey;
this.shortName = shortName;
this.cacheOptions = cacheOptions;
this.tenantProvider = tenantProvider;
this.queryCacheEntryValidate = queryCacheEntryValidate;
}
/**
* Return the cache type.
*/
public ServerCacheType getType() {
return type;
}
/**
* Return the name of the cache.
*/
public String getCacheKey() {
return cacheKey;
}
/**
* Return the short name for the cache.
*/
public String getShortName() {
return shortName;
}
/**
* Return the tuning options.
*/
public ServerCacheOptions getCacheOptions() {
return cacheOptions;
}
/**
* Return the current tenant provider.
*/
public CurrentTenantProvider getTenantProvider() {
return tenantProvider;
}
/**
* Return the service that provides validation for query cache entries.
*/
public QueryCacheEntryValidate getQueryCacheEntryValidate() {
return queryCacheEntryValidate;
}
/**
* Return true if the cache is a query cache.
*/
public boolean isQueryCache() {
return type == ServerCacheType.QUERY;
}
}
@@ -0,0 +1,31 @@
package io.ebean.cache;
import io.ebean.meta.MetricVisitor;
/**
* Defines method for constructing caches for beans and queries.
*/
public interface ServerCacheFactory {
/**
* Visit the metrics for the cache.
*/
default void visit(MetricVisitor visitor) {
// do nothing by default
}
/**
* Create the cache for the given type with options.
*/
ServerCache createCache(ServerCacheConfig config);
/**
* Return a ServerCacheNotify that we will send ServerCacheNotification events to.
* <p>
* This is used if a ServerCacheNotifyPlugin is not supplied.
* </p>
*
* @param listener The listener that should be used to process the notification events.
*/
ServerCacheNotify createCacheNotify(ServerCacheNotify listener);
}
@@ -0,0 +1,96 @@
package io.ebean.cache;
import io.ebean.meta.MetricVisitor;
import java.util.List;
/**
* The cache service for server side caching of beans and query results.
*/
public interface ServerCacheManager {
/**
* Visit the metrics for all the server caches.
*/
void visitMetrics(MetricVisitor visitor);
/**
* Return true if the L2 caching is local.
* <p>
* Local L2 caching means that the cache updates should occur in foreground
* rather than background processing.
* </p>
*/
boolean isLocalL2Caching();
/**
* Return all the cache regions.
*/
List<ServerCacheRegion> allRegions();
/**
* Set the regions that are enabled.
* <p>
* Typically this is set on startup and at runtime (via dynamic configuration).
* </p>
*
* @param regions A region name or comma delimited list of region names.
*/
void setEnabledRegions(String regions);
/**
* Enable or disable all the cache regions.
*/
void setAllRegionsEnabled(boolean enabled);
/**
* Return the cache region by name. Typically to enable or disable the region.
*/
ServerCacheRegion getRegion(String name);
/**
* Return the cache for mapping natural keys to id values.
*/
ServerCache getNaturalKeyCache(Class<?> beanType);
/**
* Return the cache for beans of a particular type.
*/
ServerCache getBeanCache(Class<?> beanType);
/**
* Return the cache for associated many properties of a bean type.
*/
ServerCache getCollectionIdsCache(Class<?> beanType, String propertyName);
/**
* Return the cache for query results of a particular type of bean.
*/
ServerCache getQueryCache(Class<?> beanType);
/**
* This clears both the bean and query cache for a given type.
*/
void clear(Class<?> beanType);
/**
* Clear all the caches.
*/
void clearAll();
/**
* Clear all the local caches.
* <p>
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
*/
void clearAllLocal();
/**
* Clear the local caches for this bean type.
* <p>
* This is used when the L2 Cache is based on clustered near-caches (Like Ebean-K8s-L2Cache).
* It is not used when the L2 cache is a distributed cache such as HazelCast or Ignite etc.
*/
void clearLocal(Class<?> beanType);
}
@@ -0,0 +1,27 @@
package io.ebean.cache;
import java.util.Set;
/**
* Notification event that dependent tables have been modified.
* <p>
* This is sent to other interested servers (in the cluster).
* </p>
*/
public class ServerCacheNotification {
private final Set<String> dependentTables;
public ServerCacheNotification(Set<String> dependentTables) {
this.dependentTables = dependentTables;
}
@Override
public String toString() {
return "tables:" + dependentTables;
}
public Set<String> getDependentTables() {
return dependentTables;
}
}
@@ -0,0 +1,12 @@
package io.ebean.cache;
/**
* Interface for both listening to notification changes and sending them to other members of the cluster.
*/
public interface ServerCacheNotify {
/**
* Notify other server cache members of the table modifications or process the notifications.
*/
void notify(ServerCacheNotification notification);
}
@@ -0,0 +1,17 @@
package io.ebean.cache;
import io.ebean.config.DatabaseConfig;
/**
* Plugin that provides a ServerCacheNotify implementation.
* <p>
* Is supplied this will be used to send the ServerCacheNotification event to other cluster members.
* </p>
*/
public interface ServerCacheNotifyPlugin {
/**
* Create a ServerCacheNotify implementation given the server configuration.
*/
ServerCacheNotify create(DatabaseConfig config);
}

Some files were not shown because too many files have changed in this diff Show More