+ * After this amount of profiling has been obtained profiling is collected at
+ * the Profiling Percentage rate.
+ *
+ */
+ public void setProfilingBase(int profilingBase);
+
+ /**
+ * Return the minimum number of queries profiled before autoFetch will start
+ * automatically tuning the queries.
+ *
+ * This could be one which means start autoFetch tuning after the first
+ * profiling information is collected.
+ *
+ */
+ public int getProfilingMin();
+
+ /**
+ * Set the minimum number of queries profiled per query point before autoFetch
+ * will automatically tune the queries.
+ *
+ * Increasing this number will mean more profiling is collected before
+ * autoFetch starts tuning the query.
+ *
+ */
+ public void setProfilingMin(int autoFetchMinThreshold);
+
+ /**
+ * Fire a garbage collection (hint to the JVM). Assuming garbage collection
+ * fires this will gather the usage profiling information.
+ */
+ public String collectUsageViaGC();
+
+ /**
+ * This will take the current profiling information and update the "tuned
+ * query detail".
+ *
+ * This is done periodically and can also be manually invoked.
+ *
+ *
+ * @return a summary of the updates that occurred
+ */
+ public String updateTunedQueryInfo();
+
+ /**
+ * Clear all the tuned query info.
+ *
+ * Should only need do this for testing and playing around.
+ *
+ *
+ * @return the amount of tuned query information cleared.
+ */
+ public int clearTunedQueryInfo();
+
+ /**
+ * Clear all the profiling information.
+ *
+ * This means the profiling information will need to be re-gathered.
+ *
+ *
+ * Should only need do this for testing and playing around.
+ *
+ *
+ * @return the amount of profiled information cleared.
+ */
+ public int clearProfilingInfo();
+
+ /**
+ * Clear the query execution statistics.
+ */
+ public void clearQueryStatistics();
+
+ /**
+ * Return the number of queries tuned by AutoFetch.
+ */
+ public int getTotalTunedQueryCount();
+
+ /**
+ * Return the size of the TuneQuery map.
+ */
+ public int getTotalTunedQuerySize();
+
+ /**
+ * Return the size of the profile map.
+ */
+ public int getTotalProfileSize();
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/avaje/ebean/BackgroundExecutor.java b/src/main/java/com/avaje/ebean/BackgroundExecutor.java
new file mode 100644
index 000000000..f3b86b79b
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/BackgroundExecutor.java
@@ -0,0 +1,40 @@
+package com.avaje.ebean;
+
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Background thread pool service for executing of tasks asynchronously.
+ *
+ * This service is used internally by Ebean for executing background tasks such
+ * as the {@link Query#findFutureList()} and also for executing background tasks
+ * periodically.
+ *
+ *
+ * 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).
+ *
+ *
+ * @author rbygrave
+ */
+public interface BackgroundExecutor {
+
+ /**
+ * Execute a task in the background.
+ */
+ public void execute(Runnable r);
+
+ /**
+ * Execute a task periodically with a fixed delay between each execution.
+ *
+ * For example, execute a runnable every minute.
+ *
+ *
+ * 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)}
+ *
+ */
+ public void executePeriodically(Runnable r, long delay, TimeUnit unit);
+}
diff --git a/src/main/java/com/avaje/ebean/BeanState.java b/src/main/java/com/avaje/ebean/BeanState.java
new file mode 100644
index 000000000..fc4e3c371
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/BeanState.java
@@ -0,0 +1,94 @@
+package com.avaje.ebean;
+
+import java.beans.PropertyChangeListener;
+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.
+ *
+ * If so the this bean only holds the Id property and will invoke lazy loading
+ * if any other property is get or set.
+ *
+ */
+ public boolean isReference();
+
+ /**
+ * Return true if the bean is new (and not yet saved).
+ */
+ public boolean isNew();
+
+ /**
+ * Return true if the bean is new or dirty (and probably needs to be saved).
+ */
+ public boolean isNewOrDirty();
+
+ /**
+ * Return true if the bean has been changed but not yet saved.
+ */
+ public boolean isDirty();
+
+ /**
+ * For partially populated beans returns the properties that are loaded on the
+ * bean.
+ *
+ * Accessing another property will cause lazy loading to occur.
+ *
+ */
+ public Set getLoadedProps();
+
+ /**
+ * Return the set of changed properties.
+ */
+ public Set getChangedProps();
+
+ /**
+ * Return true if the bean is readOnly.
+ *
+ * If a setter is called on a readOnly bean it will throw an exception.
+ *
+ */
+ public boolean isReadOnly();
+
+ /**
+ * Set the readOnly status for the bean.
+ */
+ public void setReadOnly(boolean readOnly);
+
+ /**
+ * Add a propertyChangeListener.
+ */
+ public void addPropertyChangeListener(PropertyChangeListener listener);
+
+ /**
+ * Remove a propertyChangeListener.
+ */
+ public void removePropertyChangeListener(PropertyChangeListener listener);
+
+ /**
+ * Advanced - Used to programmatically build a reference object.
+ *
+ * You can create a new EntityBean (
+ * {@link EbeanServer#createEntityBean(Class)}, set its Id property and then
+ * call this setReference() method.
+ *
+ */
+ public void setReference();
+
+ /**
+ * Advanced - Used to programmatically build a partially or fully loaded
+ * entity bean. First create an entity bean via
+ * {@link EbeanServer#createEntityBean(Class)}, then populate its properties
+ * and then call this method specifying which properties where loaded or null
+ * for a fully loaded entity bean.
+ *
+ * @param loadedProperties
+ * the properties that where loaded or null for a fully loaded entity
+ * bean.
+ */
+ public void setLoaded(Set loadedProperties);
+}
\ No newline at end of file
diff --git a/src/main/java/com/avaje/ebean/CallableSql.java b/src/main/java/com/avaje/ebean/CallableSql.java
new file mode 100644
index 000000000..d6e13f635
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/CallableSql.java
@@ -0,0 +1,194 @@
+package com.avaje.ebean;
+
+import java.sql.CallableStatement;
+import java.sql.SQLException;
+
+/**
+ * For making calls to stored procedures. Refer to the Ebean execute() method.
+ *
+ * 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.
+ *
+ * Example 2:
+ * 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.
+ *
+ *
+ * @see com.avaje.ebean.SqlUpdate
+ * @see com.avaje.ebean.Ebean#execute(CallableSql)
+ */
+public interface CallableSql {
+
+ /**
+ * Return the label that is put into the transaction log.
+ */
+ public String getLabel();
+
+ /**
+ * Set the label that is put in the transaction log.
+ */
+ public CallableSql setLabel(String label);
+
+ /**
+ * Return the statement execution timeout.
+ */
+ public int getTimeout();
+
+ /**
+ * Return the callable sql.
+ */
+ public String getSql();
+
+ /**
+ * Set the statement execution timeout. Zero implies unlimited time.
+ *
+ * This is set to the underlying CallableStatement.
+ *
+ */
+ public CallableSql setTimeout(int secs);
+
+ /**
+ * Set the callable sql.
+ */
+ public CallableSql setSql(String sql);
+
+ /**
+ * Bind a parameter that is bound as a IN parameter.
+ *
+ * position starts at value 1 (not 0) to be consistent with CallableStatement.
+ *
+ *
+ * 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.
+ *
+ *
+ * @param position
+ * the index position of the parameter.
+ * @param value
+ * the value of the parameter.
+ */
+ public 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.
+ */
+ public CallableSql setParameter(int position, Object value);
+
+ /**
+ * Register an OUT parameter.
+ *
+ * Note that position starts at value 1 (not 0) to be consistent with
+ * CallableStatement.
+ *
+ *
+ * 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.
+ *
+ *
+ * @param position
+ * the index position of the parameter (starts with 1).
+ * @param type
+ * the jdbc type of the OUT parameter that will be read.
+ */
+ public CallableSql registerOut(int position, int type);
+
+ /**
+ * Return an OUT parameter value.
+ *
+ * position starts at value 1 (not 0) to be consistent with CallableStatement.
+ *
+ *
+ * This can only be called after the CallableSql has been executed. When run
+ * in batch mode you effectively can't use this method.
+ *
+ */
+ public 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.
+ */
+ public boolean executeOverride(CallableStatement cstmt) throws SQLException;
+
+ /**
+ * Add table modification information to the TransactionEvent.
+ *
+ * This would be similar to using the
+ * Ebean.externalModification() method. It may be easier and make
+ * more sense to set it here with the CallableSql.
+ *
+ *
+ * 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.
+ *
+ */
+ public CallableSql addModification(String tableName, boolean inserts, boolean updates,
+ boolean deletes);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/avaje/ebean/DRawSqlColumnsParser.java b/src/main/java/com/avaje/ebean/DRawSqlColumnsParser.java
new file mode 100644
index 000000000..058990467
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/DRawSqlColumnsParser.java
@@ -0,0 +1,93 @@
+package com.avaje.ebean;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.persistence.PersistenceException;
+
+import com.avaje.ebean.RawSql.ColumnMapping;
+
+/**
+ * Parses columnMapping (select clause) mapping columns to bean properties.
+ */
+final class DRawSqlColumnsParser {
+
+ private final int end;
+
+ private final String sqlSelect;
+
+ private int pos;
+
+ private int indexPos;
+
+ public static ColumnMapping parse(String sqlSelect) {
+ return new DRawSqlColumnsParser(sqlSelect).parse();
+ }
+
+ private DRawSqlColumnsParser(String sqlSelect) {
+ this.sqlSelect = sqlSelect;
+ this.end = sqlSelect.length();
+ }
+
+ private ColumnMapping parse() {
+
+ ArrayList columns = new ArrayList();
+ while (pos <= end) {
+ ColumnMapping.Column c = nextColumnInfo();
+ columns.add(c);
+ }
+
+ return new ColumnMapping(columns);
+ }
+
+ private ColumnMapping.Column nextColumnInfo() {
+ int start = pos;
+ nextComma();
+ String colInfo = sqlSelect.substring(start, pos++);
+ colInfo = colInfo.trim();
+
+ String[] split = colInfo.split(" ");
+ if (split.length > 1) {
+ ArrayList tmp = new ArrayList(split.length);
+ for (int i = 0; i < split.length; i++) {
+ if (split[i].trim().length() > 0) {
+ tmp.add(split[i].trim());
+ }
+ }
+ split = tmp.toArray(new String[tmp.size()]);
+ }
+
+ if (split.length == 1) {
+ // default to column the same name as the property
+ return new ColumnMapping.Column(indexPos++, split[0], null);
+ }
+ if (split.length == 2) {
+ return new ColumnMapping.Column(indexPos++, split[0], split[1]);
+ }
+ if (split.length == 3) {
+ if (!split[1].equalsIgnoreCase("as")) {
+ String msg = "Expecting AS keyword parsing column " + colInfo;
+ throw new PersistenceException(msg);
+ }
+ return new ColumnMapping.Column(indexPos++, split[0], split[2]);
+ }
+
+ String msg = "Expecting Max 3 words parsing column " + colInfo + ". Got "
+ + Arrays.toString(split);
+ throw new PersistenceException(msg);
+ }
+
+ private int nextComma() {
+ boolean inQuote = false;
+ while (pos < end) {
+ char c = sqlSelect.charAt(pos);
+ if (c == '\'') {
+ inQuote = !inQuote;
+ } else if (!inQuote && c == ',') {
+ return pos;
+ }
+ pos++;
+ }
+ return pos;
+ }
+}
diff --git a/src/main/java/com/avaje/ebean/DRawSqlParser.java b/src/main/java/com/avaje/ebean/DRawSqlParser.java
new file mode 100644
index 000000000..eaa196146
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/DRawSqlParser.java
@@ -0,0 +1,298 @@
+package com.avaje.ebean;
+
+import com.avaje.ebean.RawSql.Sql;
+
+/**
+ * Parses sql-select queries to try and determine the location where WHERE and
+ * HAVING clauses can be added dynamically to the sql.
+ */
+class DRawSqlParser {
+
+ public static final String $_AND_HAVING = "${andHaving}";
+
+ public static final String $_HAVING = "${having}";
+
+ public static final String $_AND_WHERE = "${andWhere}";
+
+ public static final String $_WHERE = "${where}";
+
+ private static final String ORDER_BY = "order by";
+
+ private final SimpleTextParser textParser;
+
+ private String sql;
+
+ private int placeHolderWhere;
+ private int placeHolderAndWhere;
+ private int placeHolderHaving;
+ private int placeHolderAndHaving;
+ private boolean hasPlaceHolders;
+
+ private int selectPos = -1;
+ private int distinctPos = -1;
+ private int fromPos = -1;
+ private int wherePos = -1;
+ private int groupByPos = -1;
+ private int havingPos = -1;
+ private int orderByPos = -1;
+
+ private boolean whereExprAnd;
+ private int whereExprPos = -1;
+ private boolean havingExprAnd;
+ private int havingExprPos = -1;
+
+ public static Sql parse(String sql) {
+ return new DRawSqlParser(sql).parse();
+ }
+
+ private DRawSqlParser(String sqlString) {
+ sqlString = sqlString.trim();
+ this.sql = sqlString;
+ this.hasPlaceHolders = findAndRemovePlaceHolders();
+ this.textParser = new SimpleTextParser(sqlString);
+ }
+
+ private Sql parse() {
+
+ if (!hasPlaceHolders()) {
+ // parse the sql for the keywords...
+ // select, from, where, having, group by, order by
+ parseSqlFindKeywords(true);
+ }
+
+ whereExprPos = findWhereExprPosition();
+ havingExprPos = findHavingExprPosition();
+
+ String preFrom = removeWhitespace(findPreFromSql());
+ String preWhere = removeWhitespace(findPreWhereSql());
+ String preHaving = removeWhitespace(findPreHavingSql());
+ String orderBySql = findOrderBySql();
+
+ preFrom = trimSelectKeyword(preFrom);
+
+ return new Sql(sql.hashCode(), preFrom, preWhere, whereExprAnd, preHaving, havingExprAnd,
+ orderBySql, (distinctPos > -1));
+ }
+
+ /**
+ * Find and remove the known place holders such as ${where}.
+ */
+ private boolean findAndRemovePlaceHolders() {
+ placeHolderWhere = removePlaceHolder($_WHERE);
+ placeHolderAndWhere = removePlaceHolder($_AND_WHERE);
+ placeHolderHaving = removePlaceHolder($_HAVING);
+ placeHolderAndHaving = removePlaceHolder($_AND_HAVING);
+ return hasPlaceHolders();
+ }
+
+ private int removePlaceHolder(String placeHolder) {
+ int pos = sql.indexOf(placeHolder);
+ if (pos > -1) {
+ int after = pos + placeHolder.length() + 1;
+ if (after > sql.length()) {
+ sql = sql.substring(0, pos);
+ } else {
+ sql = sql.substring(0, pos) + sql.substring(after);
+ }
+ }
+ return pos;
+ }
+
+ private boolean hasPlaceHolders() {
+ if (placeHolderWhere > -1) {
+ return true;
+ }
+ if (placeHolderAndWhere > -1) {
+ return true;
+ }
+ if (placeHolderHaving > -1) {
+ return true;
+ }
+ if (placeHolderAndHaving > -1) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Trim off the select keyword (to support row_number() limit function).
+ */
+ private String trimSelectKeyword(String preWhereExprSql) {
+
+ if (selectPos < 0) {
+ throw new IllegalStateException("select keyword not found?");
+ }
+ // trim of select keyword
+ preWhereExprSql = preWhereExprSql.trim();
+ String select = preWhereExprSql.substring(0, 7);
+ if (!select.equalsIgnoreCase("select ")) {
+ throw new RuntimeException("Expecting [" + preWhereExprSql + "] to start with \"select\"");
+ }
+ preWhereExprSql = preWhereExprSql.substring(7).trim();
+ if (distinctPos > -1) {
+ // trim of distinct keyword
+ String distinct = preWhereExprSql.substring(0, 9);
+ if (!distinct.equalsIgnoreCase("distinct ")) {
+ throw new RuntimeException("Expecting [" + preWhereExprSql
+ + "] to start with \"select distinct\"");
+ }
+ preWhereExprSql = preWhereExprSql.substring(9);
+ }
+
+ return preWhereExprSql;
+ }
+
+ private String findOrderBySql() {
+ if (orderByPos > -1) {
+ int pos = orderByPos + ORDER_BY.length();
+ return sql.substring(pos).trim();
+ }
+ return null;
+ }
+
+ private String findPreHavingSql() {
+ if (havingExprPos > whereExprPos) {
+ // an order by clause follows...
+ return sql.substring(whereExprPos, havingExprPos - 1);
+ }
+ if (whereExprPos > -1) {
+ if (orderByPos == -1) {
+ return sql.substring(whereExprPos);
+
+ } else if (whereExprPos == orderByPos) {
+ return "";
+
+ } else {
+ return sql.substring(whereExprPos, orderByPos - 1);
+ }
+ }
+ return null;
+ }
+
+ private String findPreFromSql() {
+ return sql.substring(0, fromPos - 1);
+ }
+
+ private String findPreWhereSql() {
+ if (whereExprPos > -1) {
+ return sql.substring(fromPos, whereExprPos - 1);
+ } else {
+ return sql.substring(fromPos);
+ }
+ }
+
+ private void parseSqlFindKeywords(boolean allKeywords) {
+
+ selectPos = textParser.findWordLower("select");
+ if (selectPos == -1) {
+ String msg = "Error parsing sql, can not find SELECT keyword in:";
+ throw new RuntimeException(msg + sql);
+ }
+
+ String possibleDistinct = textParser.nextWord();
+ if ("distinct".equals(possibleDistinct)) {
+ distinctPos = textParser.getPos() - 8;
+ }
+
+ fromPos = textParser.findWordLower("from");
+ if (fromPos == -1) {
+ String msg = "Error parsing sql, can not find FROM keyword in:";
+ throw new RuntimeException(msg + sql);
+ }
+
+ if (!allKeywords) {
+ return;
+ }
+
+ wherePos = textParser.findWordLower("where");
+ if (wherePos == -1) {
+ groupByPos = textParser.findWordLower("group", fromPos + 5);
+ } else {
+ groupByPos = textParser.findWordLower("group");
+ }
+ if (groupByPos > -1) {
+ havingPos = textParser.findWordLower("having");
+ }
+
+ int startOrderBy = havingPos;
+ if (startOrderBy == -1) {
+ startOrderBy = groupByPos;
+ }
+ if (startOrderBy == -1) {
+ startOrderBy = wherePos;
+ }
+ if (startOrderBy == -1) {
+ startOrderBy = fromPos;
+ }
+
+ orderByPos = textParser.findWordLower("order", startOrderBy);
+ }
+
+ private int findWhereExprPosition() {
+ if (hasPlaceHolders) {
+ if (placeHolderWhere > -1) {
+ return placeHolderWhere;
+ } else {
+ whereExprAnd = true;
+ return placeHolderAndWhere;
+ }
+ }
+ whereExprAnd = wherePos > 0;
+ if (groupByPos > 0) {
+ return groupByPos;
+ }
+ if (havingPos > 0) {
+ return havingPos;
+ }
+ if (orderByPos > 0) {
+ return orderByPos;
+ }
+ return -1;
+ }
+
+ private int findHavingExprPosition() {
+ if (hasPlaceHolders) {
+ if (placeHolderHaving > -1) {
+ return placeHolderHaving;
+ } else {
+ havingExprAnd = true;
+ return placeHolderAndHaving;
+ }
+ }
+ havingExprAnd = havingPos > 0;
+ if (orderByPos > 0) {
+ return orderByPos;
+ }
+ return -1;
+ }
+
+ private String removeWhitespace(String sql) {
+ if (sql == null) {
+ return "";
+ }
+
+ boolean removeWhitespace = false;
+
+ int length = sql.length();
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < length; i++) {
+ char c = sql.charAt(i);
+ if (removeWhitespace) {
+ if (!Character.isWhitespace(c)) {
+ sb.append(c);
+ removeWhitespace = false;
+ }
+ } else {
+ if (c == '\r' || c == '\n') {
+ sb.append('\n');
+ removeWhitespace = true;
+ } else {
+ sb.append(c);
+ }
+ }
+ }
+
+ String s = sb.toString();
+ return s.trim();
+ }
+}
diff --git a/src/main/java/com/avaje/ebean/Ebean.java b/src/main/java/com/avaje/ebean/Ebean.java
new file mode 100644
index 000000000..b8f6a86f5
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/Ebean.java
@@ -0,0 +1,1364 @@
+package com.avaje.ebean;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.persistence.OptimisticLockException;
+import javax.persistence.PersistenceException;
+
+import com.avaje.ebean.annotation.CacheStrategy;
+import com.avaje.ebean.cache.ServerCacheManager;
+import com.avaje.ebean.config.GlobalProperties;
+import com.avaje.ebean.config.ServerConfig;
+import com.avaje.ebean.text.csv.CsvReader;
+import com.avaje.ebean.text.json.JsonContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This Ebean object is effectively a singleton that holds a map of registered
+ * {@link EbeanServer}s. It additionally provides a convenient way to use the
+ * 'default/primary' EbeanServer.
+ *
+ * If you are using a Dependency Injection framework such as
+ * Spring or Guice you will probably
+ * NOT use this Ebean singleton object. Instead you will
+ * configure and construct EbeanServer instances using {@link ServerConfig} and
+ * {@link EbeanServerFactory} and inject those EbeanServer instances into your
+ * data access objects.
+ *
+ *
+ * In documentation "Ebean singleton" refers to this object.
+ *
+ *
+ *
There is one EbeanServer per Database (javax.sql.DataSource).
+ *
EbeanServers can be 'registered' with the Ebean singleton (put into its
+ * map). Registered EbeanServer's can later be retrieved via
+ * {@link #getServer(String)}.
+ *
One EbeanServer can be referred to as the 'default' EbeanServer. For
+ * convenience, the Ebean singleton (this object) provides methods such as
+ * {@link #find(Class)} that proxy through to the 'default' EbeanServer. This
+ * can be useful for applications that use a single database.
+ *
+ *
+ *
+ * For developer convenience Ebean has static methods that proxy through to the
+ * methods on the 'default' EbeanServer. These methods are provided for
+ * developers who are mostly using a single database. Many developers will be
+ * able to use the methods on Ebean rather than get a EbeanServer.
+ *
+ *
+ * EbeanServers can be created and used without ever needing or using the Ebean
+ * singleton. Refer to {@link ServerConfig#setRegister(boolean)}.
+ *
+ *
+ * You can either programmatically create/register EbeanServers via
+ * {@link EbeanServerFactory} or they can automatically be created and
+ * registered when you first use the Ebean singleton. When EbeanServers are
+ * created automatically they are configured using information in the
+ * ebean.properties file.
+ *
+ *
+ *
+ * // fetch shipped orders (and also their customer)
+ * List<Order> list = Ebean.find(Order.class)
+ * .fetch("customer")
+ * .where()
+ * .eq("status.code", Order.Status.SHIPPED)
+ * .findList();
+ *
+ * // read/use the order list ...
+ * for (Order order : list) {
+ * Customer customer = order.getCustomer();
+ * ...
+ * }
+ *
+ *
+ *
+ * // fetch order 10, modify and save
+ * Order order = Ebean.find(Order.class, 10);
+ *
+ * OrderStatus shipped = Ebean.getReference(OrderStatus.class,"SHIPPED");
+ * order.setStatus(shipped);
+ * order.setShippedDate(shippedDate);
+ * ...
+ *
+ * // implicitly creates a transaction and commits
+ * Ebean.save(order);
+ *
+ *
+ *
+ * When you have multiple databases and need access to a specific one the
+ * {@link #getServer(String)} method provides access to the EbeanServer for that
+ * specific database.
+ *
+ *
+ *
+ * // Get access to the Human Resources EbeanServer/Database
+ * EbeanServer hrDb = Ebean.getServer("hr");
+ *
+ *
+ * // fetch contact 3 from the HR database
+ * Contact contact = hrDb.find(Contact.class, 3);
+ *
+ * contact.setName("I'm going to change");
+ * ...
+ *
+ * // save the contact back to the HR database
+ * hrDb.save(contact);
+ *
+ */
+public final class Ebean {
+ private static final Logger logger = LoggerFactory.getLogger(Ebean.class);
+
+ /**
+ * Manages creation and cache of EbeanServers.
+ */
+ private static final Ebean.ServerManager serverMgr = new Ebean.ServerManager();
+
+ /**
+ * Helper class for managing fast and safe access and creation of
+ * EbeanServers.
+ */
+ private static final class ServerManager {
+
+ /**
+ * Cache for fast concurrent read access.
+ */
+ private final ConcurrentHashMap concMap = new ConcurrentHashMap();
+
+ /**
+ * Cache for synchronized read, creation and put. Protected by the monitor
+ * object.
+ */
+ private final HashMap syncMap = new HashMap();
+
+ private final Object monitor = new Object();
+
+ /**
+ * The 'default/primary' EbeanServer.
+ */
+ private EbeanServer primaryServer;
+
+ private ServerManager() {
+
+ // skipDefaultServer is set by EbeanServerFactory
+ // ... when it is creating the primaryServer
+ if (GlobalProperties.isSkipPrimaryServer()) {
+ // primary server being created by EbeanServerFactory
+ // ... so we should not try and create it here
+ logger.debug("GlobalProperties.isSkipPrimaryServer()");
+
+ } else {
+ // look to see if there is a default server defined
+ String primaryName = getPrimaryServerName();
+ logger.debug("primaryName:" + primaryName);
+ if (primaryName != null && primaryName.trim().length() > 0) {
+ primaryServer = getWithCreate(primaryName.trim());
+ }
+ }
+ }
+
+ private String getPrimaryServerName() {
+
+ String serverName = GlobalProperties.get("ebean.default.datasource", null);
+ return GlobalProperties.get("datasource.default", serverName);
+ }
+
+ private EbeanServer getPrimaryServer() {
+ if (primaryServer == null) {
+ String msg = "The default EbeanServer has not been defined?";
+ msg += " This is normally set via the ebean.datasource.default property.";
+ msg += " Otherwise it should be registered programatically via registerServer()";
+ throw new PersistenceException(msg);
+ }
+ return primaryServer;
+ }
+
+ private EbeanServer get(String name) {
+ if (name == null || name.length() == 0) {
+ return primaryServer;
+ }
+ // non-synchronized read
+ EbeanServer server = concMap.get(name);
+ if (server != null) {
+ return server;
+ }
+ // synchronized read, create and put
+ return getWithCreate(name);
+ }
+
+ /**
+ * Synchronized read, create and put of EbeanServers.
+ */
+ private EbeanServer getWithCreate(String name) {
+
+ synchronized (monitor) {
+
+ EbeanServer 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.
+ */
+ private void register(EbeanServer server, boolean isPrimaryServer) {
+ synchronized (monitor) {
+ concMap.put(server.getName(), server);
+ EbeanServer existingServer = syncMap.put(server.getName(), server);
+ if (existingServer != null) {
+ String msg = "Existing EbeanServer [" + server.getName() + "] is being replaced?";
+ logger.warn(msg);
+ }
+
+ if (isPrimaryServer) {
+ primaryServer = server;
+ }
+ }
+ }
+
+ }
+
+ private Ebean() {
+ }
+
+ /**
+ * Get the EbeanServer for a given DataSource. If name is null this will
+ * return the 'default' EbeanServer.
+ *
+ * This is provided to access EbeanServer for databases other than the
+ * 'default' database. EbeanServer also provides more control over
+ * transactions and the ability to use transactions created externally to
+ * Ebean.
+ *
+ *
+ *
+ * // use the "hr" database
+ * EbeanServer hrDatabase = Ebean.getServer("hr");
+ *
+ * Person person = hrDatabase.find(Person.class, 10);
+ *
+ *
+ * @param name
+ * the name of the server, use null for the 'default server'
+ */
+ public static EbeanServer getServer(String name) {
+ return serverMgr.get(name);
+ }
+
+ /**
+ * Return the ExpressionFactory from the default server.
+ *
+ * The ExpressionFactory is used internally by the query and ExpressionList to
+ * build the WHERE and HAVING clauses. Alternatively you can use the
+ * ExpressionFactory directly to create expressions to add to the query where
+ * clause.
+ *
+ *
+ * Alternatively you can use the {@link Expr} as a shortcut to the
+ * ExpressionFactory of the 'Default' EbeanServer.
+ *
+ *
+ * You generally need to the an ExpressionFactory (or {@link Expr}) to build
+ * an expression that uses OR like Expression e = Expr.or(..., ...);
+ *
+ */
+ public static ExpressionFactory getExpressionFactory() {
+ return serverMgr.getPrimaryServer().getExpressionFactory();
+ }
+
+ /**
+ * Register the server with this Ebean singleton. Specify if the registered
+ * server is the primary/default server.
+ */
+ protected static void register(EbeanServer server, boolean isPrimaryServer) {
+ serverMgr.register(server, isPrimaryServer);
+ }
+
+ /**
+ * Return the next identity value for a given bean type.
+ *
+ * This will only work when a IdGenerator is on this bean type such as a DB
+ * sequence or UUID.
+ *
+ *
+ * For DB's supporting getGeneratedKeys and sequences such as Oracle10 you do
+ * not need to use this method generally. It is made available for more
+ * complex cases where it is useful to get an ID prior to some processing.
+ *
+ */
+ public static Object nextId(Class> beanType) {
+ return serverMgr.getPrimaryServer().nextId(beanType);
+ }
+
+ /**
+ * Start a new explicit transaction.
+ *
+ * The transaction is stored in a ThreadLocal variable and typically you only
+ * need to use the returned Transaction IF you wish to do things like
+ * use batch mode, change the transaction isolation level, use savepoints or
+ * log comments to the transaction log.
+ *
+ *
+ * Example of using a transaction to span multiple calls to find(), save()
+ * etc.
+ *
+ *
+ *
+ * // start a transaction (stored in a ThreadLocal)
+ * Ebean.beginTransaction();
+ * try {
+ * Order order = Ebean.find(Order.class,10); ...
+ *
+ * Ebean.save(order);
+ *
+ * Ebean.commitTransaction();
+ *
+ * } finally {
+ * // rollback if we didn't commit
+ * // i.e. an exception occurred before commitTransaction().
+ * Ebean.endTransaction();
+ * }
+ *
+ *
+ *
+ * If you want to externalise the transaction management then you should be
+ * able to do this via EbeanServer. Specifically with EbeanServer you can pass
+ * the transaction to the various find() and save() execute() methods. This
+ * gives you the ability to create the transactions yourself externally from
+ * Ebean and pass those transactions through to the various methods available
+ * on EbeanServer.
+ *
+ */
+ public static Transaction beginTransaction() {
+ return serverMgr.getPrimaryServer().beginTransaction();
+ }
+
+ /**
+ * Start a transaction additionally specifying the isolation level.
+ *
+ * @param isolation
+ * the Transaction isolation level
+ *
+ */
+ public static Transaction beginTransaction(TxIsolation isolation) {
+ return serverMgr.getPrimaryServer().beginTransaction(isolation);
+ }
+
+ /**
+ * Returns the current transaction or null if there is no current transaction
+ * in scope.
+ */
+ public static Transaction currentTransaction() {
+ return serverMgr.getPrimaryServer().currentTransaction();
+ }
+
+ /**
+ * Commit the current transaction.
+ */
+ public static void commitTransaction() {
+ serverMgr.getPrimaryServer().commitTransaction();
+ }
+
+ /**
+ * Rollback the current transaction.
+ */
+ public static void rollbackTransaction() {
+ serverMgr.getPrimaryServer().rollbackTransaction();
+ }
+
+ /**
+ * If the current transaction has already been committed do nothing otherwise
+ * rollback the transaction.
+ *
+ * Useful to put in a finally block to ensure the transaction is ended, rather
+ * than a rollbackTransaction() in each catch block.
+ *
+ *
+ * Code example:
+ *
+ *
+ *
+ * Ebean.beginTransaction();
+ * try {
+ * // do some fetching and or persisting
+ * // commit at the end Ebean.commitTransaction();
+ *
+ * } finally {
+ * // if commit didn't occur then rollback the transaction
+ * Ebean.endTransaction();
+ * }
+ *
+ */
+ public static void endTransaction() {
+ serverMgr.getPrimaryServer().endTransaction();
+ }
+
+ /**
+ * Return a map of the differences between two objects of the same type.
+ *
+ * When null is passed in for b, then the 'OldValues' of a is used for the
+ * difference comparison.
+ *
+ */
+ public static Map diff(Object a, Object b) {
+ return serverMgr.getPrimaryServer().diff(a, b);
+ }
+
+ /**
+ * Either Insert or Update the bean depending on its state.
+ *
+ * If there is no current transaction one will be created and committed for
+ * you automatically.
+ *
+ *
+ * Save can cascade along relationships. For this to happen you need to
+ * specify a cascade of CascadeType.ALL or CascadeType.PERSIST on the
+ * OneToMany, OneToOne or ManyToMany annotation.
+ *
+ *
+ * In this example below the details property has a CascadeType.ALL set so
+ * saving an order will also save all its details.
+ *
+ *
+ *
+ * public class Order { ...
+ *
+ * @OneToMany(cascade=CascadeType.ALL, mappedBy="order")
+ * @JoinColumn(name="order_id")
+ * List<OrderDetail> details;
+ * ...
+ * }
+ *
+ *
+ *
+ * When a save cascades via a OneToMany or ManyToMany Ebean will automatically
+ * set the 'parent' object to the 'detail' object. In the example below in
+ * saving the order and cascade saving the order details the 'parent' order
+ * will be set against each order detail when it is saved.
+ *
+ */
+ public static void save(Object bean) throws OptimisticLockException {
+ serverMgr.getPrimaryServer().save(bean);
+ }
+
+ /**
+ * Force an update using the bean updating the non-null properties.
+ *
+ * You can use this method to FORCE an update to occur (even on a bean that
+ * has not been fetched but say built from JSON or XML). When
+ * {@link Ebean#save(Object)} is used Ebean determines whether to use an
+ * insert or an update based on the state of the bean. Using this method will
+ * force an update to occur.
+ *
+ *
+ * It is expected that this method is most useful in stateless REST services
+ * or web applications where you have the values you wish to update but no
+ * existing bean.
+ *
+ *
+ * For updates against beans that have not been fetched (say built from JSON
+ * or XML) this will treat deleteMissingChildren=true and will delete any
+ * 'missing children'. Refer to
+ * {@link EbeanServer#update(Object, Set, Transaction, boolean, boolean)}.
+ *
+ *
+ *
+ *
+ * Customer c = new Customer();
+ * c.setId(7);
+ * c.setName("ModifiedNameNoOCC");
+ *
+ * // generally you should set the version property
+ * // so that Optimistic Concurrency Checking is used.
+ * // If a version property is not set then no Optimistic
+ * // Concurrency Checking occurs for the update
+ * // c.setLastUpdate(lastUpdateTime);
+ *
+ * // by default the Non-null properties
+ * // are included in the update
+ * Ebean.update(c);
+ *
+ *
+ */
+ public static void update(Object bean) {
+ serverMgr.getPrimaryServer().update(bean);
+ }
+
+ /**
+ * Force an update using the bean explicitly stating the properties to update.
+ *
+ * If you don't specify explicit properties to use in the update then the
+ * non-null properties are included in the update.
+ *
+ *
+ * For updates against beans that have not been fetched (say built from JSON
+ * or XML) this will treat deleteMissingChildren=true and will delete any
+ * 'missing children'. Refer to
+ * {@link EbeanServer#update(Object, Set, Transaction, boolean, boolean)}.
+ *
+ *
+ * @param bean
+ * The bean holding the values to be included in the update.
+ * @param updateProps
+ * the explicit set of properties to include in the update (can be
+ * null).
+ */
+ public static void update(Object bean, Set updateProps) {
+ serverMgr.getPrimaryServer().update(bean, updateProps);
+ }
+
+ /**
+ * Save all the beans from an Iterator.
+ */
+ public static int save(Iterator> iterator) throws OptimisticLockException {
+ return serverMgr.getPrimaryServer().save(iterator);
+ }
+
+ /**
+ * Save all the beans from a Collection.
+ */
+ public static int save(Collection> c) throws OptimisticLockException {
+ return save(c.iterator());
+ }
+
+ /**
+ * Delete the associations (from the intersection table) of a ManyToMany given
+ * the owner bean and the propertyName of the ManyToMany collection.
+ *
+ * Typically these deletions occur automatically when persisting a ManyToMany
+ * collection and this provides a way to invoke those deletions directly.
+ *
+ *
+ * @return the number of associations deleted (from the intersection table).
+ */
+ public static int deleteManyToManyAssociations(Object ownerBean, String propertyName) {
+ return serverMgr.getPrimaryServer().deleteManyToManyAssociations(ownerBean, propertyName);
+ }
+
+ /**
+ * Save the associations of a ManyToMany given the owner bean and the
+ * propertyName of the ManyToMany collection.
+ *
+ * Typically the saving of these associations (inserting into the intersection
+ * table) occurs automatically when persisting a ManyToMany. This provides a
+ * way to invoke those insertions directly.
+ *
+ *
+ * You can use this when the collection is new and in this case all the
+ * entries in the collection are treated as additions are result in inserts
+ * into the intersection table.
+ *
+ */
+ public static void saveManyToManyAssociations(Object ownerBean, String propertyName) {
+ serverMgr.getPrimaryServer().saveManyToManyAssociations(ownerBean, propertyName);
+ }
+
+ /**
+ * Save the associated collection or bean given the property name.
+ *
+ * This is similar to performing a save cascade on a specific property
+ * manually/programmatically.
+ *
+ *
+ * Note that you can turn on/off cascading for a transaction via
+ * {@link Transaction#setPersistCascade(boolean)}
+ *
+ *
+ * @param ownerBean
+ * the bean instance holding the property we want to save
+ * @param propertyName
+ * the property we want to save
+ */
+ public static void saveAssociation(Object ownerBean, String propertyName) {
+ serverMgr.getPrimaryServer().saveAssociation(ownerBean, propertyName);
+ }
+
+ /**
+ * Delete the bean.
+ *
+ * If there is no current transaction one will be created and committed for
+ * you automatically.
+ *
+ */
+ public static void delete(Object bean) throws OptimisticLockException {
+ serverMgr.getPrimaryServer().delete(bean);
+ }
+
+ /**
+ * Delete the bean given its type and id.
+ */
+ public static int delete(Class> beanType, Object id) {
+ return serverMgr.getPrimaryServer().delete(beanType, id);
+ }
+
+ /**
+ * Delete several beans given their type and id values.
+ */
+ public static void delete(Class> beanType, Collection> ids) {
+ serverMgr.getPrimaryServer().delete(beanType, ids);
+ }
+
+ /**
+ * Delete all the beans from an Iterator.
+ */
+ public static int delete(Iterator> it) throws OptimisticLockException {
+ return serverMgr.getPrimaryServer().delete(it);
+ }
+
+ /**
+ * Delete all the beans from a Collection.
+ */
+ public static int delete(Collection> c) throws OptimisticLockException {
+ return delete(c.iterator());
+ }
+
+ /**
+ * Refresh the values of a bean.
+ *
+ * Note that this does not refresh any OneToMany or ManyToMany properties.
+ *
+ */
+ public static void refresh(Object bean) {
+ serverMgr.getPrimaryServer().refresh(bean);
+ }
+
+ /**
+ * Refresh a 'many' property of a bean.
+ *
+ *
+ * Order order = ...;
+ * ...
+ * // refresh the order details...
+ * Ebean.refreshMany(order, "details");
+ *
+ *
+ * @param bean
+ * the entity bean containing the List Set or Map to refresh.
+ * @param manyPropertyName
+ * the property name of the List Set or Map to refresh.
+ */
+ public static void refreshMany(Object bean, String manyPropertyName) {
+ serverMgr.getPrimaryServer().refreshMany(bean, manyPropertyName);
+ }
+
+ /**
+ * Get a reference object.
+ *
+ * This is sometimes described as a proxy (with lazy loading).
+ *
+ *
+ *
+ * Product product = Ebean.getReference(Product.class, 1);
+ *
+ * // You can get the id without causing a fetch/lazy load
+ * Integer productId = product.getId();
+ *
+ * // If you try to get any other property a fetch/lazy loading will occur
+ * // This will cause a query to execute...
+ * String name = product.getName();
+ *
+ *
+ * @param beanType
+ * the type of entity bean
+ * @param id
+ * the id value
+ */
+ public static T getReference(Class beanType, Object id) {
+ return serverMgr.getPrimaryServer().getReference(beanType, id);
+ }
+
+ /**
+ * Sort the list using the sortByClause which can contain a comma delimited
+ * list of property names and keywords asc, desc, nullsHigh and nullsLow.
+ *
+ *
asc - ascending order (which is the default)
+ *
desc - Descending order
+ *
nullsHigh - Treat null values as high/large values (which is the
+ * default)
+ *
nullsLow- Treat null values as low/very small values
+ *
+ *
+ * If you leave off any keywords the defaults are ascending order and treating
+ * nulls as high values.
+ *
+ *
+ * Note that the sorting uses a Comparator and Collections.sort(); and does
+ * not invoke a DB query.
+ *
+ *
+ *
+ *
+ * // find orders and their customers
+ * List<Order> list = Ebean.find(Order.class)
+ * .fetch("customer")
+ * .orderBy("id")
+ * .findList();
+ *
+ * // sort by customer name ascending, then by order shipDate
+ * // ... then by the order status descending
+ * Ebean.sort(list, "customer.name, shipDate, status desc");
+ *
+ * // sort by customer name descending (with nulls low)
+ * // ... then by the order id
+ * Ebean.sort(list, "customer.name desc nullsLow, id");
+ *
+ *
+ *
+ * @param list
+ * the list of entity beans
+ * @param sortByClause
+ * the properties to sort the list by
+ */
+ public static void sort(List list, String sortByClause) {
+ serverMgr.getPrimaryServer().sort(list, sortByClause);
+ }
+
+ /**
+ * Find a bean using its unique id. This will not use caching.
+ *
+ *
+ * // Fetch order 1
+ * Order order = Ebean.find(Order.class, 1);
+ *
+ *
+ *
+ * If you want more control over the query then you can use createQuery() and
+ * Query.findUnique();
+ *
+ *
+ *
+ * // ... additionally fetching customer, customer shipping address,
+ * // order details, and the product associated with each order detail.
+ * // note: only product id and name is fetch (its a "partial object").
+ * // note: all other objects use "*" and have all their properties fetched.
+ *
+ * Query<Order> query = Ebean.createQuery(Order.class);
+ * query.setId(1);
+ * query.fetch("customer");
+ * query.fetch("customer.shippingAddress");
+ * query.fetch("details");
+ *
+ * // fetch associated products but only fetch their product id and name
+ * query.fetch("details.product", "name");
+ *
+ * // traverse the object graph...
+ *
+ * Order order = query.findUnique();
+ * Customer customer = order.getCustomer();
+ * Address shippingAddress = customer.getShippingAddress();
+ * List<OrderDetail> details = order.getDetails();
+ * OrderDetail detail0 = details.get(0);
+ * Product product = detail0.getProduct();
+ * String productName = product.getName();
+ *
+ *
+ * @param beanType
+ * the type of entity bean to fetch
+ * @param id
+ * the id value
+ */
+ public static T find(Class beanType, Object id) {
+ return serverMgr.getPrimaryServer().find(beanType, id);
+ }
+
+ /**
+ * Create a SqlQuery for executing native sql
+ * query statements.
+ *
+ * Note that you can use raw SQL with entity beans, refer to the SqlSelect
+ * annotation for examples.
+ *
+ */
+ public static SqlQuery createSqlQuery(String sql) {
+ return serverMgr.getPrimaryServer().createSqlQuery(sql);
+ }
+
+ /**
+ * Create a named sql query.
+ *
+ * The query statement will be defined in a deployment orm xml file.
+ *
+ *
+ * @param namedQuery
+ * the name of the query
+ */
+ public static SqlQuery createNamedSqlQuery(String namedQuery) {
+ return serverMgr.getPrimaryServer().createNamedSqlQuery(namedQuery);
+ }
+
+ /**
+ * Create a sql update for executing native dml statements.
+ *
+ * Use this to execute a Insert Update or Delete statement. The statement will
+ * be native to the database and contain database table and column names.
+ *
+ *
+ * See {@link SqlUpdate} for example usage.
+ *
+ *
+ * Where possible it would be expected practice to put the statement in a orm
+ * xml file (named update) and use {@link #createNamedSqlUpdate(String)} .
+ *
+ */
+ public static SqlUpdate createSqlUpdate(String sql) {
+ return serverMgr.getPrimaryServer().createSqlUpdate(sql);
+ }
+
+ /**
+ * Create a CallableSql to execute a given stored procedure.
+ *
+ * @see CallableSql
+ */
+ public static CallableSql createCallableSql(String sql) {
+ return serverMgr.getPrimaryServer().createCallableSql(sql);
+ }
+
+ /**
+ * Create a named sql update.
+ *
+ * The statement (an Insert Update or Delete statement) will be defined in a
+ * deployment orm xml file.
+ *
+ */
+ public static SqlUpdate createNamedSqlUpdate(String namedQuery) {
+ return serverMgr.getPrimaryServer().createNamedSqlUpdate(namedQuery);
+ }
+
+ /**
+ * Return a named Query that will have defined fetch paths, predicates etc.
+ *
+ * The query is created from a statement that will be defined in a deployment
+ * orm xml file or NamedQuery annotations. The query will typically already
+ * define fetch paths, predicates, order by clauses etc so often you will just
+ * need to bind required parameters and then execute the query.
+ *
+ *
+ * @param beanType
+ * the class of entity to be fetched
+ * @param namedQuery
+ * the name of the query
+ */
+ public static Query createNamedQuery(Class beanType, String namedQuery) {
+
+ return serverMgr.getPrimaryServer().createNamedQuery(beanType, namedQuery);
+ }
+
+ /**
+ * Create a query using the query language.
+ *
+ * Note that you are allowed to add additional clauses using where() as well
+ * as use fetch() and setOrderBy() after the query has been created.
+ *
+ *
+ * Note that this method signature used to map to named queries and that has
+ * moved to {@link #createNamedQuery(Class, String)}.
+ *
+ *
+ * @param query
+ * the object query
+ */
+ public static Query createQuery(Class beanType, String query) {
+ return serverMgr.getPrimaryServer().createQuery(beanType, query);
+ }
+
+ /**
+ * Create a named orm update. The update statement is specified via the
+ * NamedUpdate annotation.
+ *
+ * The orm update differs from the SqlUpdate in that it uses the bean name and
+ * bean property names rather than table and column names.
+ *
+ *
+ * Note that named update statements can be specified in raw sql (with column
+ * and table names) or using bean name and bean property names. This can be
+ * specified with the isSql flag.
+ *
+ *
+ * Example named updates:
+ *
+ *
+ *
+ * package app.data;
+ *
+ * import ...
+ *
+ * @NamedUpdates(value = {
+ * @NamedUpdate( name = "setTitle",
+ * isSql = false,
+ * notifyCache = false,
+ * update = "update topic set title = :title, postCount = :postCount where id = :id"),
+ * @NamedUpdate( name = "setPostCount",
+ * notifyCache = false,
+ * update = "update f_topic set post_count = :postCount where id = :id"),
+ * @NamedUpdate( name = "incrementPostCount",
+ * notifyCache = false,
+ * isSql = false,
+ * update = "update Topic set postCount = postCount + 1 where id = :id") })
+ * @Entity
+ * @Table(name = "f_topic")
+ * public class Topic { ...
+ *
+ */
+ public static Update createNamedUpdate(Class beanType, String namedUpdate) {
+
+ return serverMgr.getPrimaryServer().createNamedUpdate(beanType, namedUpdate);
+ }
+
+ /**
+ * Create a orm update where you will supply the insert/update or delete
+ * statement (rather than using a named one that is already defined using the
+ * @NamedUpdates annotation).
+ *
+ * The orm update differs from the sql update in that it you can use the bean
+ * name and bean property names rather than table and column names.
+ *
+ *
+ * An example:
+ *
+ *
+ *
+ *
+ * // The bean name and properties - "topic","postCount" and "id"
+ *
+ * // will be converted into their associated table and column names
+ * String updStatement = "update topic set postCount = :pc where id = :id";
+ *
+ * Update<Topic> update = Ebean.createUpdate(Topic.class, updStatement);
+ *
+ * update.set("pc", 9);
+ * update.set("id", 3);
+ *
+ * int rows = update.execute();
+ * System.out.println("rows updated:" + rows);
+ *
+ */
+ public static Update createUpdate(Class beanType, String ormUpdate) {
+
+ return serverMgr.getPrimaryServer().createUpdate(beanType, ormUpdate);
+ }
+
+ /**
+ * Create a CsvReader for a given beanType.
+ */
+ public static CsvReader createCsvReader(Class beanType) {
+
+ return serverMgr.getPrimaryServer().createCsvReader(beanType);
+ }
+
+ /**
+ * Create a query for a type of entity bean.
+ *
+ * You can use the methods on the Query object to specify fetch paths,
+ * predicates, order by, limits etc.
+ *
+ *
+ * You then use findList(), findSet(), findMap() and findUnique() to execute
+ * the query and return the collection or bean.
+ *
+ *
+ * Note that a query executed by {@link Query#findList()}
+ * {@link Query#findSet()} etc will execute against the same EbeanServer from
+ * which is was created.
+ *
+ *
+ *
+ * // Find order 2 additionally fetching the customer, details and details.product
+ * // name.
+ *
+ * Query<Order> query = Ebean.createQuery(Order.class);
+ * query.fetch("customer");
+ * query.fetch("details");
+ * query.fetch("detail.product", "name");
+ * query.setId(2);
+ *
+ * Order order = query.findUnique();
+ *
+ * // Find order 2 additionally fetching the customer, details and details.product
+ * // name.
+ * // Note: same query as above but using the query language
+ * // Note: using a named query would be preferred practice
+ *
+ * String oql = "find order fetch customer fetch details fetch details.product (name) where id = :orderId ";
+ *
+ * Query<Order> query = Ebean.createQuery(Order.class);
+ * query.setQuery(oql);
+ * query.setParameter("orderId", 2);
+ *
+ * Order order = query.findUnique();
+ *
+ * // Using a named query
+ * Query<Order> query = Ebean.createQuery(Order.class, "with.details");
+ * query.setParameter("orderId", 2);
+ *
+ * Order order = query.findUnique();
+ *
+ *
+ *
+ * @param beanType
+ * the class of entity to be fetched
+ * @return A ORM Query object for this beanType
+ */
+ public static Query createQuery(Class beanType) {
+
+ return serverMgr.getPrimaryServer().createQuery(beanType);
+ }
+
+ /**
+ * Create a query for a type of entity bean.
+ *
+ * This is actually the same as {@link #createQuery(Class)}. The reason it
+ * exists is that people used to JPA will probably be looking for a
+ * createQuery method (the same as entityManager).
+ *
+ *
+ * @param beanType
+ * the type of entity bean to find
+ * @return A ORM Query object for this beanType
+ */
+ public static Query find(Class beanType) {
+
+ return serverMgr.getPrimaryServer().find(beanType);
+ }
+
+ /**
+ * Create a filter for sorting and filtering lists of entities locally without
+ * going back to the database.
+ *
+ * This produces and returns a new list with the sort and filters applied.
+ *
+ *
+ * Refer to {@link Filter} for an example of its use.
+ *
+ */
+ public static Filter filter(Class beanType) {
+ return serverMgr.getPrimaryServer().filter(beanType);
+ }
+
+ /**
+ * Execute a Sql Update Delete or Insert statement. This returns the number of
+ * rows that where updated, deleted or inserted. If is executed in batch then
+ * this returns -1. You can get the actual rowCount after commit() from
+ * updateSql.getRowCount().
+ *
+ * If you wish to execute a Sql Select natively then you should use the
+ * FindByNativeSql object.
+ *
+ *
+ * Note that the table modification information is automatically deduced and
+ * you do not need to call the Ebean.externalModification() method when you
+ * use this method.
+ *
+ *
+ * Example:
+ *
+ *
+ *
+ * // example that uses 'named' parameters
+ * String s = "UPDATE f_topic set post_count = :count where id = :id"
+ *
+ * SqlUpdate update = Ebean.createSqlUpdate(s);
+ *
+ * update.setParameter("id", 1);
+ * update.setParameter("count", 50);
+ *
+ * int modifiedCount = Ebean.execute(update);
+ *
+ * String msg = "There where " + modifiedCount + "rows updated";
+ *
+ *
+ * @param sqlUpdate
+ * the update sql potentially with bind values
+ *
+ * @return the number of rows updated or deleted. -1 if executed in batch.
+ *
+ * @see SqlUpdate
+ * @see CallableSql
+ * @see Ebean#execute(CallableSql)
+ */
+ public static int execute(SqlUpdate sqlUpdate) {
+ return serverMgr.getPrimaryServer().execute(sqlUpdate);
+ }
+
+ /**
+ * For making calls to stored procedures.
+ *
+ */
+ public static void execute(TxRunnable r) {
+ serverMgr.getPrimaryServer().execute(r);
+ }
+
+ /**
+ * Execute a TxCallable in a Transaction with an explicit scope.
+ *
+ * The scope can control the transaction type, isolation and rollback
+ * semantics.
+ *
+ *
+ *
+ * // set specific transactional scope settings
+ * TxScope scope = TxScope.requiresNew().setIsolation(TxIsolation.SERIALIZABLE);
+ *
+ * Ebean.execute(scope, new TxCallable<String>() {
+ * public String call() {
+ * User u1 = Ebean.find(User.class, 1);
+ * ...
+ * return u1.getEmail();
+ * }
+ * });
+ *
+ *
+ */
+ public static T execute(TxScope scope, TxCallable c) {
+ return serverMgr.getPrimaryServer().execute(scope, c);
+ }
+
+ /**
+ * Execute a TxCallable in a Transaction with the default scope.
+ *
+ * The default scope runs with REQUIRED and by default will rollback on any
+ * exception (checked or runtime).
+ *
+ *
+ * This is basically the same as TxRunnable except that it returns an Object
+ * (and you specify the return type via generics).
+ *
+ */
+ public static T execute(TxCallable c) {
+ return serverMgr.getPrimaryServer().execute(c);
+ }
+
+ /**
+ * Inform Ebean that tables have been modified externally. These could be the
+ * result of from calling a stored procedure, other JDBC calls or external
+ * programs including other frameworks.
+ *
+ * If you use Ebean.execute(UpdateSql) then the table modification information
+ * is automatically deduced and you do not need to call this method yourself.
+ *
+ *
+ * This information is used to invalidate objects out of the cache and
+ * potentially text indexes. This information is also automatically broadcast
+ * across the cluster.
+ *
+ *
+ * If there is a transaction then this information is placed into the current
+ * transactions event information. When the transaction is commited this
+ * information is registered (with the transaction manager). If this
+ * transaction is rolled back then none of the transaction event information
+ * registers including the information you put in via this method.
+ *
+ *
+ * If there is NO current transaction when you call this method then this
+ * information is registered immediately (with the transaction manager).
+ *
+ *
+ * @param tableName
+ * the name of the table that was modified
+ * @param inserts
+ * true if rows where inserted into the table
+ * @param updates
+ * true if rows on the table where updated
+ * @param deletes
+ * true if rows on the table where deleted
+ */
+ public static void externalModification(String tableName, boolean inserts, boolean updates,
+ boolean deletes) {
+
+ serverMgr.getPrimaryServer().externalModification(tableName, inserts, updates, deletes);
+ }
+
+ /**
+ * Return the BeanState for a given entity bean.
+ *
+ * This will return null if the bean is not an enhanced (or subclassed) entity
+ * bean.
+ *
+ */
+ public static BeanState getBeanState(Object bean) {
+ return serverMgr.getPrimaryServer().getBeanState(bean);
+ }
+
+ /**
+ * Return the manager of the server cache ("L2" cache).
+ *
+ */
+ public static ServerCacheManager getServerCacheManager() {
+ return serverMgr.getPrimaryServer().getServerCacheManager();
+ }
+
+ /**
+ * Return the BackgroundExecutor service for asynchronous processing of
+ * queries.
+ */
+ public static BackgroundExecutor getBackgroundExecutor() {
+ return serverMgr.getPrimaryServer().getBackgroundExecutor();
+ }
+
+ /**
+ * Run the cache warming queries on all bean types that have one defined for
+ * the default/primary EbeanServer.
+ *
+ * A cache warming query can be defined via {@link CacheStrategy}.
+ *
+ */
+ public static void runCacheWarming() {
+ serverMgr.getPrimaryServer().runCacheWarming();
+ }
+
+ /**
+ * Run the cache warming query for a specific bean type for the
+ * default/primary EbeanServer.
+ *
+ * A cache warming query can be defined via {@link CacheStrategy}.
+ *
+ */
+ public static void runCacheWarming(Class> beanType) {
+
+ serverMgr.getPrimaryServer().runCacheWarming(beanType);
+ }
+
+ /**
+ * Create a JsonContext that will use the default configuration options.
+ */
+ public static JsonContext createJsonContext() {
+ return serverMgr.getPrimaryServer().createJsonContext();
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebean/EbeanServer.java b/src/main/java/com/avaje/ebean/EbeanServer.java
new file mode 100644
index 000000000..e31240efe
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/EbeanServer.java
@@ -0,0 +1,1154 @@
+package com.avaje.ebean;
+
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.persistence.OptimisticLockException;
+
+import com.avaje.ebean.annotation.CacheStrategy;
+import com.avaje.ebean.cache.ServerCacheManager;
+import com.avaje.ebean.config.ServerConfig;
+import com.avaje.ebean.text.csv.CsvReader;
+import com.avaje.ebean.text.json.JsonContext;
+
+/**
+ * Provides the API for fetching and saving beans to a particular DataSource.
+ *
+ * Registration with the Ebean Singleton:
+ * When a EbeanServer is constructed it can be registered with the Ebean
+ * singleton (see {@link ServerConfig#setRegister(boolean)}). The Ebean
+ * singleton is essentially a map of EbeanServer's that have been registered
+ * with it. The EbeanServer can then be retrieved later via
+ * {@link Ebean#getServer(String)}.
+ *
+ *
+ * The 'default' EbeanServer
+ * One EbeanServer can be designated as the 'default' or 'primary' EbeanServer
+ * (see {@link ServerConfig#setDefaultServer(boolean)}. Many methods on Ebean
+ * such as {@link Ebean#find(Class)} etc are actually just a convenient way to
+ * call methods on the 'default/primary' EbeanServer. This is handy for
+ * applications that use a single DataSource.
+ *
+ * There is one EbeanServer per Database (javax.sql.DataSource). One EbeanServer
+ * is referred to as the 'default' server and that is the one that
+ * Ebean methods such as {@link Ebean#find(Class)} use.
+ *
+ *
+ * Constructing a EbeanServer
+ * EbeanServer's are constructed by the EbeanServerFactory. They can be created
+ * programmatically via {@link EbeanServerFactory#create(ServerConfig)} or they
+ * can be automatically constructed on demand using configuration information in
+ * the ebean.properties file.
+ *
+ *
+ * Example: Get a EbeanServer
+ *
+ *
+ *
+ * // Get access to the Human Resources EbeanServer/Database
+ * EbeanServer hrServer = Ebean.getServer("HR");
+ *
+ *
+ * // fetch contact 3 from the HR database Contact contact =
+ * hrServer.find(Contact.class, new Integer(3));
+ *
+ * contact.setStatus("INACTIVE"); ...
+ *
+ * // save the contact back to the HR database hrServer.save(contact);
+ *
+ *
+ *
+ * EbeanServer has more API than Ebean
+ * EbeanServer provides additional API compared with Ebean. For example it
+ * provides more control over the use of Transactions that is not available in
+ * the Ebean API.
+ *
+ *
+ * External Transactions: If you wanted to use transactions created
+ * externally to eBean then EbeanServer provides additional methods where you
+ * can explicitly pass a transaction (that can be created externally).
+ *
+ *
+ * Bypass ThreadLocal Mechanism: If you want to bypass the built in
+ * ThreadLocal transaction management you can use the createTransaction()
+ * method. Example: a single thread requires more than one transaction.
+ *
+ *
+ * @see Ebean
+ * @see EbeanServerFactory
+ * @see ServerConfig
+ */
+public interface EbeanServer {
+
+ /**
+ * Return the AdminAutofetch which is used to control and configure the
+ * Autofetch service at runtime.
+ */
+ public AdminAutofetch getAdminAutofetch();
+
+ /**
+ * Return the name. This is used with {@link Ebean#getServer(String)} to get a
+ * EbeanServer that was registered with the Ebean singleton.
+ */
+ public String getName();
+
+ /**
+ * Return the ExpressionFactory for this server.
+ */
+ public ExpressionFactory getExpressionFactory();
+
+ /**
+ * Return the BeanState for a given entity bean.
+ *
+ * This will return null if the bean is not an enhanced (or subclassed) entity
+ * bean.
+ *
+ */
+ public BeanState getBeanState(Object bean);
+
+ /**
+ * Return the value of the Id property for a given bean.
+ */
+ public Object getBeanId(Object bean);
+
+ /**
+ * Return a map of the differences between two objects of the same type.
+ *
+ * When null is passed in for b, then the 'OldValues' of a is used for the
+ * difference comparison.
+ *
+ */
+ public Map diff(Object a, Object b);
+
+ /**
+ * Create a new instance of T that is an EntityBean (for subclassing).
+ *
+ * Note that if you are using enhancement (rather than subclassing) then you
+ * do not need to use this method and just new up a bean.
+ *
+ *
+ * Potentially useful when using subclassing and you wish to programmatically
+ * load a entity bean . Otherwise this method is generally not required.
+ *
+ */
+ public T createEntityBean(Class type);
+
+ /**
+ * Create a ObjectInputStream that can be used to deserialise "Proxy" or
+ * "SubClassed" entity beans.
+ *
+ * This is NOT required when entity beans are "Enhanced" (via java agent or
+ * ant task etc).
+ *
+ *
+ * The reason this is needed to deserialise "Proxy" beans is because Ebean
+ * creates the "Proxy/SubClass" classes in a class loader - and generally the
+ * class loader deserialising the inputStream is not aware of these other
+ * classes.
+ *
+ */
+ public ObjectInputStream createProxyObjectInputStream(InputStream is);
+
+ /**
+ * Create a CsvReader for a given beanType.
+ */
+ public CsvReader createCsvReader(Class beanType);
+
+ /**
+ * Create a named query for an entity bean (refer
+ * {@link Ebean#createQuery(Class, String)})
+ *
+ * The query statement will be defined in a deployment orm xml file.
+ *
+ *
+ * @see Ebean#createQuery(Class, String)
+ */
+ public Query createNamedQuery(Class beanType, String namedQuery);
+
+ /**
+ * Create a query using the query language.
+ *
+ * Note that you are allowed to add additional clauses using where() as well
+ * as use fetch() and setOrderBy() after the query has been created.
+ *
+ *
+ * Note that this method signature used to map to named queries and that has
+ * moved to {@link #createNamedQuery(Class, String)}.
+ *
+ *
+ * @param query
+ * the object query
+ */
+ public Query createQuery(Class beanType, String query);
+
+ /**
+ * Create a query for an entity bean (refer {@link Ebean#createQuery(Class)}
+ * ).
+ *
+ * @see Ebean#createQuery(Class)
+ */
+ public Query createQuery(Class beanType);
+
+ /**
+ * Create a query for a type of entity bean (the same as
+ * {@link EbeanServer#createQuery(Class)}).
+ */
+ public Query find(Class beanType);
+
+ /**
+ * Return the next unique identity value for a given bean type.
+ *
+ * This will only work when a IdGenerator is on the bean such as for beans
+ * that use a DB sequence or UUID.
+ *
+ *
+ * For DB's supporting getGeneratedKeys and sequences such as Oracle10 you do
+ * not need to use this method generally. It is made available for more
+ * complex cases where it is useful to get an ID prior to some processing.
+ *
+ */
+ public Object nextId(Class> beanType);
+
+ /**
+ * Create a filter for filtering lists of entity beans.
+ */
+ public Filter filter(Class beanType);
+
+ /**
+ * Sort the list using the sortByClause.
+ *
+ * @see Ebean#sort(List, String)
+ *
+ * @param list
+ * the list of entity beans
+ * @param sortByClause
+ * the properties to sort the list by
+ */
+ public void sort(List list, String sortByClause);
+
+ /**
+ * Create a named update for an entity bean (refer
+ * {@link Ebean#createNamedUpdate(Class, String)}).
+ */
+ public Update createNamedUpdate(Class beanType, String namedUpdate);
+
+ /**
+ * Create a update for an entity bean where you will manually specify the
+ * insert update or delete statement.
+ */
+ public Update createUpdate(Class beanType, String ormUpdate);
+
+ /**
+ * Create a sql query for executing native sql query statements (refer
+ * {@link Ebean#createSqlQuery(String)}).
+ *
+ * @see Ebean#createSqlQuery(String)
+ */
+ public SqlQuery createSqlQuery(String sql);
+
+ /**
+ * Create a named sql query (refer {@link Ebean#createNamedSqlQuery(String)}
+ * ).
+ *
+ * The query statement will be defined in a deployment orm xml file.
+ *
+ *
+ * @see Ebean#createNamedSqlQuery(String)
+ */
+ public SqlQuery createNamedSqlQuery(String namedQuery);
+
+ /**
+ * Create a sql update for executing native dml statements (refer
+ * {@link Ebean#createSqlUpdate(String)}).
+ *
+ * @see Ebean#createSqlUpdate(String)
+ */
+ public SqlUpdate createSqlUpdate(String sql);
+
+ /**
+ * Create a CallableSql to execute a given stored procedure.
+ */
+ public CallableSql createCallableSql(String callableSql);
+
+ /**
+ * Create a named sql update (refer {@link Ebean#createNamedSqlUpdate(String)}
+ * ).
+ *
+ * The statement (an Insert Update or Delete statement) will be defined in a
+ * deployment orm xml file.
+ *
+ *
+ * @see Ebean#createNamedSqlUpdate(String)
+ */
+ public SqlUpdate createNamedSqlUpdate(String namedQuery);
+
+ /**
+ * Create a new transaction that is not held in TransactionThreadLocal.
+ *
+ * You will want to do this if you want multiple Transactions in a single
+ * thread or generally use transactions outside of the TransactionThreadLocal
+ * management.
+ *
+ */
+ public Transaction createTransaction();
+
+ /**
+ * Create a new transaction additionally specifying the isolation level.
+ *
+ * Note that this transaction is NOT stored in a thread local.
+ *
+ */
+ public Transaction createTransaction(TxIsolation isolation);
+
+ /**
+ * Start a new transaction putting it into a ThreadLocal.
+ *
+ * @see Ebean#beginTransaction()
+ */
+ public Transaction beginTransaction();
+
+ /**
+ * Start a transaction additionally specifying the isolation level.
+ */
+ public Transaction beginTransaction(TxIsolation isolation);
+
+ /**
+ * Returns the current transaction or null if there is no current transaction
+ * in scope.
+ */
+ public Transaction currentTransaction();
+
+ /**
+ * Commit the current transaction.
+ *
+ * @see Ebean#commitTransaction()
+ */
+ public void commitTransaction();
+
+ /**
+ * Rollback the current transaction.
+ *
+ * @see Ebean#rollbackTransaction()
+ */
+ public void rollbackTransaction();
+
+ /**
+ * If the current transaction has already been committed do nothing otherwise
+ * rollback the transaction.
+ *
+ * Useful to put in a finally block to ensure the transaction is ended, rather
+ * than a rollbackTransaction() in each catch block.
+ *
+ *
+ * Code example:
+ *
+ *
+ * Ebean.startTransaction(); try { // do some fetching
+ * and or persisting
+ *
+ * // commit at the end Ebean.commitTransaction();
+ *
+ * } finally { // if commit didn't occur then rollback the transaction
+ * Ebean.endTransaction(); }
+ *
+ *
+ *
+ *
+ * @see Ebean#endTransaction()
+ */
+ public void endTransaction();
+
+ /**
+ * Refresh the values of a bean.
+ *
+ * Note that this does not refresh any OneToMany or ManyToMany properties.
+ *
+ *
+ * @see Ebean#refresh(Object)
+ */
+ public void refresh(Object bean);
+
+ /**
+ * Refresh a many property of an entity bean.
+ *
+ * @param bean
+ * the entity bean containing the 'many' property
+ * @param propertyName
+ * the 'many' property to be refreshed
+ *
+ * @see Ebean#refreshMany(Object, String)
+ */
+ public void refreshMany(Object bean, String propertyName);
+
+ /**
+ * Find a bean using its unique id.
+ *
+ * @see Ebean#find(Class, Object)
+ */
+ public T find(Class beanType, Object uid);
+
+ /**
+ * Get a reference Object (see {@link Ebean#getReference(Class, Object)}.
+ *
+ * This will not perform a query against the database.
+ *
+ *
+ * @see Ebean#getReference(Class, Object)
+ */
+ public T getReference(Class beanType, Object uid);
+
+ /**
+ * Return the number of 'top level' or 'root' entities this query should
+ * return.
+ */
+ public int findRowCount(Query query, Transaction transaction);
+
+ /**
+ * Return the Id values of the query as a List.
+ */
+ public List