Compare commits

..
Author SHA1 Message Date
rob bygrave bddfa3bc5f [maven-release-plugin] prepare release ebean-11.17.4 2018-06-14 00:18:00 +12:00
Roland PramlandRob Bygrave 9ad460ecd8 FIX: applied history drops were not recognized (#1422) 2018-06-14 00:07:14 +12:00
rob bygrave 50593a7f9d #1423 - Close resources - InputStream of entity bean properties of File type 2018-06-14 00:04:43 +12:00
rob bygrave c088cfa339 #1421 - Nested programatic transaction using auto close / try with resources ... does not end/rollback the parent/outer transaction 2018-06-13 22:22:29 +12:00
rob bygrave e683e25a95 #1420 - ENH: Add a ServiceLoader mechanism to configure ServerConfig - io.ebean.config.ServerConfigProvider 2018-06-13 21:35:26 +12:00
rob bygrave c7052c5bfa #1420 - ENH: Add a ServiceLoader mechanism to configure ServerConfig - io.ebean.config.ServerConfigProvider 2018-06-13 21:30:41 +12:00
rob bygrave 698ee2198d No effective change - refactor DefaultContainer from iterator to forEach 2018-06-12 22:14:33 +12:00
rob bygrave 91c4eca528 #1419 - Refactor - saveAll() and deleteAll() ... when collections are empty skip transaction init check 2018-06-12 22:08:17 +12:00
rob bygrave 81897bd54d #1418 - Add saveAll() and deleteAll() to io.ebean.BeanRepository 2018-06-12 21:40:39 +12:00
rob bygrave 2427630c07 #1400 - Postgres @History table sys_period empty with multiple updates 2018-06-11 22:10:43 +12:00
rob bygrave 3238c0def4 [maven-release-plugin] prepare for next development iteration 2018-06-11 14:27:53 +12:00
rob bygrave 93f75151f0 [maven-release-plugin] prepare release ebean-11.17.3 2018-06-11 14:27:40 +12:00
rob bygrave 32e9b48359 #1414 - Support dynamic aggregate formula without alias e.g. query.select("date, sum(totalKms), sum(hours)") 2018-06-11 14:13:13 +12:00
rob bygrave 258e29d2f0 #1415 - Add asDto(Class<D> dtoClass) ... to ExpressionList to support fluid style 2018-06-11 13:29:13 +12:00
rob bygrave ec2f1f7243 #1414 - Support dynamic aggregate formula without alias e.g. query.select("date, sum(totalKms), sum(hours)") 2018-06-11 13:20:39 +12:00
Rob Bygrave 5775985468 [maven-release-plugin] prepare for next development iteration 2018-06-11 00:12:45 +12:00
Rob Bygrave 4788dd4ec9 [maven-release-plugin] prepare release ebean-11.17.2 2018-06-11 00:12:30 +12:00
Rob Bygrave ca7e5b79b8 #1412 - Fix Resource leaks for properties & yaml files etc - ManifestReader
Not fixing a leak but just changing to use try with resources
2018-06-11 00:04:58 +12:00
Rob Bygrave 90dfbcd7b3 #1412 - Fix Resource leaks for properties & yaml files etc - DefaultContainer.checkDataSource()
The resource was already being closed properly, changing to use try with resources and note that this now throws PersistenceException if we fail to close() on the connection (which I think is an ok change).
2018-06-11 00:02:14 +12:00
Rob Bygrave 6b39bad4da No effective change - use JdbcClose for DatabasePlatformFactory 2018-06-10 23:55:45 +12:00
Rob Bygrave 25dea0b5b0 #1412 - Fix Resource leaks for properties & yaml files etc - DefaultAutoTuneService 2018-06-10 23:48:49 +12:00
Rob Bygrave 0069d2fabc #1412 - Fix Resource leaks for properties & yaml files etc - ExtraDdlXmlReader 2018-06-10 23:46:55 +12:00
Rob Bygrave 98964638ba #1412 - Fix Resource leaks for properties & yaml files etc - MigrationXmlReader 2018-06-10 23:43:41 +12:00
Rob Bygrave 4573a995f1 #1412 - Fix Resource leaks for properties & yaml files etc
No effective change - reformat only
2018-06-10 23:37:28 +12:00
Rob Bygrave 4bc80cc724 #1412 - Fix Resource leaks for properties & yaml files etc - EbeanVersion 2018-06-10 23:36:12 +12:00
Rob Bygrave a2972abe3e #1412 - Fix Resource leaks for properties & yaml files etc - DdlGenerator 2018-06-10 23:33:21 +12:00
Rob Bygrave 54eaf561f4 #1412 - Fix Resource leaks for properties & yaml files etc - Loader 2018-06-10 23:31:16 +12:00
Rob Bygrave 3134dd3165 No effective change - reformat only 2018-06-10 23:23:48 +12:00
Roland PramlandRob Bygrave 8fc6abd480 IOUtils is no longer used, using java 1.7 StandardCharsets makes EncodeUtil also obsolete (#1410) 2018-06-10 22:30:47 +12:00
rob bygrave f031bb72b6 #1408 - Incorrect SQL when @Aggregation formula matches the property - sum(t0.sum(totalKms)) 2018-06-08 17:38:15 +12:00
rob bygrave 42f0b419e8 #1405 - ENH: add findSingleDecimal() and findSingleLong() alias methods for SqlQuery.findSingleAttribute() ... 2018-06-08 02:19:36 +12:00
rob bygrave 7cbae758fc #1407 - ENH: Add SqlQuery RowMapper and RowConsumer ... for raw JDBC ResultSet mapping and consuming 2018-06-08 02:04:38 +12:00
rob bygrave fb32cfa170 #1406 - ENH: Add SqlQuery.findSingleAttributeList() ... for returning lists of scalar values via SqlQuery 2018-06-07 22:39:19 +12:00
rob bygrave ffa8268125 #1405 - ENH: Add SqlQuery.findSingleAttribute() ... for returning single scalar values via SqlQuery 2018-06-07 22:17:01 +12:00
rob bygrave eba1003c19 #1404 - Query.findCount() with query.setDistinct(true) ... doesn't give count distinct SQL query 2018-06-07 00:32:39 +12:00
rob bygrave eca6383dba [maven-release-plugin] prepare for next development iteration 2018-06-01 15:44:03 +12:00
rob bygrave 624d08f46c [maven-release-plugin] prepare release ebean-11.17.1 2018-06-01 15:43:50 +12:00
rob bygrave 14749daf9b Bump to 11.17.1-SNAPSHOT ... as we added a SPI for io.ebeaninternal.api.SpiLogManager (for capturing the io.ebean.SQL log for testing purposes) 2018-06-01 15:42:24 +12:00
rob bygrave 2817875706 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 14:15:05 +12:00
rob bygrave 3f8d705439 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 14:10:00 +12:00
rob bygrave e9faef2692 #1401 - FOR UPDATE queries inside a transaction are not honored when beans are already cached (in the persistence context) 2018-06-01 11:33:41 +12:00
rob bygrave d459807af7 #1402 - Refactor SQL Logging internals (io.ebean.SQL) such that it is easier to capture for testing/asserts 2018-06-01 09:19:26 +12:00
ooknightandRob Bygrave 10ad9ef096 beautify bindNoLog format (#1399) 2018-05-31 20:23:52 +12:00
rob bygrave 11e3649b85 #1398 - Support EQ / "Equal to" predicate on client side encrypted entity property - @Encrypted(dbEncryption = false) 2018-05-31 16:02:49 +12:00
rob bygrave ddc00fedb6 #1397 - @DbForeignKey(noConstraint = true) is ignored on @ManyToMany ... foreign constraints still added 2018-05-31 10:55:58 +12:00
rob bygrave 0aa9208dcc [maven-release-plugin] prepare for next development iteration 2018-05-30 03:36:38 +12:00
rob bygrave e3b1996dc9 [maven-release-plugin] prepare release ebean-11.16.1 2018-05-30 03:36:24 +12:00
rob bygrave 24ca744e06 Bump version to 11.16.1-SNAPSHOT 2018-05-30 03:35:13 +12:00
rob bygrave 7f9ce5bdd4 #1396 merge() with ManyToMany throwing error Referential integrity constraint violation 2018-05-30 03:32:38 +12:00
rob bygrave 23e5de0819 #1394 #1395 - ENH: Add addBatch() and executeBatch() to SqlUpdate ... alternative to changing transaction batch mode enhancement 2018-05-30 03:32:23 +12:00
rob bygrave c23508819f #1396 merge() with ManyToMany throwing error Referential integrity constraint violation 2018-05-30 03:32:00 +12:00
rob bygrave af030e3da4 #1394 #1395 - ENH: Add addBatch() and executeBatch() to SqlUpdate ... alternative to changing transaction batch mode enhancement
ENH: Add merge() API (using default merge options) to EbeanServer, Ebean and BeanRepository enhancement
2018-05-30 03:31:32 +12:00
rob bygrave d8e67d09a9 #1393 - MergeOptionsBuilder build() return MergeOptions interface type 2018-05-29 15:30:39 +12:00
rob bygrave 66c08e663e #1392 - MergeHandler, change regex Pattern to be static 2018-05-29 15:25:38 +12:00
rob bygrave 9a096e272e #1392 - MergeOptionsBuilder setClientGeneratedIds() ... should not be required if the Id property does not have Identity/Sequence generator assigned 2018-05-29 15:24:32 +12:00
rob bygrave 21e87e12cb Adjust test for #1391 ... such that OrganizationTreeNode has at least one db column as part of insert (for PG) 2018-05-29 13:38:17 +12:00
rob bygrave 7c6d7dd4d0 Merge branch 'FOCONIS-fix-create-reference-bean' 2018-05-29 13:28:32 +12:00
Roland Praml c5329e9db0 reformatting 2018-05-28 17:05:52 +02:00
chrishartlandRoland Praml bccb9f8ddb Fix/create reference with descriptor using inheritance (#13)
* FIXED: Discriminator must be ask for inherited classes

* Fixed: Compile

* Refactoring and Testing
2018-05-28 16:54:53 +02:00
rob bygrave c57af29fca [maven-release-plugin] prepare for next development iteration 2018-05-29 00:44:36 +12:00
rob bygrave 37b6a321ab [maven-release-plugin] prepare release ebean-11.15.12 2018-05-29 00:44:22 +12:00
rob bygrave 896f7d9ea8 No effective change - improve error message when entity not registered to include entity annotation question 2018-05-29 00:38:41 +12:00
rob bygrave 141a0d9358 #1390 - ENH: Support explicit @Id @GeneratedValue ... mapping (where absence of @GeneratedValue means externally supplied id values) 2018-05-29 00:35:31 +12:00
Roland PramlandRob Bygrave 5a7f24da5d FIX: jsonRead of transient maps (#1389)
FIX: jsonRead of transient maps - to skip the read of start_array - Avoid error "Unexpected token start_object - expecting start_array "
2018-05-28 23:06:45 +12:00
rob bygrave 8ea86d6c6d [maven-release-plugin] prepare for next development iteration 2018-05-27 19:19:52 +12:00
169 changed files with 4503 additions and 769 deletions
+2 -2
View File
@@ -9,7 +9,7 @@
<groupId>io.ebean</groupId>
<artifactId>ebean</artifactId>
<version>11.15.11</version>
<version>11.17.4</version>
<packaging>jar</packaging>
<name>ebean</name>
@@ -22,7 +22,7 @@
<scm>
<developerConnection>scm:git:git@github.com:ebean-orm/ebean.git</developerConnection>
<tag>ebean-11.15.11</tag>
<tag>ebean-11.17.4</tag>
</scm>
<profiles>
@@ -2,6 +2,8 @@ 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>
@@ -115,6 +117,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
db().save(bean);
}
/**
* Save all the beans in the collection.
*/
public int saveAll(Collection<T> bean) {
return db().saveAll(bean);
}
/**
* Update this entity.
*
@@ -154,6 +163,13 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
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>
@@ -167,6 +183,30 @@ public abstract class BeanRepository<I, T> extends BeanFinder<I, T> {
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 EbeanServer#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 EbeanServer#merge(Object, MergeOptions)
*/
public void merge(T bean, MergeOptions options) {
db().merge(bean, options);
}
/**
* Refreshes this entity from the database.
*
+9
View File
@@ -638,6 +638,15 @@ public final class Ebean {
serverMgr.getDefaultServer().updateAll(beans);
}
/**
* Merge the bean using the default merge options.
*
* @param bean The bean to merge
*/
public static void merge(Object bean) {
serverMgr.getDefaultServer().merge(bean);
}
/**
* Merge the bean using the given merge options.
*
+7
View File
@@ -1628,6 +1628,13 @@ public interface EbeanServer {
*/
void updateAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException;
/**
* Merge the bean using the default merge options (no paths specified, default delete).
*
* @param bean The bean to merge
*/
void merge(Object bean);
/**
* Merge the bean using the given merge options.
*
+13 -12
View File
@@ -1,11 +1,11 @@
package io.ebean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class to determine the ebean version. (
* @author Roland Praml, FOCONIS AG
@@ -13,31 +13,32 @@ import org.slf4j.LoggerFactory;
*/
public class EbeanVersion {
private EbeanVersion() {
}
private static final Logger logger = LoggerFactory.getLogger(EbeanVersion.class);
private static String version = "unknown";
static {
try {
Properties prop = new Properties();
InputStream in = Ebean.class.getResourceAsStream("/META-INF/maven/io.ebean/ebean/pom.properties");
if (in != null) {
prop.load(in);
in.close();
version = prop.getProperty("version");
try (InputStream in = Ebean.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());
}
}
/**
* Returns the ebean version (read from /META-INF/maven/io.ebean/ebean/pom.properties)
*/
public static String getVersion() {
return version;
}
}
@@ -120,6 +120,14 @@ public interface ExpressionList<T> {
*/
Query<T> asDraft();
/**
* Convert the query to a DTO bean query.
* <p>
* We effectively use the underlying ORM query to build the SQL and then execute
* and map it into DTO beans.
*/
<D> DtoQuery<D> asDto(Class<D> dtoClass);
/**
* Execute using "for update" clause which results in the DB locking the record.
*/
@@ -8,11 +8,20 @@ import java.util.Set;
*/
public class MergeOptionsBuilder {
private static final MOptions DEFAULT_OPTIONS = new MOptions();
private Set<String> paths = new LinkedHashSet<>();
private boolean clientGeneratedIds;
private boolean deletePermanent = true;
private boolean deletePermanent;
/**
* Return the default options.
*/
public static MergeOptions defaultOptions() {
return DEFAULT_OPTIONS;
}
/**
* Add a path that will included in the merge.
@@ -47,7 +56,7 @@ public class MergeOptionsBuilder {
/**
* Build and return the MergeOptions instance.
*/
public MOptions build() {
public MergeOptions build() {
return new MOptions(paths, clientGeneratedIds, deletePermanent);
}
@@ -57,6 +66,12 @@ public class MergeOptionsBuilder {
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;
+40
View File
@@ -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";
*
* Ebean.createSqlQuery(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;
}
+62
View File
@@ -0,0 +1,62 @@
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
*
* //
* // A mapper from ResultSet into our 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 = Ebean.createSqlQuery(sql)
* .setParameter(1, "Rob")
* .findOne(CUSTOMER_MAPPER);
*
*
* }</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;
}
+107
View File
@@ -3,6 +3,7 @@ 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;
@@ -77,12 +78,118 @@ public interface SqlQuery extends Serializable {
@Nullable
SqlRow findOne();
/**
* Execute the query returning a single result using the mapper.
*
* @param mapper Used to map each ResultSet row into the result object.
*/
<T> T findOne(RowMapper<T> mapper);
/**
* Execute the query returning a list using the mapper.
*
* @param mapper Used to map each ResultSet row into the result object.
*/
<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";
*
* Ebean.createSqlQuery(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();
/**
* Execute the query returning a single scalar attribute.
* <pre>@{code
*
* String sql = "select max(unit_price) from o_order_detail where order_qty > ?";
*
* BigDecimal maxPrice = Ebean.createSqlQuery(sql)
* .setParameter(1, 2)
* .findSingleAttribute(BigDecimal.class);
*
* }</pre>
*
* <p>
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
* </p>
*
* @param attributeType The type of the returned value
*/
<T> T findSingleAttribute(Class<T> attributeType);
/**
* Execute the query returning a single BigDecimal value.
* <p>
* This is an alias for <code>findSingleAttribute(BigDecimal.class)</code>
* </p>
*/
BigDecimal findSingleDecimal();
/**
* Execute the query returning a single Long value.
* <p>
* This is an alias for <code>findSingleAttribute(Long.class)</code>
* </p>
*/
Long findSingleLong();
/**
* Execute the query returning a list of scalar attribute values.
*
* <pre>{@code
*
* String sql =
* " select (unit_price * order_qty) " +
* " from o_order_detail " +
* " where unit_price > ? " +
* " order by (unit_price * order_qty) desc";
*
* //
* List<BigDecimal> lineAmounts = Ebean.createSqlQuery(sql)
* .setParameter(1, 3)
* .findSingleAttributeList(BigDecimal.class);
*
* }</pre>
*
* <p>
* The attributeType can be any scalar type that Ebean supports (includes javax time types, Joda types etc).
* </p>
*
* @param attributeType The type of the returned value
*/
<T> List<T> findSingleAttributeList(Class<T> attributeType);
/**
* The same as bind for named parameters.
*/
+48 -7
View File
@@ -19,15 +19,17 @@ package io.ebean;
* <p>
* <pre>{@code
*
* // 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);
* // example that uses 'named' parameters
*
* int modifiedCount = Ebean.execute(update);
* String s = "UPDATE f_topic set post_count = :count where id = :id";
*
* String msg = "There were " + modifiedCount + " rows updated";
* SqlUpdate update = Ebean.createSqlUpdate(s);
* update.setParameter("id", 1);
* update.setParameter("count", 50);
*
* int modifiedCount = update.execute();
*
* String msg = "There were " + modifiedCount + " rows updated";
*
* }</pre>
* <p>
@@ -57,6 +59,33 @@ package io.ebean;
*
* 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 = Ebean.beginTransaction()) {
*
* insert.setNextParameter(10000);
* insert.setNextParameter("hello");
* insert.setNextParameter("rob");
* insert.addBatch();
*
* insert.setNextParameter(10001);
* insert.setNextParameter("goodbye");
* insert.setNextParameter("rob");
* insert.addBatch();
*
* insert.setNextParameter(10002);
* insert.setNextParameter("chow");
* insert.setNextParameter("bob");
* insert.addBatch();
*
* int[] rows = insert.executeBatch();
*
* txn.commit();
* }
*
* }</pre>
*
@@ -83,6 +112,18 @@ public interface SqlUpdate {
*/
int execute();
/**
* 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.
*/
@@ -57,22 +57,24 @@ import java.util.ServiceLoader;
* <pre>{@code
*
* ServerConfig c = new ServerConfig();
* c.setName("db");
*
* // read the ebean.properties and load
* // those settings into this serverConfig object
* c.loadFromProperties();
*
* // add any classes found in the app.data package
* c.addPackage("com.myapp.domain");
*
* // register as the 'Default' server
* c.setDefaultServer(true);
* // explicitly register the entity beans to avoid classpath scanning
* c.addClass(Customer.class);
* c.addClass(User.class);
*
* EbeanServer server = EbeanServerFactory.create(c);
*
* }</pre>
*
* <p>
* Note that ServerConfigProvider provides a standard Java ServiceLoader mechanism that can
* be used to apply configuration to the ServerConfig.
* </p>
*
* @author emcgreal
* @author rbygrave
* @see EbeanServerFactory
@@ -493,6 +495,11 @@ public class ServerConfig {
*/
private List<String> mappingLocations = new ArrayList<>();
/**
* When true we do not need explicit GeneratedValue mapping.
*/
private boolean idGeneratorAutomatic = true;
/**
* Construct a Server Configuration for programmatically creating an EbeanServer.
*/
@@ -1967,7 +1974,7 @@ public class ServerConfig {
* This is the same as serverConfig.getMigrationConfig().setRunMigration(). We have added this method here
* as it is often the only thing we need to configure for migrations.
*/
public void setRunMigration(boolean runMigration){
public void setRunMigration(boolean runMigration) {
migrationConfig.setRunMigration(runMigration);
}
@@ -2772,6 +2779,7 @@ public class ServerConfig {
useJtaTransactionManager = p.getBoolean("useJtaTransactionManager", useJtaTransactionManager);
useJavaxValidationNotNull = p.getBoolean("useJavaxValidationNotNull", useJavaxValidationNotNull);
autoReadOnlyDataSource = p.getBoolean("autoReadOnlyDataSource", autoReadOnlyDataSource);
idGeneratorAutomatic = p.getBoolean("idGeneratorAutomatic", idGeneratorAutomatic);
backgroundExecutorSchedulePoolSize = p.getInt("backgroundExecutorSchedulePoolSize", backgroundExecutorSchedulePoolSize);
backgroundExecutorShutdownSecs = p.getInt("backgroundExecutorShutdownSecs", backgroundExecutorShutdownSecs);
@@ -3109,6 +3117,23 @@ public class ServerConfig {
this.mappingLocations = mappingLocations;
}
/**
* When false we need explicit <code>@GeneratedValue</code> mapping to assign
* Identity or Sequence generated values. When true Id properties are automatically
* assigned Identity or Sequence without the GeneratedValue mapping.
*/
public boolean isIdGeneratorAutomatic() {
return idGeneratorAutomatic;
}
/**
* Set to false such that Id properties require explicit <code>@GeneratedValue</code>
* mapping before they are assigned Identity or Sequence generation based on platform.
*/
public void setIdGeneratorAutomatic(boolean idGeneratorAutomatic) {
this.idGeneratorAutomatic = idGeneratorAutomatic;
}
public enum UuidVersion {
VERSION4,
VERSION1,
@@ -0,0 +1,39 @@
package io.ebean.config;
/**
* Provides a ServiceLoader based mechanism to configure a ServerConfig.
* <p>
* Provide an implementation and register it via the standard Java ServiceLoader mechanism
* via a file at <code>META-INF/services/io.ebean.config.ServerConfigProvider</code>.
* </p>
* <p>
* If you are using a DI container like Spring or Guice you are unlikely to use this but instead use a
* spring specific configuration. When we are not using a DI container we may use this mechanism to
* explicitly register the entity beans and avoid classpath scanning.
* </p>
* <pre>{@code
*
* public class EbeanConfigProvider implements ServerConfigProvider {
*
* @Override
* public void apply(ServerConfig config) {
*
* // register the entity bean classes explicitly
* config.addClass(Customer.class);
* config.addClass(User.class);
* ...
* }
* }
*
* }</pre>
*/
public interface ServerConfigProvider {
/**
* Apply the configuration to the ServerConfig.
* <p>
* Typically we explicitly register entity bean classes and thus avoid classpath scanning.
* </p>
*/
void apply(ServerConfig config);
}
@@ -18,22 +18,22 @@ import java.util.Set;
*/
class LoadContext {
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
private static final Logger log = LoggerFactory.getLogger(LoadContext.class);
/**
* Map we are loading the properties into.
*/
private final Map<String, String> map = new LinkedHashMap<>();
private final Map<String, String> map = new LinkedHashMap<>();
/**
* Names of resources/files that were loaded.
*/
private final Set<String> loadedResources = new LinkedHashSet<>();
private final Set<String> loadedResources = new LinkedHashSet<>();
/**
* Return the input stream (maybe null) for the given source.
*/
InputStream resource(String resourcePath, Loader.Source source) {
InputStream resource(String resourcePath, Loader.Source source) {
InputStream is = null;
if (source == Loader.Source.RESOURCE) {
@@ -46,21 +46,21 @@ class LoadContext {
if (file.exists()) {
try {
is = new FileInputStream(file);
loadedResources.add("file:"+resourcePath);
loadedResources.add("file:" + resourcePath);
} catch (FileNotFoundException e) {
throw new IllegalStateException(e);
}
}
}
return is;
}
return is;
}
private InputStream resourceStream(String resourcePath) {
InputStream is = getClass().getResourceAsStream("/" + resourcePath);
if (is == null) {
// search the module path for top level resource
is = ClassLoader.getSystemResourceAsStream(resourcePath);
is = ClassLoader.getSystemResourceAsStream(resourcePath);
}
return is;
}
@@ -68,30 +68,30 @@ class LoadContext {
/**
* Add a property entry.
*/
void put(String key, String val) {
if (val != null) {
val = val.trim();
void put(String key, String val) {
if (val != null) {
val = val.trim();
}
map.put(key, val);
}
map.put(key, val);
}
/**
* Evaluate all the expressions and return as a Properties object.
*/
Properties eval() {
Properties eval() {
log.info("loaded properties from {}", loadedResources);
log.info("loaded properties from {}", loadedResources);
Properties properties = new Properties();
Properties properties = new Properties();
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = PropertyEval.eval(entry.getValue());
properties.setProperty(key, value);
}
for (Map.Entry<String, String> entry : map.entrySet()) {
String key = entry.getKey();
String value = PropertyEval.eval(entry.getValue());
properties.setProperty(key, value);
}
return properties;
}
return properties;
}
/**
* Read the special properties that can point to an external properties source.
@@ -16,10 +16,10 @@ import java.util.Properties;
*/
class Loader {
private static final Logger log = LoggerFactory.getLogger(Loader.class);
private static final Logger log = LoggerFactory.getLogger(Loader.class);
enum Source {
RESOURCE,
enum Source {
RESOURCE,
FILE
}
@@ -27,7 +27,7 @@ class Loader {
private YamlLoader yamlLoader;
Loader() {
Loader() {
String skipYaml = System.getProperty("ebeanSkipYaml");
if (!"true".equals(skipYaml)) {
try {
@@ -44,19 +44,19 @@ class Loader {
/**
* Load the configuration with the expected ordering.
*/
void load() {
void load() {
loadMain(Source.RESOURCE);
// external file configuration overrides the resources configuration
loadMain(Source.FILE);
loadViaSystemProperty();
loadViaIndirection();
loadViaSystemProperty();
loadViaIndirection();
// test configuration (if found) overrides main configuration
// test configuration (if found) overrides main configuration
// we should only find these resources when running tests
loadTest();
}
loadTest();
}
/**
* Load test configuration.
@@ -89,7 +89,7 @@ class Loader {
}
private void loadViaSystemProperty() {
String fileName = System.getenv("EBEAN_PROPS_FILE");
String fileName = System.getenv("EBEAN_PROPS_FILE");
if (fileName == null) {
fileName = System.getProperty("ebean.props.file");
if (fileName != null) {
@@ -112,59 +112,66 @@ class Loader {
* Evaluate all the configuration entries and return as properties.
*/
Properties eval() {
return loadContext.eval();
}
return loadContext.eval();
}
void loadYaml(String resourcePath, Source source) {
if (yamlLoader != null) {
try {
yamlLoader.load(resource(resourcePath, source));
void loadYaml(String resourcePath, Source source) {
if (yamlLoader != null) {
try {
try (InputStream is = resource(resourcePath, source)) {
yamlLoader.load(is);
}
} catch (Exception e) {
log.warn("Failed to read yml from:" + resourcePath, e);
}
}
}
}
}
void loadProperties(String resourcePath, Source source) {
InputStream is = resource(resourcePath, source);
if (is != null) {
loadProperties(is);
}
}
void loadProperties(String resourcePath, Source source) {
try {
try (InputStream is = resource(resourcePath, source)) {
if (is != null) {
loadProperties(is);
}
}
} catch (Exception e) {
log.warn("Failed to read properties from:" + resourcePath, e);
}
}
private InputStream resource(String resourcePath, Source source) {
return loadContext.resource(resourcePath, source);
}
private InputStream resource(String resourcePath, Source source) {
return loadContext.resource(resourcePath, source);
}
private void loadProperties(InputStream is) {
private void loadProperties(InputStream is) {
if (is != null) {
try {
Properties properties = new Properties();
properties.load(is);
put(properties);
} catch (IOException e) {
throw new RuntimeException("Failed to load properties?", e);
} finally {
close(is);
}
}
}
if (is != null) {
try {
Properties properties = new Properties();
properties.load(is);
put(properties);
} catch (IOException e) {
throw new RuntimeException("Failed to load properties?", e);
} finally {
close(is);
}
}
}
private void put(Properties properties) {
Enumeration<?> enumeration = properties.propertyNames();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
String property = properties.getProperty(key);
loadContext.put(key, property);
}
}
private void put(Properties properties) {
Enumeration<?> enumeration = properties.propertyNames();
while (enumeration.hasMoreElements()) {
String key = (String) enumeration.nextElement();
String property = properties.getProperty(key);
loadContext.put(key, property);
}
}
private void close(InputStream is) {
try {
is.close();
} catch (IOException e) {
log.warn("Error closing input stream for properties", e);
}
}
private void close(InputStream is) {
try {
is.close();
} catch (IOException e) {
log.warn("Error closing input stream for properties", e);
}
}
}
@@ -53,6 +53,10 @@ public class ScopeTrans {
*/
private boolean rolledBack;
/**
* Flag set when nested commit has occurred.
*/
private boolean nestedCommit;
public ScopeTrans(boolean rollbackOnChecked, boolean created, SpiTransaction transaction, TxScope txScope) {
@@ -113,7 +117,6 @@ public class ScopeTrans {
complete();
}
/**
* Complete the transaction programmatically. Try to commit.
*/
@@ -124,7 +127,7 @@ public class ScopeTrans {
}
public void end() {
if (created) {
if (created || !nestedCommit) {
transaction.end();
}
}
@@ -133,6 +136,7 @@ public class ScopeTrans {
if (created) {
transaction.commit();
} else {
nestedCommit = true;
transaction.setBatchFlushOnQuery(restoreBatchFlushOnQuery);
if (restoreBatch != null) {
transaction.setBatch(restoreBatch);
@@ -4,6 +4,8 @@ import io.ebean.DtoQuery;
import io.ebean.EbeanServer;
import io.ebean.PersistenceContextScope;
import io.ebean.Query;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.Transaction;
import io.ebean.TxScope;
import io.ebean.bean.BeanCollectionLoader;
@@ -31,6 +33,11 @@ import java.util.function.Predicate;
*/
public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionLoader {
/**
* Return the log manager.
*/
SpiLogManager log();
/**
* Return the server extended Json context.
*/
@@ -229,6 +236,31 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
*/
void scopedTransactionExit(Object returnOrThrowable, int opCode);
/**
* SqlQuery find single attribute.
*/
<T> T findSingleAttribute(SpiSqlQuery query, Class<T> cls);
/**
* SqlQuery find single attribute list.
*/
<T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls);
/**
* SqlQuery find one with mapper.
*/
<T> T findOneMapper(SpiSqlQuery query, RowMapper<T> mapper);
/**
* SqlQuery find list with mapper.
*/
<T> List<T> findListMapper(SpiSqlQuery query, RowMapper<T> mapper);
/**
* SqlQuery find each with consumer.
*/
void findEachRow(SpiSqlQuery query, RowConsumer consumer);
/**
* DTO findList query.
*/
@@ -268,4 +300,15 @@ public interface SpiEbeanServer extends EbeanServer, BeanLoader, BeanCollectionL
* Return true if a row for the bean type and id exists.
*/
boolean exists(Class<?> beanType, Object beanId, Transaction transaction);
/**
* Add to JDBC batch for later execution.
*/
void addBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
/**
* Execute the batched statement.
*/
int[] executeBatch(SpiSqlUpdate defaultSqlUpdate, SpiTransaction transaction);
}
@@ -0,0 +1,27 @@
package io.ebeaninternal.api;
/**
* Log manager for SQL, TXN and Summary logging.
* <p>
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
* the logged SQL during testing such that we can assert against the executed sql if desired.
* </p>
*/
public interface SpiLogManager {
/**
* Return the SQL logger.
*/
SpiLogger sql();
/**
* Return the TXN logger.
*/
SpiLogger txn();
/**
* Return the Summary logger.
*/
SpiLogger sum();
}
@@ -0,0 +1,31 @@
package io.ebeaninternal.api;
/**
* Logger for SQL, TXN and Summary logging.
* <p>
* In general at runtime this uses SLF4J Logger but this abstraction allows us to capture
* the logged SQL during testing such that we can assert against the executed sql if desired.
* </p>
*/
public interface SpiLogger {
/**
* Is debug logging enabled.
*/
boolean isDebug();
/**
* Is trace logging enabled.
*/
boolean isTrace();
/**
* Log a debug level message.
*/
void debug(String msg);
/**
* Log a trace level message.
*/
void trace(String msg);
}
@@ -0,0 +1,12 @@
package io.ebeaninternal.api;
/**
* API for Plugins to define the logger for io.ebean.SQL, io.ebean.TXN and io.ebean.SUM.
*/
public interface SpiLoggerFactory {
/**
* Create the logger given the name.
*/
SpiLogger create(String name);
}
@@ -644,9 +644,9 @@ public interface SpiQuery<T> extends Query<T>, TxnProfileEventCodes {
boolean isBeanCachePut();
/**
* Return true if the bean cache is being explicitly loaded via RECACHE mode.
* Return true if we must hit the DB (Cache reload or select for update).
*/
boolean isBeanCacheReload();
boolean isForceHitDatabase();
/**
* Return the cache mode for using the bean cache (Get and Put).
@@ -2,10 +2,10 @@ package io.ebeaninternal.dbmigration;
import io.ebean.Transaction;
import io.ebean.config.ServerConfig;
import io.ebeaninternal.dbmigration.model.CurrentModel;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import io.ebean.migration.ddl.DdlRunner;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.dbmigration.model.CurrentModel;
import io.ebeaninternal.extraddl.model.ExtraDdlXmlReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -173,10 +173,11 @@ public class DdlGenerator {
protected void runResourceScript(String sqlScript) throws IOException {
if (sqlScript != null) {
InputStream is = getClassLoader().getResourceAsStream(sqlScript);
if (is != null) {
String content = readContent(new InputStreamReader(is));
runScript(false, content, sqlScript);
try (InputStream is = getClassLoader().getResourceAsStream(sqlScript)) {
if (is != null) {
String content = readContent(new InputStreamReader(is));
runScript(false, content, sqlScript);
}
}
}
}
@@ -79,13 +79,21 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
protected void createOrReplaceFunction(DdlBuffer apply, String procedureName, String historyTable, List<String> includedColumns) throws IOException {
apply
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine()
.append("begin").newLine();
.append("create or replace function ").append(procedureName).append("() returns trigger as $$").newLine();
apply.append("declare").newLine()
.append(" lowerTs timestamptz;").newLine()
.append(" upperTs timestamptz;").newLine();
apply.append("begin").newLine()
.append(" lowerTs = lower(OLD.sys_period);").newLine()
.append(" upperTs = greatest(lowerTs + '1 microsecond',current_timestamp);").newLine();
apply
.append(" if (TG_OP = 'UPDATE') then").newLine();
appendInsertIntoHistory(apply, historyTable, includedColumns);
apply
.append(" NEW.").append(sysPeriod).append(" = tstzrange(").append(currentTimestamp).append(",null);").newLine()
.append(" NEW.").append(sysPeriod).append(" = tstzrange(upperTs,null);").newLine()
.append(" return new;").newLine();
apply
.append(" elsif (TG_OP = 'DELETE') then").newLine();
@@ -124,7 +132,7 @@ public class PostgresHistoryDdl extends DbTriggerBasedHistoryDdl {
buffer.append(" insert into ").append(historyTable).append(" (").append(sysPeriod).append(",");
appendColumnNames(buffer, columns, "");
buffer.append(") values (tstzrange(lower(OLD.").append(sysPeriod).append("), ").append(currentTimestamp).append("), ");
buffer.append(") values (tstzrange(lowerTs,upperTs), ");
appendColumnNames(buffer, columns, "OLD.");
buffer.append(");").newLine();
}
@@ -24,12 +24,14 @@ public class MigrationXmlReader {
*/
public static Migration read(String resourcePath) {
InputStream is = MigrationXmlReader.class.getResourceAsStream(resourcePath);
if (is == null) {
throw new IllegalArgumentException("No resource found for path [" + resourcePath + "]");
try (InputStream is = MigrationXmlReader.class.getResourceAsStream(resourcePath)) {
if (is == null) {
throw new IllegalArgumentException("No resource found for path [" + resourcePath + "]");
}
return read(is);
} catch (IOException e) {
throw new IllegalArgumentException("Failed to auto close resource " + resourcePath, e);
}
return read(is);
}
/**
@@ -3,6 +3,7 @@ package io.ebeaninternal.dbmigration.model;
import io.ebeaninternal.dbmigration.migration.ChangeSet;
import io.ebeaninternal.dbmigration.migration.ChangeSetType;
import io.ebeaninternal.dbmigration.migration.DropColumn;
import io.ebeaninternal.dbmigration.migration.DropHistoryTable;
import io.ebeaninternal.dbmigration.migration.DropTable;
import io.ebeaninternal.dbmigration.migration.Migration;
@@ -207,10 +208,26 @@ public class PendingDrops {
} else if (pendingDrop instanceof DropTable && dropTableIn((DropTable) pendingDrop, appliedDrops)) {
iterator.remove();
} else if (pendingDrop instanceof DropHistoryTable && dropHistoryTableIn((DropHistoryTable) pendingDrop, appliedDrops)) {
iterator.remove();
}
}
}
/**
* Return true if the pendingDrop is contained in the appliedDrops.
*/
private boolean dropHistoryTableIn(DropHistoryTable pendingDrop, ChangeSet appliedDrops) {
for (Object o : appliedDrops.getChangeSetChildren()) {
if (o instanceof DropHistoryTable && sameHistoryTable(pendingDrop, (DropHistoryTable) o)) {
return true;
}
}
return false;
}
/**
* Return true if the pendingDrop is contained in the appliedDrops.
*/
@@ -235,6 +252,13 @@ public class PendingDrops {
return false;
}
/**
* Return true if the DropHistoryTable match by base-table name.
*/
private boolean sameHistoryTable(DropHistoryTable pendingDrop, DropHistoryTable o) {
return pendingDrop.getBaseTable().equals(o.getBaseTable());
}
/**
* Return true if the DropTable match by table name.
*/
@@ -5,6 +5,7 @@ import io.ebeaninternal.dbmigration.model.MTable;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.deploy.TableJoin;
import io.ebeaninternal.server.deploy.TableJoinColumn;
@@ -48,10 +49,12 @@ class ModelBuildIntersectionTable {
private void buildFkConstraints() {
ctx.fkeyBuilder(intersectionTable)
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
PropertyForeignKey foreignKey = manyProp.getForeignKey();
if (foreignKey == null || !foreignKey.isNoConstraint()) {
ctx.fkeyBuilder(intersectionTable)
.addForeignKey(manyProp.getBeanDescriptor(), intersectionTableJoin, true)
.addForeignKey(manyProp.getTargetDescriptor(), tableJoin, false);
}
intersectionTable.checkDuplicateForeignKeys();
}
@@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.IOException;
import java.io.InputStream;
/**
@@ -87,12 +88,15 @@ public class ExtraDdlXmlReader {
*/
public static ExtraDdl read(String resourcePath) {
InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath);
if (is == null) {
// we expect this and check for null
return null;
try (InputStream is = ExtraDdlXmlReader.class.getResourceAsStream(resourcePath)) {
if (is == null) {
// we expect this and check for null
return null;
}
return read(is);
} catch (IOException e) {
throw new IllegalStateException("Error on auto close of " + resourcePath, e);
}
return read(is);
}
/**
@@ -144,7 +144,7 @@ public class BaseQueryTuner {
default:
// not using autoTune when explicitly loading the l2 bean cache
// or when using Versions query
return !query.isBeanCacheReload() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
return !query.isForceHitDatabase() && SpiQuery.TemporalMode.VERSIONS != query.getTemporalMode();
}
}
@@ -12,6 +12,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.TimeUnit;
@@ -106,11 +107,14 @@ public class DefaultAutoTuneService implements AutoTuneService {
loadAutoTuneProfiling(AutoTuneXmlReader.read(file));
} else {
// look for autotune as a resource
InputStream stream = getClass().getResourceAsStream("/" + tuningFile);
if (stream != null) {
loadAutoTuneProfiling(AutoTuneXmlReader.read(stream));
} else {
logger.warn("AutoTune file {} not found - no initial automatic query tuning", tuningFile);
try (InputStream stream = getClass().getResourceAsStream("/" + tuningFile)) {
if (stream != null) {
loadAutoTuneProfiling(AutoTuneXmlReader.read(stream));
} else {
logger.warn("AutoTune file {} not found - no initial automatic query tuning", tuningFile);
}
} catch (IOException e) {
throw new IllegalStateException("Error on auto close of " + tuningFile, e);
}
}
}
@@ -170,11 +170,7 @@ public abstract class AbstractSqlQueryRequest {
}
if (isLogSql()) {
String logSql = TrimLogSql.trim(sql);
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
trans.logSql(logSql);
trans.logSql(Str.add(TrimLogSql.trim(sql), "; --bind(", bindLog, ")"));
}
setResultSet(pstmt.executeQuery(), null);
@@ -14,7 +14,7 @@ import io.ebean.config.dbplatform.sqlanywhere.SqlAnywherePlatform;
import io.ebean.config.dbplatform.sqlite.SQLitePlatform;
import io.ebean.config.dbplatform.sqlserver.SqlServer16Platform;
import io.ebean.config.dbplatform.sqlserver.SqlServer17Platform;
import io.ebean.migration.util.JdbcClose;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.dbmigration.DbOffline;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -126,13 +126,7 @@ public class DatabasePlatformFactory {
throw new PersistenceException(ex);
} finally {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException ex) {
logger.error(null, ex);
}
JdbcClose.close(connection);
}
}
@@ -6,11 +6,11 @@ import io.ebean.cache.ServerCacheOptions;
import io.ebean.cache.ServerCachePlugin;
import io.ebean.config.ContainerConfig;
import io.ebean.config.ServerConfig;
import io.ebean.config.ServerConfigProvider;
import io.ebean.config.TenantMode;
import io.ebean.config.UnderscoreNamingConvention;
import io.ebean.config.dbplatform.DatabasePlatform;
import io.ebean.config.dbplatform.h2.H2Platform;
import io.ebean.config.properties.PropertiesLoader;
import io.ebean.service.SpiContainer;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiContainerBootup;
@@ -63,9 +63,8 @@ public class DefaultContainer implements SpiContainer {
}
private void invokeBootupPlugin() {
Iterator<SpiContainerBootup> it = ServiceLoader.load(SpiContainerBootup.class).iterator();
while (it.hasNext()) {
it.next().bootup();
for (SpiContainerBootup boot : ServiceLoader.load(SpiContainerBootup.class)) {
boot.bootup();
}
}
@@ -104,8 +103,13 @@ public class DefaultContainer implements SpiContainer {
public SpiEbeanServer createServer(ServerConfig serverConfig) {
synchronized (this) {
setNamingConvention(serverConfig);
if (serverConfig.isDefaultServer()) {
for (ServerConfigProvider configProvider : ServiceLoader.load(ServerConfigProvider.class)) {
configProvider.apply(serverConfig);
}
}
setNamingConvention(serverConfig);
BootupClasses bootupClasses = getBootupClasses(serverConfig);
boolean online = true;
@@ -374,25 +378,14 @@ public class DefaultContainer implements SpiContainer {
throw new RuntimeException("DataSource not set?");
}
Connection c = null;
try {
c = serverConfig.getDataSource().getConnection();
if (!serverConfig.isAutoCommitMode() && c.getAutoCommit()) {
try (Connection connection = serverConfig.getDataSource().getConnection()) {
if (!serverConfig.isAutoCommitMode() && connection.getAutoCommit()) {
logger.warn("DataSource [{}] has autoCommit defaulting to true!", serverConfig.getName());
}
return true;
} catch (SQLException ex) {
throw new PersistenceException(ex);
} finally {
if (c != null) {
try {
c.close();
} catch (SQLException ex) {
logger.error("Error closing connection", ex);
}
}
}
}
@@ -13,11 +13,14 @@ import io.ebean.FutureIds;
import io.ebean.FutureList;
import io.ebean.FutureRowCount;
import io.ebean.MergeOptions;
import io.ebean.MergeOptionsBuilder;
import io.ebean.PagedList;
import io.ebean.PersistenceContextScope;
import io.ebean.ProfileLocation;
import io.ebean.Query;
import io.ebean.QueryIterator;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.SqlUpdate;
@@ -63,8 +66,11 @@ import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiDtoQuery;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.SpiQuery.Type;
import io.ebeaninternal.api.SpiSqlQuery;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionManager;
import io.ebeaninternal.api.TransactionEventTable;
@@ -189,6 +195,8 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
private final CurrentTenantProvider currentTenantProvider;
private final SpiLogManager logManager;
/**
* The default PersistenceContextScope used if it is not explicitly set on a query.
*/
@@ -229,6 +237,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
public DefaultServer(InternalConfiguration config, ServerCacheManager cache) {
this.logManager = config.getLogManager();
this.dtoBeanManager = config.getDtoBeanManager();
this.serverConfig = config.getServerConfig();
this.objectGraphStats = new ConcurrentHashMap<>();
@@ -315,6 +324,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
@Override
public SpiLogManager log() {
return logManager;
}
@Override
public boolean isCollectQueryOrigins() {
return collectQueryOrigins;
@@ -919,6 +933,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return new DefaultUpdateQuery<>(createQuery(beanType));
}
@Override
public void merge(Object bean) {
merge(bean, MergeOptionsBuilder.defaultOptions(), null);
}
@Override
public void merge(Object bean, MergeOptions options) {
merge(bean, options, null);
@@ -1159,7 +1178,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
SpiQuery<T> spiQuery = (SpiQuery<T>) query;
spiQuery.setType(Type.BEAN);
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isBeanCacheReload()) {
if (SpiQuery.Mode.NORMAL == spiQuery.getMode() && !spiQuery.isForceHitDatabase()) {
// See if we can skip doing the fetch completely by getting the bean from the
// persistence context or the bean cache
T bean = findIdCheckPersistenceContextAndCache(t, spiQuery, spiQuery.getId());
@@ -1278,8 +1297,11 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public <T> int findCount(Query<T> query, Transaction t) {
SpiQuery<T> copy = ((SpiQuery<T>) query).copy();
return findCountWithCopy(copy, t);
SpiQuery<T> spiQuery = ((SpiQuery<T>) query);
if (!spiQuery.isDistinct()) {
spiQuery = spiQuery.copy();
}
return findCountWithCopy(spiQuery, t);
}
@Override
@@ -1566,6 +1588,41 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
}
}
private <P> P executeSqlQuery(Function<RelationalQueryRequest, P> fun, SpiSqlQuery query) {
RelationalQueryRequest request = new RelationalQueryRequest(this, relationalQueryEngine, query, null);
try {
request.initTransIfRequired();
return fun.apply(request);
} finally {
request.endTransIfRequired();
}
}
@Override
public void findEachRow(SpiSqlQuery query, RowConsumer consumer) {
executeSqlQuery((req) -> req.findEachRow(consumer), query);
}
@Override
public <T> List<T> findListMapper(SpiSqlQuery query, RowMapper<T> mapper) {
return executeSqlQuery((req) -> req.findListMapper(mapper), query);
}
@Override
public <T> T findOneMapper(SpiSqlQuery query, RowMapper<T> mapper) {
return executeSqlQuery((req) -> req.findOneMapper(mapper), query);
}
@Override
public <T> List<T> findSingleAttributeList(SpiSqlQuery query, Class<T> cls) {
return executeSqlQuery((req) -> req.findSingleAttributeList(cls), query);
}
@Override
public <T> T findSingleAttribute(SpiSqlQuery query, Class<T> cls) {
return executeSqlQuery((req) -> req.findSingleAttribute(cls), query);
}
@Override
public <T> void findDtoEach(SpiDtoQuery<T> query, Consumer<T> consumer) {
DtoQueryRequest<T> request = new DtoQueryRequest<>(this, dtoQueryEngine, query);
@@ -1677,7 +1734,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (beans == null || beans.isEmpty()) {
return;
}
executeInTrans((txn) -> {
for (Object bean : beans) {
update(checkEntityBean(bean), txn);
@@ -1719,7 +1775,6 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
if (beans == null || beans.isEmpty()) {
return;
}
executeInTrans((txn) -> {
for (Object bean : beans) {
persister.insert(checkEntityBean(bean), txn);
@@ -1788,24 +1843,27 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public int saveAll(Collection<?> beans, Transaction transaction) throws OptimisticLockException {
return saveAllInternal(beans.iterator(), transaction);
return saveAllInternal(beans, transaction);
}
@Override
public int saveAll(Collection<?> beans) throws OptimisticLockException {
return saveAllInternal(beans.iterator(), null);
return saveAllInternal(beans, null);
}
/**
* Save all beans in the iterator with an explicit transaction.
*/
private int saveAllInternal(Iterator<?> it, Transaction transaction) {
private int saveAllInternal(Collection<?> beans, Transaction transaction) {
if (beans == null || beans.isEmpty()) {
return 0;
}
return executeInTrans((txn) -> {
txn.checkBatchEscalationOnCollection();
int saveCount = 0;
while (it.hasNext()) {
persister.save(checkEntityBean(it.next()), txn);
for (Object bean : beans) {
persister.save(checkEntityBean(bean), txn);
saveCount++;
}
@@ -1893,12 +1951,12 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
@Override
public int deleteAllPermanent(Collection<?> beans) {
return deleteAllInternal(beans.iterator(), null, true);
return deleteAllInternal(beans, null, true);
}
@Override
public int deleteAllPermanent(Collection<?> beans, Transaction t) {
return deleteAllInternal(beans.iterator(), t, true);
return deleteAllInternal(beans, t, true);
}
/**
@@ -1906,7 +1964,7 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public int deleteAll(Collection<?> beans) {
return deleteAllInternal(beans.iterator(), null, false);
return deleteAllInternal(beans, null, false);
}
/**
@@ -1914,21 +1972,23 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
*/
@Override
public int deleteAll(Collection<?> beans, Transaction t) {
return deleteAllInternal(beans.iterator(), t, false);
return deleteAllInternal(beans, t, false);
}
/**
* Delete all the beans in the iterator with an explicit transaction.
*/
private int deleteAllInternal(Iterator<?> it, Transaction transaction, boolean permanent) {
private int deleteAllInternal(Collection<?> beans, Transaction transaction, boolean permanent) {
if (beans == null || beans.isEmpty()) {
return 0;
}
return executeInTrans((txn) -> {
txn.checkBatchEscalationOnCollection();
int deleteCount = 0;
while (it.hasNext()) {
EntityBean bean = checkEntityBean(it.next());
persister.delete(bean, txn, permanent);
for (Object bean : beans) {
persister.delete(checkEntityBean(bean), txn, permanent);
deleteCount++;
}
@@ -1961,6 +2021,16 @@ public final class DefaultServer implements SpiServer, SpiEbeanServer {
return persister.executeSqlUpdate(updSql, t);
}
@Override
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
persister.addBatch(sqlUpdate, transaction);
}
@Override
public int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
return persister.executeBatch(sqlUpdate, transaction);
}
/**
* Execute the updateSql.
*/
@@ -1,11 +1,12 @@
package io.ebeaninternal.server.core;
import io.ebean.Ebean;
import io.ebean.EbeanServer;
import io.ebean.SqlUpdate;
import io.ebean.Update;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import java.io.Serializable;
@@ -26,7 +27,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
private static final long serialVersionUID = -6493829438421253102L;
private transient final EbeanServer server;
private transient final SpiEbeanServer server;
/**
* The parameters used to bind to the sql.
@@ -69,6 +70,16 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
private Object generatedKey;
/**
* Set when batching explicitly used.
*/
private boolean batched;
/**
* Transaction used for addBatch() executeBatch() processing.
*/
private transient SpiTransaction transaction;
/**
* Create with server sql and bindParams object.
* <p>
@@ -76,7 +87,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
* same time.
* </p>
*/
public DefaultSqlUpdate(EbeanServer server, String sql, BindParams bindParams) {
public DefaultSqlUpdate(SpiEbeanServer server, String sql, BindParams bindParams) {
this.server = server;
this.sql = sql;
this.bindParams = bindParams;
@@ -86,7 +97,7 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
* Create with a specific server. This means you can use the
* SqlUpdate.execute() method.
*/
public DefaultSqlUpdate(EbeanServer server, String sql) {
public DefaultSqlUpdate(SpiEbeanServer server, String sql) {
this(server, sql, new BindParams());
}
@@ -111,6 +122,10 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
@Override
public int execute() {
if (server != null) {
if (batched) {
server.executeBatch(this, transaction);
return -1;
}
return server.execute(this);
} else {
// Hopefully this doesn't catch anyone out...
@@ -118,6 +133,34 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
}
}
@Override
public int[] executeBatch() {
if (server == null) {
throw new IllegalStateException("No EbeanServer set?");
}
if (!batched) {
throw new IllegalStateException("No prior addBatch() called?");
}
return server.executeBatch(this, transaction);
}
@Override
public void addBatch() {
if (server == null) {
throw new IllegalStateException("No EbeanServer set?");
}
if (transaction == null) {
transaction = server.currentServerTransaction();
if (transaction == null) {
throw new IllegalStateException("No current transaction? Must have a transaction to use addBatch()");
}
}
batched = true;
server.addBatch(this, transaction);
}
@Override
public Object getGeneratedKey() {
return generatedKey;
@@ -20,6 +20,9 @@ import io.ebean.plugin.SpiServer;
import io.ebeaninternal.api.SpiBackgroundExecutor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiJsonContext;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiLoggerFactory;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.dbmigration.DbOffline;
import io.ebeaninternal.server.autotune.AutoTuneService;
@@ -44,6 +47,8 @@ import io.ebeaninternal.server.dto.DtoBeanManager;
import io.ebeaninternal.server.expression.DefaultExpressionFactory;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.expression.platform.DbExpressionHandlerFactory;
import io.ebeaninternal.server.logger.DLogManager;
import io.ebeaninternal.server.logger.DLoggerFactory;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.persist.DefaultPersister;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
@@ -134,16 +139,19 @@ public class InternalConfiguration {
private final MultiValueBind multiValueBind;
private final SpiLogManager logManager;
public InternalConfiguration(ClusterManager clusterManager,
SpiCacheManager cacheManager, SpiBackgroundExecutor backgroundExecutor,
ServerConfig serverConfig, BootupClasses bootupClasses) {
this.serverConfig = serverConfig;
this.logManager = initLogManager();
this.docStoreFactory = initDocStoreFactory(serverConfig.service(DocStoreFactory.class));
this.jsonFactory = serverConfig.getJsonFactory();
this.clusterManager = clusterManager;
this.backgroundExecutor = backgroundExecutor;
this.cacheManager = cacheManager;
this.serverConfig = serverConfig;
this.bootupClasses = bootupClasses;
this.databasePlatform = serverConfig.getDatabasePlatform();
@@ -169,6 +177,20 @@ public class InternalConfiguration {
this.cQueryEngine = new CQueryEngine(serverConfig, databasePlatform, binder, asOfTableMapping, draftTableMap);
}
private SpiLogManager initLogManager() {
// allow plugin - i.e. capture executed SQL for testing/asserts
SpiLoggerFactory loggerFactory = serverConfig.service(SpiLoggerFactory.class);
if (loggerFactory == null) {
loggerFactory = new DLoggerFactory();
}
SpiLogger sql = loggerFactory.create("io.ebean.SQL");
SpiLogger sum = loggerFactory.create("io.ebean.SUM");
SpiLogger txn = loggerFactory.create("io.ebean.TXN");
return new DLogManager(sql, sum, txn);
}
/**
* Create and return the ExpressionFactory based on configuration and database platform.
*/
@@ -262,9 +284,9 @@ public class InternalConfiguration {
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
if (historySupport == null) {
return new Binder(typeManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
return new Binder(typeManager, logManager, 0, false, jsonHandler, dataTimeZone, multiValueBind);
}
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
return new Binder(typeManager, logManager, historySupport.getBindCount(), historySupport.isStandardsBased(), jsonHandler, dataTimeZone, multiValueBind);
}
/**
@@ -378,7 +400,7 @@ public class InternalConfiguration {
TransactionManagerOptions options =
new TransactionManagerOptions(notifyL2CacheInForeground, serverConfig, scopeManager, clusterManager, backgroundExecutor,
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler());
indexUpdateProcessor, beanDescriptorManager, dataSource(), profileHandler(), logManager);
if (serverConfig.isExplicitTransactionBeginMode()) {
return new ExplicitTransactionManager(options);
@@ -511,4 +533,8 @@ public class InternalConfiguration {
public DtoBeanManager getDtoBeanManager() {
return dtoBeanManager;
}
public SpiLogManager getLogManager() {
return logManager;
}
}
@@ -81,6 +81,8 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
private BeanPropertyAssocMany<?> manyProperty;
private boolean inlineCountDistinct;
/**
* Create the InternalQueryRequest.
*/
@@ -742,4 +744,12 @@ public final class OrmQueryRequest<T> extends BeanRequest implements SpiOrmQuery
public void slowQueryCheck(long executionTimeMicros, int rowCount) {
ebeanServer.slowQueryCheck(executionTimeMicros, rowCount, query);
}
public void setInlineCountDistinct() {
inlineCountDistinct = true;
}
public boolean isInlineCountDistinct() {
return inlineCountDistinct;
}
}
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.core;
import io.ebean.SqlUpdate;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
@@ -27,15 +26,17 @@ public final class PersistRequestUpdateSql extends PersistRequest {
private String description;
private boolean addBatch;
/**
* Create.
*/
public PersistRequestUpdateSql(SpiEbeanServer server, SqlUpdate sqlUpdate,
public PersistRequestUpdateSql(SpiEbeanServer server, SpiSqlUpdate sqlUpdate,
SpiTransaction t, PersistExecute persistExecute) {
super(server, t, persistExecute, sqlUpdate.getLabel());
this.type = Type.UPDATESQL;
this.updateSql = (SpiSqlUpdate) sqlUpdate;
this.updateSql = sqlUpdate;
updateSql.reset();
}
@@ -44,11 +45,24 @@ public final class PersistRequestUpdateSql extends PersistRequest {
profileBase(EVT_UPDATESQL, offset, (short)0, flushCount);
}
/**
* Add this statement to JDBC batch for later execution.
*/
public int addBatch() {
this.addBatch = true;
return executeOrQueue();
}
@Override
public int executeNow() {
return persistExecute.executeSqlUpdate(this);
}
@Override
public boolean isBatchThisRequest() {
return addBatch || super.isBatchThisRequest();
}
@Override
public int executeOrQueue() {
return executeStatement();
@@ -8,6 +8,7 @@ import io.ebean.Transaction;
import io.ebean.Update;
import io.ebean.bean.EntityBean;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -91,4 +92,14 @@ public interface Persister {
* Visit the metrics.
*/
void visitMetrics(MetricVisitor visitor);
/**
* Add the statement to JDBC batch for later execution via executeBatch.
*/
void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction);
/**
* Execute the associated batched statement.
*/
int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction);
}
@@ -1,6 +1,8 @@
package io.ebeaninternal.server.core;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlRow;
import io.ebean.meta.MetricVisitor;
@@ -30,6 +32,31 @@ public interface RelationalQueryEngine {
*/
void findEach(RelationalQueryRequest request, Predicate<SqlRow> consumer);
/**
* Find single attribute.
*/
<T> T findSingleAttribute(RelationalQueryRequest request, Class<T> cls);
/**
* Find single attribute list.
*/
<T> List<T> findSingleAttributeList(RelationalQueryRequest request, Class<T> cls);
/**
* Find one via mapper.
*/
<T> T findOneMapper(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find list via mapper.
*/
<T> List<T> findListMapper(RelationalQueryRequest request, RowMapper<T> mapper);
/**
* Find each via raw consumer.
*/
void findEachRow(RelationalQueryRequest request, RowConsumer mapper);
/**
* Collect SQL query execution statistics.
*/
@@ -1,5 +1,7 @@
package io.ebeaninternal.server.core;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlQuery;
import io.ebean.SqlRow;
import io.ebean.Transaction;
@@ -53,6 +55,27 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
}
}
boolean findEachRow(RowConsumer mapper) {
queryEngine.findEachRow(this, mapper);
return true;
}
<T> List<T> findListMapper(RowMapper<T> mapper) {
return queryEngine.findListMapper(this, mapper);
}
<T> T findOneMapper(RowMapper<T> mapper) {
return queryEngine.findOneMapper(this, mapper);
}
public <T> List<T> findSingleAttributeList(Class<T> cls) {
return queryEngine.findSingleAttributeList(this, cls);
}
public <T> T findSingleAttribute(Class<T> cls) {
return queryEngine.findSingleAttribute(this, cls);
}
public void findEach(Consumer<SqlRow> consumer) {
queryEngine.findEach(this, consumer);
}
@@ -104,4 +127,35 @@ public final class RelationalQueryRequest extends AbstractSqlQueryRequest {
}
}
public ResultSet getResultSet() {
return resultSet;
}
public void incrementRows() {
rows++;
}
public <T> List<T> mapList(RowMapper<T> mapper) throws SQLException {
List<T> list = new ArrayList<>();
while (next()) {
list.add(mapper.map(resultSet, rows++));
}
return list;
}
public <T> T mapOne(RowMapper<T> mapper) throws SQLException {
if (!next()) {
return null;
} else {
return mapper.map(resultSet, rows++);
}
}
public void mapEach(RowConsumer consumer) throws SQLException {
while (next()) {
consumer.accept(resultSet, rows++);
}
}
}
@@ -59,11 +59,8 @@ class ManifestReader {
try {
Enumeration<URL> resources = classLoader.getResources(resourcePath);
while (resources.hasMoreElements()) {
InputStream is = resources.nextElement().openStream();
try {
try (InputStream is = resources.nextElement().openStream()) {
read(new Manifest(is));
} finally {
close(is);
}
}
} catch (IOException e) {
@@ -72,14 +69,6 @@ class ManifestReader {
return packageSet;
}
private void close(InputStream is) {
try {
is.close();
} catch (IOException e) {
logger.warn("Error closing manifest InputStream", e);
}
}
/**
* Read the entity packages from the manifest.
*/
@@ -152,6 +152,11 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
private final IdType idType;
/**
* Set when Id property is marked with GeneratedValue annotation.
*/
private final boolean idGeneratedValue;
private final boolean idTypePlatformDefault;
private final PlatformIdGenerator idGenerator;
@@ -450,6 +455,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
this.defaultSelectClause = deploy.getDefaultSelectClause();
this.idType = deploy.getIdType();
this.idGeneratedValue = deploy.isIdGeneratedValue();
this.idTypePlatformDefault = deploy.isIdTypePlatformDefault();
this.idGenerator = deploy.getIdGenerator();
this.sequenceName = deploy.getSequenceName();
@@ -631,9 +637,9 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
*/
public void setEbeanServer(SpiEbeanServer ebeanServer) {
this.ebeanServer = ebeanServer;
for (BeanPropertyAssocMany<?> aPropertiesMany : propertiesMany) {
for (BeanPropertyAssocMany<?> assocMany : propertiesMany) {
// used for creating lazy loading lists etc
aPropertiesMany.setLoader(ebeanServer);
assocMany.setEbeanServer(ebeanServer);
}
}
@@ -1922,6 +1928,20 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return createEntityBean();
}
/**
* We actually need to do a query because we don't know the type without the discriminator
* value, just select the id property and discriminator column (auto added)
*/
private T findReferenceBean(Object id, PersistenceContext pc) {
DefaultOrmQuery<T> query = new DefaultOrmQuery<>(this, ebeanServer, ebeanServer.getExpressionFactory());
query.setPersistenceContext(pc);
return query
// .select(getIdProperty().getName())
// we do not select the id because we
// probably have to load the entire bean
.setId(id).findOne();
}
/**
* Create a reference bean based on the id.
*/
@@ -1941,6 +1961,10 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
}
}
try {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
return findReferenceBean(id, pc);
}
EntityBean eb = createEntityBean();
id = convertSetId(id, eb);
@@ -1974,10 +1998,7 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
try {
if (inheritInfo != null && !inheritInfo.isConcrete()) {
// we actually need to do a query because we don't know the type without the discriminator
// value, just select the id property and discriminator column (auto added)
DefaultOrmQuery<T> query = new DefaultOrmQuery<>(this, ebeanServer, ebeanServer.getExpressionFactory());
return query.select(getIdProperty().getName()).setId(id).findOne();
return findReferenceBean(id, pc);
}
EntityBean eb = createEntityBean();
@@ -2984,6 +3005,13 @@ public class BeanDescriptor<T> implements BeanType<T>, STreeType {
return idType;
}
/**
* Return true if the Id value is marked as a <code>@GeneratedValue</code>.
*/
public boolean isIdGeneratedValue() {
return idGeneratedValue;
}
/**
* Return true if the identity is the platform default (not explicitly set).
*/
@@ -1369,9 +1369,15 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
desc.setIdType(IdType.EXTERNAL);
return;
}
// use the default. IDENTITY or SEQUENCE.
desc.setIdType(dbIdentity.getIdType());
desc.setIdTypePlatformDefault();
if (desc.isIdGeneratedValue() || serverConfig.isIdGeneratorAutomatic()) {
// use IDENTITY or SEQUENCE based on platform
desc.setIdType(dbIdentity.getIdType());
desc.setIdTypePlatformDefault();
} else {
// externally/application supplied Id values
desc.setIdType(IdType.EXTERNAL);
return;
}
}
if (desc.getBaseTable() == null) {
@@ -1387,21 +1393,23 @@ public class BeanDescriptorManager implements BeanDescriptorMap {
return;
}
String seqName = desc.getIdGeneratorName();
if (seqName != null) {
logger.debug("explicit sequence {} on {}", seqName, desc.getFullName());
} else {
String primaryKeyColumn = desc.getSinglePrimaryKeyColumn();
// use namingConvention to define sequence name
seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn);
}
if (IdType.SEQUENCE == desc.getIdType()) {
String seqName = desc.getIdGeneratorName();
if (seqName != null) {
logger.debug("explicit sequence {} on {}", seqName, desc.getFullName());
} else {
String primaryKeyColumn = desc.getSinglePrimaryKeyColumn();
// use namingConvention to define sequence name
seqName = namingConvention.getSequenceName(desc.getBaseTable(), primaryKeyColumn);
}
if (databasePlatform.isSequenceBatchMode()) {
// use sequence next step 1 as we are going to batch fetch them instead
desc.setSequenceAllocationSize(1);
if (databasePlatform.isSequenceBatchMode()) {
// use sequence next step 1 as we are going to batch fetch them instead
desc.setSequenceAllocationSize(1);
}
int stepSize = desc.getSequenceAllocationSize();
desc.setIdGenerator(createSequenceIdGenerator(seqName, stepSize));
}
int stepSize = desc.getSequenceAllocationSize();
desc.setIdGenerator(createSequenceIdGenerator(seqName, stepSize));
}
private PlatformIdGenerator createSequenceIdGenerator(String seqName, int stepSize) {
@@ -78,6 +78,11 @@ public final class BeanFkeyProperty implements ElPropertyValue {
return false;
}
@Override
public Object localEncrypt(Object value) {
throw new IllegalArgumentException("Should not get here?");
}
@Override
public boolean containsFormulaWithJoin() {
return false;
@@ -28,6 +28,7 @@ import io.ebeaninternal.server.query.STreeProperty;
import io.ebeaninternal.server.query.SqlBeanLoad;
import io.ebeaninternal.server.query.SqlJoinType;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.LocalEncryptedType;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeBoolean;
import io.ebeaninternal.server.type.ScalarTypeEnum;
@@ -1246,6 +1247,11 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
return dbBind;
}
@Override
public Object localEncrypt(Object value) {
return ((LocalEncryptedType)scalarType).localEncrypt(value);
}
/**
* Returns true if DB encrypted.
*/
@@ -65,6 +65,8 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
*/
final TableJoin tableJoin;
final PropertyForeignKey foreignKey;
/**
* The type of the joined bean.
*/
@@ -90,6 +92,7 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
*/
public BeanPropertyAssoc(BeanDescriptor<?> descriptor, DeployBeanPropertyAssoc<T> deploy) {
super(descriptor, deploy);
this.foreignKey = deploy.getForeignKey();
this.extraWhere = InternString.intern(deploy.getExtraWhere());
this.beanTable = deploy.getBeanTable();
this.mappedBy = InternString.intern(deploy.getMappedBy());
@@ -128,6 +131,13 @@ public abstract class BeanPropertyAssoc<T> extends BeanProperty implements STree
return fetchPreference;
}
/**
* Return the extra configuration for the foreign key.
*/
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
/**
* Create a ElPropertyValue for a *ToOne or *ToMany.
*/
@@ -7,10 +7,10 @@ import io.ebean.Transaction;
import io.ebean.bean.BeanCollection;
import io.ebean.bean.BeanCollection.ModifyListenMode;
import io.ebean.bean.BeanCollectionAdd;
import io.ebean.bean.BeanCollectionLoader;
import io.ebean.bean.EntityBean;
import io.ebean.bean.PersistenceContext;
import io.ebean.text.PathProperties;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
@@ -50,6 +50,8 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
private final String intersectionPublishTable;
private final String intersectionDraftTable;
private IntersectionTable intersectionTable;
/**
* For ManyToMany this is the Inverse join used to build reference queries.
*/
@@ -352,10 +354,30 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
* Set the lazy load server to help create reference collections (that lazy
* load on demand).
*/
public void setLoader(BeanCollectionLoader loader) {
public void setEbeanServer(SpiEbeanServer server) {
if (help != null) {
help.setLoader(loader);
help.setLoader(server);
}
if (manyToMany) {
intersectionTable = initIntersectionTable();
}
}
private IntersectionTable initIntersectionTable() {
IntersectionBuilder row = new IntersectionBuilder(intersectionPublishTable, intersectionDraftTable);
for (ExportedProperty exportedProperty : exportedProperties) {
row.addColumn(exportedProperty.getForeignDbColumn());
}
importedId.buildImport(row);
return row.build();
}
/**
* Return the intersection table helper.
*/
public IntersectionTable intersectionTable() {
return intersectionTable;
}
/**
@@ -755,6 +777,20 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> implements ST
return intersectionDraftTable != null && !intersectionDraftTable.equals(intersectionPublishTable);
}
/**
* Bind the two side of a many to many to the given SqlUpdate.
*/
public void intersectionBind(SqlUpdate sql, EntityBean parentBean, EntityBean other) {
if (embeddedExportedProperties) {
BeanProperty idProp = descriptor.getIdProperty();
parentBean = (EntityBean) idProp.getValue(parentBean);
}
for (ExportedProperty exportedProperty : exportedProperties) {
sql.setNextParameter(exportedProperty.getValue(parentBean));
}
importedId.bindImport(sql, other);
}
private void buildExport(IntersectionRow row, EntityBean parentBean) {
if (embeddedExportedProperties) {
@@ -46,15 +46,16 @@ class BeanPropertyAssocManyJsonHelp {
if (JsonToken.VALUE_NULL == event) {
return;
}
if (JsonToken.START_ARRAY != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array ");
}
if (many.isTransient()) {
jsonReadTransientUsingObjectMapper(readJson, parentBean);
return;
}
if (JsonToken.START_ARRAY != event) {
throw new JsonParseException(parser, "Unexpected token " + event + " - expecting start_array");
}
many.setValue(parentBean, many.jsonReadCollection(readJson, parentBean));
}
@@ -44,8 +44,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
private final boolean primaryKeyExport;
private final PropertyForeignKey foreignKey;
private AssocOneHelp localHelp;
protected final BeanProperty[] embeddedProps;
@@ -73,8 +71,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
DeployBeanPropertyAssocOne<T> deploy) {
super(descriptor, deploy);
foreignKey = deploy.getForeignKey();
primaryKeyExport = deploy.isPrimaryKeyExport();
oneToOne = deploy.isOneToOne();
oneToOneExported = deploy.isOneToOneExported();
@@ -323,10 +319,6 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
}
}
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
public boolean hasForeignKey() {
return foreignKey == null || !foreignKey.isNoConstraint();
}
@@ -8,7 +8,7 @@ import java.sql.Types;
class FormulaPropertyPath {
private static final String[] AGG_FUNCTIONS = {"count", "max", "min", "avg"};
private static final String[] AGG_FUNCTIONS = {"count", "max", "min", "avg", "sum"};
private static final String DISTINCT_ = "distinct ";
@@ -118,12 +118,8 @@ class FormulaPropertyPath {
}
}
String logicalName = (alias == null) ? formula : alias;
BeanProperty targetProperty = null;
if (alias != null) {
targetProperty = descriptor._findBeanProperty(alias);
}
String logicalName = (alias == null) ? internalExpression : alias;
BeanProperty targetProperty = descriptor._findBeanProperty(logicalName);
String parsedAggregation = buildFormula(parsed);
return new DynamicPropertyAggregationFormula(logicalName, scalarType, parsedAggregation, isAggregate(), targetProperty, alias);
}
@@ -0,0 +1,85 @@
package io.ebeaninternal.server.deploy;
import java.util.ArrayList;
import java.util.List;
/**
* Helper to build IntersectionTable.
*/
public class IntersectionBuilder {
private final String publishTable;
private final String draftTable;
private final List<String> columns = new ArrayList<>();
IntersectionBuilder(String publishTable, String draftTable) {
this.publishTable = publishTable;
this.draftTable = draftTable;
}
public void addColumn(String column) {
columns.add(column);
}
public IntersectionTable build() {
String insertSql = insertSql(publishTable);
String deleteSql = deleteSql(publishTable);
String draftInsertSql;
String draftDeleteSql;
if (publishTable.equals(draftTable)) {
draftInsertSql = insertSql;
draftDeleteSql = deleteSql;
} else {
draftInsertSql = insertSql(draftTable);
draftDeleteSql = deleteSql(draftTable);
}
return new IntersectionTable(insertSql, deleteSql, draftInsertSql, draftDeleteSql);
}
private String insertSql(String tableName) {
StringBuilder sb = new StringBuilder();
sb.append("insert into ").append(tableName).append(" (");
int count = 0;
for (String column : columns) {
if (count++ > 0) {
sb.append(", ");
}
sb.append(column);
}
sb.append(") values (");
for (int i = 0; i < count; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append("?");
}
sb.append(")");
return sb.toString();
}
private String deleteSql(String tableName) {
StringBuilder sb = new StringBuilder();
sb.append("delete from ").append(tableName);
sb.append(" where ");
int count = 0;
for (String column : columns) {
if (count++ > 0) {
sb.append(" and ");
}
sb.append(column);
sb.append(" = ?");
}
return sb.toString();
}
}
@@ -1,8 +1,8 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.SqlUpdate;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.core.DefaultSqlUpdate;
import io.ebeaninternal.server.expression.DefaultExpressionRequest;
import io.ebeaninternal.server.expression.IdInExpression;
@@ -22,12 +22,12 @@ public class IntersectionRow {
private List<Object> excludeIds;
private BeanDescriptor<?> excludeDescriptor;
public IntersectionRow(String tableName, BeanDescriptor<?> targetDescriptor) {
IntersectionRow(String tableName, BeanDescriptor<?> targetDescriptor) {
this.tableName = tableName;
this.targetDescriptor = targetDescriptor;
}
public IntersectionRow(String tableName) {
IntersectionRow(String tableName) {
this.tableName = tableName;
this.targetDescriptor = null;
}
@@ -35,7 +35,7 @@ public class IntersectionRow {
/**
* Set Id's to exclude. This is for deleting non-attached detail Id's.
*/
public void setExcludeIds(List<Object> excludeIds, BeanDescriptor<?> excludeDescriptor) {
void setExcludeIds(List<Object> excludeIds, BeanDescriptor<?> excludeDescriptor) {
this.excludeIds = excludeIds;
this.excludeDescriptor = excludeDescriptor;
}
@@ -44,7 +44,7 @@ public class IntersectionRow {
values.put(key, value);
}
public SqlUpdate createInsert(EbeanServer server) {
public SqlUpdate createInsert(SpiEbeanServer server) {
BindParams bindParams = new BindParams();
@@ -72,7 +72,7 @@ public class IntersectionRow {
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
}
public SqlUpdate createDelete(EbeanServer server, boolean softDelete) {
public SqlUpdate createDelete(SpiEbeanServer server, boolean softDelete) {
BindParams bindParams = new BindParams();
@@ -107,7 +107,7 @@ public class IntersectionRow {
return new DefaultSqlUpdate(server, sb.toString(), bindParams);
}
public SqlUpdate createDeleteChildren(EbeanServer server) {
public SqlUpdate createDeleteChildren(SpiEbeanServer server) {
BindParams bindParams = new BindParams();
@@ -0,0 +1,34 @@
package io.ebeaninternal.server.deploy;
import io.ebean.EbeanServer;
import io.ebean.SqlUpdate;
public class IntersectionTable {
private final String insertSql;
private final String deleteSql;
private final String draftInsertSql;
private final String draftDeleteSql;
IntersectionTable(String insertSql, String deleteSql, String draftInsertSql, String draftDeleteSql) {
this.insertSql = insertSql;
this.deleteSql = deleteSql;
this.draftInsertSql = draftInsertSql;
this.draftDeleteSql = draftDeleteSql;
}
/**
* Return a SqlUpdate for inserting into the intersection table.
*/
public SqlUpdate insert(EbeanServer server, boolean draft) {
return server.createSqlUpdate(draft ? draftInsertSql : insertSql);
}
/**
* Return a SqlUpdate for deleting from the intersection table.
*/
public SqlUpdate delete(EbeanServer server, boolean draft) {
return server.createSqlUpdate(draft ? draftDeleteSql : deleteSql);
}
}
@@ -4,6 +4,7 @@ import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.IntersectionBuilder;
import io.ebeaninternal.server.deploy.IntersectionRow;
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
@@ -66,4 +67,14 @@ public interface ImportedId {
* Return the set importedId clause.
*/
String importedIdClause();
/**
* Add DB columns to the intersection builder.
*/
void buildImport(IntersectionBuilder row);
/**
* Bind values to the intersection SqlUpdate.
*/
void bindImport(SqlUpdate sql, EntityBean other);
}
@@ -7,6 +7,7 @@ import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.IntersectionBuilder;
import io.ebeaninternal.server.deploy.IntersectionRow;
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
@@ -130,15 +131,31 @@ public class ImportedIdEmbedded implements ImportedId {
EntityBean embeddedId = (EntityBean) foreignAssocOne.getValue(other);
if (embeddedId == null) {
String msg = "Foreign Key value null?";
throw new PersistenceException(msg);
throw new PersistenceException("Foreign Key value null?");
}
for (ImportedIdSimple anImported : imported) {
Object scalarValue = anImported.foreignProperty.getValue(embeddedId);
row.put(anImported.localDbColumn, scalarValue);
}
}
@Override
public void buildImport(IntersectionBuilder row) {
for (ImportedIdSimple importedScalar : imported) {
row.addColumn(importedScalar.localDbColumn);
}
}
@Override
public void bindImport(SqlUpdate sql, EntityBean other) {
EntityBean embeddedId = (EntityBean) foreignAssocOne.getValue(other);
if (embeddedId == null) {
throw new PersistenceException("Foreign Key value null?");
}
for (ImportedIdSimple anImported : imported) {
Object scalarValue = anImported.foreignProperty.getValue(embeddedId);
sql.setNextParameter(scalarValue);
}
}
/**
@@ -7,6 +7,7 @@ import io.ebeaninternal.server.deploy.BeanFkeyProperty;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import io.ebeaninternal.server.deploy.DbSqlContext;
import io.ebeaninternal.server.deploy.IntersectionBuilder;
import io.ebeaninternal.server.deploy.IntersectionRow;
import io.ebeaninternal.server.persist.dml.GenerateDmlRequest;
import io.ebeaninternal.server.persist.dmlbind.BindableRequest;
@@ -98,6 +99,20 @@ public final class ImportedIdSimple implements ImportedId, Comparable<ImportedId
return foreignProperty.getValue(bean);
}
@Override
public void buildImport(IntersectionBuilder row) {
row.addColumn(localDbColumn);
}
@Override
public void bindImport(SqlUpdate sql, EntityBean other) {
Object value = getIdValue(other);
if (value == null) {
throw new PersistenceException("Foreign Key value null?");
}
sql.setNextParameter(value);
}
@Override
public void buildImport(IntersectionRow row, EntityBean other) {
@@ -106,6 +106,11 @@ public class DeployBeanDescriptor<T> {
private PlatformIdGenerator idGenerator;
/**
* Set true when explicit auto generated Id.
*/
private boolean idGeneratedValue;
/**
* The database sequence name (optional).
*/
@@ -848,6 +853,20 @@ public class DeployBeanDescriptor<T> {
}
}
/**
* Return true for automatic Id generation strategy.
*/
public boolean isIdGeneratedValue() {
return idGeneratedValue;
}
/**
* Set when GeneratedValue explicitly mapped on Id property.
*/
public void setIdGeneratedValue() {
this.idGeneratedValue = true;
}
/**
* Assign the standard UUID generator.
*/
@@ -647,11 +647,35 @@ public class DeployBeanProperty {
*/
public String parseAggregation() {
if (aggregation != null) {
int pos = aggregation.indexOf('(');
if (pos > -1) {
// check for recursive property name and formula
String maybePropertyName = aggregation.substring(pos + 1, aggregation.length() - 1);
if (name.equals(maybePropertyName)) {
// e.g. bean property cost mapped to sum(cost)
return aggregationJoin(pos, dbColumn);
} else {
DeployBeanProperty other = desc.getBeanProperty(maybePropertyName);
if (other != null) {
// e.g. bean property maxKms mapped to sum(totalKms) where totalKms is another property
return aggregationJoin(pos, other.getDbColumnRaw());
}
}
}
aggregationParsed = desc.parse(aggregation);
}
return aggregationParsed;
}
/**
* Simple aggregation parsing like sum(someProperty)
*/
private String aggregationJoin(int pos, String dbColumn) {
String p0 = aggregation.substring(0, pos + 1);
aggregationParsed = p0 + "${ta}." + dbColumn + aggregation.substring(aggregation.length() - 1);
return aggregationParsed;
}
public void setAggregation(String aggregation) {
this.aggregation = aggregation;
this.dbRead = true;
@@ -702,6 +726,13 @@ public class DeployBeanProperty {
return dbColumn;
}
/**
* Return the DB column without any aggregation parsing.
*/
private String getDbColumnRaw() {
return dbColumn;
}
/**
* Set the database column name this is mapped to.
*/
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy.meta;
import io.ebeaninternal.server.deploy.BeanCascadeInfo;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
/**
* Abstract base for properties mapped to an associated bean, list, set or map.
@@ -16,7 +17,7 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
/**
* Persist settings.
*/
protected final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo();
final BeanCascadeInfo cascadeInfo = new BeanCascadeInfo();
/**
* The join table information.
@@ -42,6 +43,8 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
private int fetchPreference = 1000;
private PropertyForeignKey foreignKey;
/**
* Construct the property.
*/
@@ -117,6 +120,13 @@ public abstract class DeployBeanPropertyAssoc<T> extends DeployBeanProperty {
return cascadeInfo;
}
public void setForeignKey(PropertyForeignKey foreignKey) {
this.foreignKey = foreignKey;
}
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
/**
* Return the mappedBy deployment attribute.
@@ -1,6 +1,5 @@
package io.ebeaninternal.server.deploy.meta;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.query.SqlJoinType;
import javax.persistence.CascadeType;
@@ -22,8 +21,6 @@ public class DeployBeanPropertyAssocOne<T> extends DeployBeanPropertyAssoc<T> {
private String columnPrefix;
private PropertyForeignKey foreignKey;
private boolean orphanRemoval;
/**
@@ -148,14 +145,6 @@ public class DeployBeanPropertyAssocOne<T> extends DeployBeanPropertyAssoc<T> {
}
}
public void setForeignKey(PropertyForeignKey foreignKey) {
this.foreignKey = foreignKey;
}
public PropertyForeignKey getForeignKey() {
return foreignKey;
}
public void setOrphanRemoval(boolean orphanRemoval) {
this.orphanRemoval = orphanRemoval;
}
@@ -1,5 +1,6 @@
package io.ebeaninternal.server.deploy.parse;
import io.ebean.annotation.DbForeignKey;
import io.ebean.annotation.FetchPreference;
import io.ebean.annotation.HistoryExclude;
import io.ebean.annotation.PrivateOwned;
@@ -12,6 +13,7 @@ import io.ebean.util.StringHelper;
import io.ebeaninternal.server.deploy.BeanDescriptorManager;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanTable;
import io.ebeaninternal.server.deploy.PropertyForeignKey;
import io.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.deploy.meta.DeployBeanPropertyAssocMany;
@@ -95,6 +97,12 @@ class AnnotationAssocManys extends AnnotationParser {
readElementCollection(prop, elementCollection);
}
// for ManyToMany typically to disable foreign keys from intersection table
DbForeignKey dbForeignKey = get(prop, DbForeignKey.class);
if (dbForeignKey != null){
prop.setForeignKey(new PropertyForeignKey(dbForeignKey));
}
if (get(prop, HistoryExclude.class) != null) {
prop.setExcludedFromHistory();
}
@@ -184,7 +184,7 @@ public class AnnotationAssocOnes extends AnnotationParser {
}
private String errorMsgMissingBeanTable(Class<?> type, String from) {
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered?";
return "Error with association to [" + type + "] from [" + from + "]. Is " + type + " registered? Does it have the @Entity annotation?";
}
private BeanTable beanTable(DeployBeanPropertyAssoc<?> prop) {
@@ -322,7 +322,7 @@ public class AnnotationFields extends AnnotationParser {
prop.setDbLength(length.value());
}
io.ebean.annotation.NotNull nonNull = get(prop, io.ebean.annotation.NotNull.class);
io.ebean.annotation.NotNull nonNull = get(prop, io.ebean.annotation.NotNull.class);
if (nonNull != null) {
prop.setNullable(false);
}
@@ -389,7 +389,7 @@ public class AnnotationFields extends AnnotationParser {
Set<DbMigration> dbMigration = getAll(prop, DbMigration.class);
dbMigration.forEach(ann -> prop.addDbMigrationInfo(
new DbMigrationInfo(ann.preAdd(), ann.postAdd(), ann.preAlter(), ann.postAlter(), ann.platforms())));
new DbMigrationInfo(ann.preAdd(), ann.postAdd(), ann.preAlter(), ann.postAlter(), ann.platforms())));
}
private void addIndex(DeployBeanProperty prop, Index index) {
@@ -515,6 +515,7 @@ public class AnnotationFields extends AnnotationParser {
private void readGenValue(GeneratedValue gen, DeployBeanProperty prop) {
descriptor.setIdGeneratedValue();
String genName = gen.generator();
SequenceGenerator sequenceGenerator = find(prop, SequenceGenerator.class);
@@ -174,6 +174,11 @@ public class ElPropertyChain implements ElPropertyValue {
return lastElPropertyValue.isLocalEncrypted();
}
@Override
public Object localEncrypt(Object value) {
return lastElPropertyValue.localEncrypt(value);
}
@Override
public String getAssocIsEmpty(SpiExpressionRequest request, String path) {
return lastElPropertyValue.getAssocIsEmpty(request, path);
@@ -52,6 +52,11 @@ public interface ElPropertyValue extends ElPropertyDeploy, ExpressionPath {
*/
boolean isDbEncrypted();
/**
* Encrypt the input value return the encrypted value.
*/
Object localEncrypt(Object value);
/**
* Return the value ensuring objects prior to the top scalar property are
* automatically populated.
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.expression;
import io.ebean.CacheMode;
import io.ebean.CountDistinctOrder;
import io.ebean.DtoQuery;
import io.ebean.Expression;
import io.ebean.ExpressionFactory;
import io.ebean.ExpressionList;
@@ -294,6 +295,11 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
return query.asDraft();
}
@Override
public <D> DtoQuery<D> asDto(Class<D> dtoClass) {
return query.asDto(dtoClass);
}
@Override
public Query<T> setIncludeSoftDeletes() {
return query.setIncludeSoftDeletes();
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.expression;
import io.ebean.CacheMode;
import io.ebean.CountDistinctOrder;
import io.ebean.DtoQuery;
import io.ebean.Expression;
import io.ebean.ExpressionList;
import io.ebean.FetchPath;
@@ -331,6 +332,11 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
return exprList.asDraft();
}
@Override
public <D> DtoQuery<D> asDto(Class<D> dtoClass) {
return exprList.asDto(dtoClass);
}
@Override
public Query<T> setIncludeSoftDeletes() {
return exprList.setIncludeSoftDeletes();
@@ -84,10 +84,11 @@ public class SimpleExpression extends AbstractValueExpression {
// bind the key as well as the value
String encryptKey = prop.getBeanProperty().getEncryptKey().getStringValue();
request.addBindEncryptKey(encryptKey);
} else if (prop.isLocalEncrypted()) {
Object bindVal = prop.localEncrypt(value());
request.addBindEncryptKey(bindVal);
return;
}
//else if (prop.isLocalEncrypted()) {
// not supporting this for equals (but probably could)
// prop.getBeanProperty().getScalarType();
}
request.addBindValue(value());
@@ -0,0 +1,33 @@
package io.ebeaninternal.server.logger;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
public class DLogManager implements SpiLogManager {
private final SpiLogger sql;
private final SpiLogger summary;
private final SpiLogger txn;
public DLogManager(SpiLogger sql, SpiLogger summary, SpiLogger txn) {
this.sql = sql;
this.summary = summary;
this.txn = txn;
}
@Override
public SpiLogger txn() {
return txn;
}
@Override
public SpiLogger sql() {
return sql;
}
@Override
public SpiLogger sum() {
return summary;
}
}
@@ -0,0 +1,19 @@
package io.ebeaninternal.server.logger;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiLoggerFactory;
import org.slf4j.LoggerFactory;
/**
* Default SpiLoggerFactory implementation.
*/
public class DLoggerFactory implements SpiLoggerFactory {
/**
* Just use a standard slf4j Logger.
*/
@Override
public SpiLogger create(String name) {
return new DSpiLogger(LoggerFactory.getLogger(name));
}
}
@@ -0,0 +1,33 @@
package io.ebeaninternal.server.logger;
import io.ebeaninternal.api.SpiLogger;
import org.slf4j.Logger;
public class DSpiLogger implements SpiLogger {
private final Logger logger;
public DSpiLogger(Logger logger) {
this.logger = logger;
}
@Override
public boolean isDebug() {
return logger.isDebugEnabled();
}
@Override
public boolean isTrace() {
return logger.isTraceEnabled();
}
@Override
public void debug(String msg) {
logger.debug(msg);
}
@Override
public void trace(String msg) {
logger.trace(msg);
}
}
@@ -6,6 +6,7 @@ import io.ebeaninternal.server.core.PersistRequestBean;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -357,4 +358,11 @@ public final class BatchControl {
private BatchedBeanHolder[] getBeanHolderArray() {
return beanHoldMap.values().toArray(new BatchedBeanHolder[beanHoldMap.size()]);
}
/**
* Execute a batched statement.
*/
public int[] execute(String key, boolean getGeneratedKeys) throws SQLException {
return pstmtHolder.execute(key, getGeneratedKeys);
}
}
@@ -2,11 +2,16 @@ package io.ebeaninternal.server.persist;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiProfileTransactionEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
/**
* A batched statement that is held in BatchedPstmtHolder. It has a list of
@@ -17,6 +22,8 @@ import java.util.ArrayList;
*/
public class BatchedPstmt implements SpiProfileTransactionEvent {
private static final Logger log = LoggerFactory.getLogger(BatchedPstmt.class);
/**
* The underlying statement.
*/
@@ -38,6 +45,10 @@ public class BatchedPstmt implements SpiProfileTransactionEvent {
private long profileStart;
private int[] results;
private List<InputStream> inputStreams;
/**
* Create with a given statement.
*/
@@ -115,16 +126,19 @@ public class BatchedPstmt implements SpiProfileTransactionEvent {
}
private void executeAndCheckRowCounts() throws SQLException {
try {
results = pstmt.executeBatch();
if (results.length != list.size()) {
String s = "results array error " + results.length + " " + list.size();
throw new SQLException(s);
}
int[] results = pstmt.executeBatch();
if (results.length != list.size()) {
String s = "results array error " + results.length + " " + list.size();
throw new SQLException(s);
}
// check for concurrency exceptions...
for (int i = 0; i < results.length; i++) {
list.get(i).checkRowCount(results[i]);
// check for concurrency exceptions...
for (int i = 0; i < results.length; i++) {
list.get(i).checkRowCount(results[i]);
}
} finally {
closeInputStreams();
}
}
@@ -140,4 +154,29 @@ public class BatchedPstmt implements SpiProfileTransactionEvent {
}
}
/**
* Return the execution results (row counts).
*/
public int[] getResults() {
return results;
}
/**
* Register any inputStreams that should be closed after execution.
*/
public void registerInputStreams(List<InputStream> inputStreams) {
this.inputStreams = inputStreams;
}
private void closeInputStreams() {
if (inputStreams != null) {
for (InputStream inputStream : inputStreams) {
try {
inputStream.close();
} catch (IOException e) {
log.warn("Error closing inputStream ", e);
}
}
}
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.persist;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.LinkedHashMap;
@@ -38,6 +39,15 @@ public class BatchedPstmtHolder {
* This will return null if no matching PreparedStatement is found.
*/
public PreparedStatement getStmt(String stmtKey, BatchPostExecute postExecute) {
BatchedPstmt batchedPstmt = getBatchedPstmt(stmtKey, postExecute);
return (batchedPstmt == null) ? null : batchedPstmt.getStatement();
}
/**
* Return the BatchedPstmt that holds the batched statement.
*/
public BatchedPstmt getBatchedPstmt(String stmtKey, BatchPostExecute postExecute) {
BatchedPstmt bs = stmtMap.get(stmtKey);
if (bs == null) {
// the PreparedStatement has need been created
@@ -52,7 +62,7 @@ public class BatchedPstmtHolder {
if (bsSize > maxSize) {
maxSize = bsSize;
}
return bs.getStatement();
return bs;
}
/**
@@ -73,6 +83,19 @@ public class BatchedPstmtHolder {
return stmtMap.isEmpty();
}
/**
* Execute one of the batched statements returning the row counts.
*/
public int[] execute(String key, boolean getGeneratedKeys) throws SQLException {
BatchedPstmt batchedPstmt = stmtMap.remove(key);
if (batchedPstmt == null) {
throw new PersistenceException("No batched statement found for key " + key);
}
batchedPstmt.executeBatch(getGeneratedKeys);
return batchedPstmt.getResults();
}
/**
* Execute all batched PreparedStatements.
*
@@ -2,12 +2,14 @@ package io.ebeaninternal.server.persist;
import io.ebean.config.dbplatform.DbPlatformType;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.server.core.Message;
import io.ebeaninternal.server.core.timezone.DataTimeZone;
import io.ebeaninternal.server.expression.platform.DbExpressionHandler;
import io.ebeaninternal.server.persist.platform.MultiValueBind;
import io.ebeaninternal.server.transaction.TransactionManager;
import io.ebeaninternal.server.type.DataBind;
import io.ebeaninternal.server.type.DataReader;
import io.ebeaninternal.server.type.RsetDataReader;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.TypeManager;
import org.slf4j.Logger;
@@ -18,6 +20,7 @@ import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
@@ -48,7 +51,7 @@ public class Binder {
/**
* Set the PreparedStatement with which to bind variables to.
*/
public Binder(TypeManager typeManager, int asOfBindCount, boolean asOfStandardsBased,
public Binder(TypeManager typeManager, SpiLogManager logManager, int asOfBindCount, boolean asOfStandardsBased,
DbExpressionHandler dbExpressionHandler, DataTimeZone dataTimeZone, MultiValueBind multiValueBind) {
this.typeManager = typeManager;
@@ -57,12 +60,7 @@ public class Binder {
this.dbExpressionHandler = dbExpressionHandler;
this.dataTimeZone = dataTimeZone;
this.multiValueBind = multiValueBind;
this.enableBindLog = enableBindLog();
}
private boolean enableBindLog() {
return TransactionManager.SQL_LOGGER.isDebugEnabled()
|| TransactionManager.SUM_LOGGER.isDebugEnabled();
this.enableBindLog = logManager.sql().isDebug();
}
/**
@@ -201,7 +199,7 @@ public class Binder {
}
}
private ScalarType<?> getScalarType(Class<?> clazz) {
public ScalarType<?> getScalarType(Class<?> clazz) {
ScalarType<?> type = typeManager.getScalarType(clazz);
if (type == null) {
throw new PersistenceException("No ScalarType registered for " + clazz);
@@ -462,4 +460,8 @@ public class Binder {
public DataBind dataBind(PreparedStatement stmt, Connection connection) {
return new DataBind(dataTimeZone, stmt, connection);
}
public DataReader createDataReader(ResultSet resultSet) {
return new RsetDataReader(dataTimeZone, resultSet);
}
}
@@ -13,6 +13,7 @@ import io.ebean.bean.PersistenceContext;
import io.ebean.event.BeanPersistController;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlUpdate;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiUpdate;
import io.ebeaninternal.server.core.Message;
@@ -34,6 +35,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -124,13 +126,28 @@ public final class DefaultPersister implements Persister {
}
}
@Override
public void addBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
new PersistRequestUpdateSql(server, sqlUpdate, transaction, persistExecute).addBatch();
}
@Override
public int[] executeBatch(SpiSqlUpdate sqlUpdate, SpiTransaction transaction) {
BatchControl batchControl = transaction.getBatchControl();
try {
return batchControl.execute(sqlUpdate.getSql(), sqlUpdate.isGetGeneratedKeys());
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
/**
* Execute the updateSql.
*/
@Override
public int executeSqlUpdate(SqlUpdate updSql, Transaction t) {
return executeOrQueue(new PersistRequestUpdateSql(server, updSql, (SpiTransaction) t, persistExecute));
return executeOrQueue(new PersistRequestUpdateSql(server, (SpiSqlUpdate) updSql, (SpiTransaction) t, persistExecute));
}
/**
@@ -26,6 +26,14 @@ class MergeContext {
this.clientGeneratedIds = clientGeneratedIds;
}
public SpiEbeanServer getServer() {
return server;
}
public SpiTransaction getTransaction() {
return transaction;
}
/**
* Add to the list of beans to delete.
*/
@@ -26,7 +26,7 @@ import java.util.regex.Pattern;
*/
class MergeHandler {
private final Pattern PATH_SPLIT = Pattern.compile("\\.");
private static final Pattern PATH_SPLIT = Pattern.compile("\\.");
private final SpiEbeanServer server;
private final BeanDescriptor<?> desc;
@@ -51,7 +51,7 @@ class MergeHandler {
List<EntityBean> merge() {
Set<String> paths = options.paths();
if (paths.isEmpty() && !options.isClientGeneratedIds()) {
if (desc.isIdGeneratedValue() && paths.isEmpty() && !options.isClientGeneratedIds()) {
// just do a single insert or update based on Id value present
Object id = desc.getId(bean);
if (id != null) {
@@ -139,7 +139,12 @@ class MergeHandler {
throw new PersistenceException("merge path [" + path + "] is not a ToMany or ToOne property of " + targetDesc.getFullName());
}
if (prop instanceof BeanPropertyAssocMany<?>) {
return new MergeNodeAssocMany(fullPath, (BeanPropertyAssocMany<?>) prop);
BeanPropertyAssocMany<?> assocMany = (BeanPropertyAssocMany<?>) prop;
if (assocMany.isManyToMany()) {
return new MergeNodeAssocManyToMany(fullPath, assocMany);
} else {
return new MergeNodeAssocOneToMany(fullPath, assocMany);
}
} else {
return new MergeNodeAssocOne(fullPath, (BeanPropertyAssocOne<?>) prop);
}
@@ -6,6 +6,8 @@ import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.deploy.BeanProperty;
import io.ebeaninternal.server.deploy.BeanPropertyAssoc;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -50,6 +52,22 @@ abstract class MergeNode {
return null;
}
/**
* Return the outline beans as a map keyed by Id values.
*/
Map<Object, EntityBean> toMap(Collection outlines) {
Map<Object, EntityBean> outlineMap = new HashMap<>();
if (outlines != null) {
for (Object out : outlines) {
EntityBean outlineBean = (EntityBean) out;
Object outlineId = targetDescriptor.getId(outlineBean);
outlineMap.put(outlineId, outlineBean);
}
}
return outlineMap;
}
/**
* Add to the query to fetch the Ids values for the foreign keys basically.
*/
@@ -0,0 +1,82 @@
package io.ebeaninternal.server.persist;
import io.ebean.SqlUpdate;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import io.ebeaninternal.server.deploy.IntersectionTable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Node for processing merge on ManyToMany properties.
*/
class MergeNodeAssocManyToMany extends MergeNode {
private final BeanPropertyAssocMany<?> many;
MergeNodeAssocManyToMany(String fullPath, BeanPropertyAssocMany<?> property) {
super(fullPath, property);
this.many = property;
}
public void merge(MergeRequest request) {
EntityBean parentBean = request.getBean();
Collection beans = many.getRawCollection(parentBean);
Collection outlines = many.getRawCollection(request.getOutline());
Map<Object, EntityBean> outlineIds = toMap(outlines);
List<EntityBean> additions = new ArrayList<>();
if (beans != null) {
for (Object bean : beans) {
EntityBean entityBean = (EntityBean) bean;
Object beanId = targetDescriptor.getId(entityBean);
if (beanId != null) {
if (outlineIds.remove(beanId) == null) {
additions.add(entityBean);
}
}
}
}
// any remaining are considered deletes
List<EntityBean> deletions = new ArrayList<>(outlineIds.values());
SpiEbeanServer server = request.getServer();
SpiTransaction transaction = request.getTransaction();
IntersectionTable intersectionTable = many.intersectionTable();
if (!deletions.isEmpty()) {
transaction.flushBatch();
SqlUpdate delete = intersectionTable.delete(server, false);
for (EntityBean deletion : deletions) {
many.intersectionBind(delete, parentBean, deletion);
delete.addBatch();
}
delete.execute();
}
if (!additions.isEmpty()) {
transaction.flushBatch();
SqlUpdate insert = intersectionTable.insert(server, false);
for (EntityBean addition : additions) {
many.intersectionBind(insert, parentBean, addition);
insert.addBatch();
}
insert.execute();
}
many.resetMany(parentBean);
}
}
@@ -10,11 +10,11 @@ import java.util.Map;
/**
* Node for processing merge on ToMany properties.
*/
class MergeNodeAssocMany extends MergeNode {
class MergeNodeAssocOneToMany extends MergeNode {
private final BeanPropertyAssocMany<?> many;
MergeNodeAssocMany(String fullPath, BeanPropertyAssocMany<?> property) {
MergeNodeAssocOneToMany(String fullPath, BeanPropertyAssocMany<?> property) {
super(fullPath, property);
this.many = property;
}
@@ -24,14 +24,7 @@ class MergeNodeAssocMany extends MergeNode {
Collection beans = many.getRawCollection(request.getBean());
Collection outlines = many.getRawCollection(request.getOutline());
Map<Object, EntityBean> outlineIds = new HashMap<>();
if (outlines != null) {
for (Object outline : outlines) {
EntityBean outlineBean = (EntityBean) outline;
Object outlineId = targetDescriptor.getId(outlineBean);
outlineIds.put(outlineId, outlineBean);
}
}
Map<Object, EntityBean> outlineIds = toMap(outlines);
if (beans != null) {
for (Object bean : beans) {
@@ -1,6 +1,8 @@
package io.ebeaninternal.server.persist;
import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiTransaction;
/**
* Request object used for processing the merge.
@@ -18,6 +20,20 @@ class MergeRequest {
this.outline = outline;
}
/**
* Return the associated server.
*/
SpiEbeanServer getServer() {
return context.getServer();
}
/**
* Return the associated transaction.
*/
public SpiTransaction getTransaction() {
return context.getTransaction();
}
/**
* Create a sub request with the given beans (to cascade the processing).
*/
@@ -46,6 +46,8 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
*/
protected DataBind dataBind;
protected BatchedPstmt batchedPstmt;
protected String sql;
/**
@@ -154,9 +156,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
*/
protected void logSql(String sql) {
if (logLevelSql) {
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
sql = Str.add(sql, "; --bind(", bindLog.toString(), ")");
}
sql = Str.add(sql, "; --bind(", bindLog.toString(), ")");
transaction.logSql(sql);
}
}
@@ -187,7 +187,10 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
@Override
public void bindNoLog(Object value, int sqlType, String logPlaceHolder) throws SQLException {
if (logLevelSql) {
bindLog.append(logPlaceHolder).append(" ");
if (bindLog.length() > 0) {
bindLog.append(",");
}
bindLog.append(logPlaceHolder);
}
dataBind.setObject(value, sqlType);
}
@@ -272,20 +275,18 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
/**
* Return a prepared statement taking into account batch requirements.
*/
protected PreparedStatement getPstmt(SpiTransaction t, String sql, PersistRequestBean<?> request,
boolean genKeys) throws SQLException {
protected PreparedStatement getPstmt(SpiTransaction t, String sql, PersistRequestBean<?> request, boolean genKeys) throws SQLException {
BatchedPstmtHolder batch = t.getBatchControl().getPstmtHolder();
PreparedStatement stmt = batch.getStmt(sql, request);
if (stmt != null) {
return stmt;
batchedPstmt = batch.getBatchedPstmt(sql, request);
if (batchedPstmt != null) {
return batchedPstmt.getStatement();
}
stmt = getPstmt(t, sql, genKeys);
PreparedStatement stmt = getPstmt(t, sql, genKeys);
BatchedPstmt bs = new BatchedPstmt(stmt, genKeys, sql, t);
batch.addStmt(bs, request);
batchedPstmt = new BatchedPstmt(stmt, genKeys, sql, t);
batch.addStmt(batchedPstmt, request);
return stmt;
}
@@ -91,7 +91,9 @@ public class InsertHandler extends DmlHandler {
}
dataBind = bind(pstmt);
meta.bind(this, bean, withId, persistRequest.isPublish());
if (persistRequest.isBatched()) {
batchedPstmt.registerInputStreams(dataBind.getInputStreams());
}
logSql(sql);
}
@@ -47,7 +47,9 @@ public class UpdateHandler extends DmlHandler {
}
dataBind = bind(pstmt);
meta.bind(persistRequest, this, updatePlan);
if (persistRequest.isBatched()) {
batchedPstmt.registerInputStreams(dataBind.getInputStreams());
}
setUpdateGenValues();
logSql(sql);
@@ -241,10 +241,14 @@ class CQueryBuilder {
ManyWhereJoins manyWhereJoins = query.getManyWhereJoins();
if (manyWhereJoins.isFormulaWithJoin()) {
query.select(manyWhereJoins.getFormulaProperties());
} else {
query.setSelectId();
boolean countDistinct = query.isDistinct();
if (!countDistinct) {
// minimise select clause for standard count
if (manyWhereJoins.isFormulaWithJoin()) {
query.select(manyWhereJoins.getFormulaProperties());
} else {
query.setSelectId();
}
}
CQueryPredicates predicates = new CQueryPredicates(binder, request);
@@ -263,23 +267,35 @@ class CQueryBuilder {
}
boolean hasMany = sqlTree.hasMany();
String sqlSelect = "select count(*)";
if (hasMany) {
// need to count distinct id's ...
query.setSqlDistinct(true);
sqlSelect = null;
String sqlSelect = null;
if (countDistinct) {
if (sqlTree.isSingleProperty()) {
request.setInlineCountDistinct();
}
} else {
if (hasMany) {
// need to count distinct id's ...
query.setSqlDistinct(true);
} else {
sqlSelect = "select count(*)";
}
}
SqlLimitResponse s = buildSql(sqlSelect, request, predicates, sqlTree);
String sql = s.getSql();
if (hasMany || query.isRawSql()) {
int pos = sql.lastIndexOf(" order by "); // remove order by - mssql does not accept order by in subqueries
if (pos != -1) {
sql = sql.substring(0, pos);
}
sql = "select count(*) from ( " + sql + ")";
if (selectCountWithAlias) {
sql += " as c";
if (!request.isInlineCountDistinct()) {
if (countDistinct) {
sql = wrapSelectCount(sql);
} else if (hasMany || query.isRawSql()) {
// remove order by - mssql does not accept order by in subqueries
int pos = sql.lastIndexOf(" order by ");
if (pos != -1) {
sql = sql.substring(0, pos);
}
sql = wrapSelectCount(sql);
}
}
@@ -290,6 +306,14 @@ class CQueryBuilder {
return new CQueryRowCount(queryPlan, request, predicates);
}
private String wrapSelectCount(String sql) {
sql = "select count(*) from ( " + sql + ")";
if (selectCountWithAlias) {
sql += " as c";
}
return sql;
}
/**
* Return the SQL Select statement as a String. Converts logical property
* names to physical deployment column names.
@@ -536,6 +560,9 @@ class CQueryBuilder {
if (!useSqlLimiter) {
sb.append("select ");
if (query.isDistinctQuery()) {
if (request.isInlineCountDistinct()) {
sb.append("count(");
}
sb.append("distinct ");
String distinctOn = select.getDistinctOn();
if (distinctOn != null) {
@@ -551,6 +578,9 @@ class CQueryBuilder {
} else {
sb.append(select.getSelectSql());
}
if (request.isInlineCountDistinct()) {
sb.append(")");
}
if (query.isDistinctQuery() && dbOrderBy != null && !query.isSingleAttribute()) {
// add the orderBy columns to the select clause (due to distinct)
sb.append(", ").append(DbOrderByTrim.trim(dbOrderBy));
@@ -19,7 +19,6 @@ import io.ebeaninternal.server.core.SpiResultSet;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.lib.util.Str;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.transaction.TransactionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -82,9 +81,7 @@ public class CQueryEngine {
if (request.isLogSql()) {
String logSql = query.getGeneratedSql();
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", query.getBindLog(), ") rows:", String.valueOf(rows));
}
logSql = Str.add(logSql, "; --bind(", query.getBindLog(), ") rows:", String.valueOf(rows));
request.logSql(logSql);
}
@@ -151,11 +148,7 @@ public class CQueryEngine {
}
private <T> void logGeneratedSql(OrmQueryRequest<T> request, String sql, String bindLog) {
String logSql = sql;
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
logSql = Str.add(logSql, "; --bind(", bindLog, ")");
}
request.logSql(logSql);
request.logSql(Str.add(sql, "; --bind(", bindLog, ")"));
}
/**
@@ -458,9 +451,7 @@ public class CQueryEngine {
private void logSql(CQuery<?> query) {
String sql = query.getGeneratedSql();
if (TransactionManager.SQL_LOGGER.isTraceEnabled()) {
sql = Str.add(sql, "; --bind(", query.getBindLog(), ")");
}
sql = Str.add(sql, "; --bind(", query.getBindLog(), ")");
query.getTransaction().logSql(sql);
}
@@ -1,5 +1,7 @@
package io.ebeaninternal.server.query;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlRow;
import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
@@ -10,6 +12,7 @@ import io.ebeaninternal.server.core.Message;
import io.ebeaninternal.server.core.RelationalQueryEngine;
import io.ebeaninternal.server.core.RelationalQueryRequest;
import io.ebeaninternal.server.persist.Binder;
import io.ebeaninternal.server.type.ScalarType;
import javax.persistence.PersistenceException;
import java.sql.SQLException;
@@ -91,6 +94,109 @@ public class DefaultRelationalQueryEngine implements RelationalQueryEngine {
}
}
@Override
public <T> T findOneMapper(RelationalQueryRequest request, RowMapper<T> mapper) {
try {
request.executeSql(binder, SpiQuery.Type.BEAN);
T value = request.mapOne(mapper);
request.logSummary();
return value;
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@Override
public <T> List<T> findListMapper(RelationalQueryRequest request, RowMapper<T> mapper) {
try {
request.executeSql(binder, SpiQuery.Type.LIST);
List<T> list = request.mapList(mapper);
request.logSummary();
return list;
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@Override
public void findEachRow(RelationalQueryRequest request, RowConsumer consumer) {
try {
request.executeSql(binder, SpiQuery.Type.LIST);
request.mapEach(consumer);
request.logSummary();
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@SuppressWarnings("unchecked")
@Override
public <T> List<T> findSingleAttributeList(RelationalQueryRequest request, Class<T> cls) {
ScalarType<T> scalarType = (ScalarType<T>) binder.getScalarType(cls);
return findScalarList(request, scalarType);
}
private <T> List<T> findScalarList(RelationalQueryRequest request, ScalarType<T> scalarType) {
try {
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
List<T> list = new ArrayList<>();
while (request.next()) {
request.incrementRows();
list.add(scalarType.read(binder.createDataReader(request.getResultSet())));
}
request.logSummary();
return list;
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@SuppressWarnings("unchecked")
@Override
public <T> T findSingleAttribute(RelationalQueryRequest request, Class<T> cls) {
ScalarType<T> scalarType = (ScalarType<T>) binder.getScalarType(cls);
return findScalar(request, scalarType);
}
private <T> T findScalar(RelationalQueryRequest request, ScalarType<T> scalarType) {
try {
request.executeSql(binder, SpiQuery.Type.ATTRIBUTE);
T value = null;
if (request.next()) {
request.incrementRows();
value = scalarType.read(binder.createDataReader(request.getResultSet()));
}
request.logSummary();
return value;
} catch (Exception e) {
throw new PersistenceException(Message.msg("fetch.error", e.getMessage(), request.getSql()), e);
} finally {
request.close();
}
}
@Override
public List<SqlRow> findList(RelationalQueryRequest request) {
@@ -159,4 +159,8 @@ class SqlTree {
boolean hasMany() {
return manyProperty != null || rootNode.hasMany();
}
boolean isSingleProperty() {
return rootNode.isSingleProperty();
}
}
@@ -83,4 +83,8 @@ interface SqlTreeNode {
*/
ScalarType<?> getSingleAttributeScalarType();
/**
* Return true if the query is known to only have a single property selected.
*/
boolean isSingleProperty();
}
@@ -138,6 +138,11 @@ class SqlTreeNodeBean implements SqlTreeNode {
return false;
}
@Override
public boolean isSingleProperty() {
return properties != null && properties.length == 1 && children.length == 0;
}
@Override
public ScalarType<?> getSingleAttributeScalarType() {
if (properties == null || properties.length == 0) {
@@ -39,6 +39,11 @@ class SqlTreeNodeExtraJoin implements SqlTreeNode {
this.manyJoin = assocBeanProperty instanceof STreePropertyAssocMany;
}
@Override
public boolean isSingleProperty() {
return false;
}
@Override
public void buildRawSqlSelectChain(List<String> selectChain) {
// nothing to add
@@ -36,6 +36,11 @@ class SqlTreeNodeManyWhereJoin implements SqlTreeNode {
this.parentPrefix = split[0];
}
@Override
public boolean isSingleProperty() {
return false;
}
@Override
public ScalarType<?> getSingleAttributeScalarType() {
throw new IllegalStateException("No expected");
@@ -1276,8 +1276,8 @@ public class DefaultOrmQuery<T> implements SpiQuery<T> {
}
@Override
public boolean isBeanCacheReload() {
return CacheMode.PUT == useBeanCache;
public boolean isForceHitDatabase() {
return forUpdate != null || CacheMode.PUT == useBeanCache;
}
@Override
@@ -1,10 +1,13 @@
package io.ebeaninternal.server.querydefn;
import io.ebean.EbeanServer;
import io.ebean.RowConsumer;
import io.ebean.RowMapper;
import io.ebean.SqlRow;
import io.ebeaninternal.api.BindParams;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.api.SpiSqlQuery;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
@@ -17,7 +20,7 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
private static final long serialVersionUID = -1098305779779591068L;
private final transient EbeanServer server;
private final transient SpiEbeanServer server;
private String label;
@@ -39,16 +42,11 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
/**
* Additional supply a query detail object.
*/
public DefaultRelationalQuery(EbeanServer server, String query) {
public DefaultRelationalQuery(SpiEbeanServer server, String query) {
this.server = server;
this.query = query;
}
public DefaultRelationalQuery setQuery(String query) {
this.query = query;
return this;
}
@Override
public void findEach(Consumer<SqlRow> consumer) {
server.findEach(this, consumer, null);
@@ -64,6 +62,41 @@ public class DefaultRelationalQuery implements SpiSqlQuery {
return server.findList(this, null);
}
@Override
public BigDecimal findSingleDecimal() {
return server.findSingleAttribute(this, BigDecimal.class);
}
@Override
public Long findSingleLong() {
return server.findSingleAttribute(this, Long.class);
}
@Override
public <T> T findSingleAttribute(Class<T> cls) {
return server.findSingleAttribute(this, cls);
}
@Override
public <T> List<T> findSingleAttributeList(Class<T> cls) {
return server.findSingleAttributeList(this, cls);
}
@Override
public <T> T findOne(RowMapper<T> mapper) {
return server.findOneMapper(this, mapper);
}
@Override
public <T> List<T> findList(RowMapper<T> mapper) {
return server.findListMapper(this, mapper);
}
@Override
public void findEachRow(RowConsumer consumer) {
server.findEachRow(this, consumer);
}
@Override
public SqlRow findOne() {
return server.findOne(this, null);
@@ -43,6 +43,9 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
private final TransactionManager manager;
private final boolean logSql;
private final boolean logSummary;
/**
* The status of the transaction.
*/
@@ -70,6 +73,8 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
*/
ImplicitReadOnlyTransaction(TransactionManager manager, Connection connection) {
this.manager = manager;
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.active = true;
this.connection = connection;
this.persistenceContext = new DefaultPersistenceContext();
@@ -441,22 +446,22 @@ class ImplicitReadOnlyTransaction implements SpiTransaction, TxnProfileEventCode
@Override
public boolean isLogSql() {
return TransactionManager.SQL_LOGGER.isDebugEnabled();
return logSql;
}
@Override
public boolean isLogSummary() {
return TransactionManager.SUM_LOGGER.isDebugEnabled();
return logSummary;
}
@Override
public void logSql(String msg) {
TransactionManager.SQL_LOGGER.debug(msg);
manager.log().sql().debug(msg);
}
@Override
public void logSummary(String msg) {
TransactionManager.SUM_LOGGER.debug(msg);
manager.log().sum().debug(msg);
}
/**
@@ -55,6 +55,9 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
*/
protected final String id;
private final boolean logSql;
private final boolean logSummary;
/**
* The user defined label to group execution statistics.
*/
@@ -200,11 +203,15 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
this.startNanos = System.nanoTime();
if (manager == null) {
this.logSql = false;
this.logSummary = false;
this.skipCacheAfterWrite = true;
this.batchMode = PersistBatch.NONE;
this.batchOnCascadeMode = PersistBatch.NONE;
this.onQueryOnly = OnQueryOnly.ROLLBACK;
} else {
this.logSql = manager.isLogSql();
this.logSummary = manager.isLogSummary();
this.skipCacheAfterWrite = manager.isSkipCacheAfterWrite();
this.batchMode = manager.getPersistBatch();
this.batchOnCascadeMode = manager.getPersistBatchOnCascade();
@@ -861,22 +868,22 @@ public class JdbcTransaction implements SpiTransaction, TxnProfileEventCodes {
@Override
public boolean isLogSql() {
return TransactionManager.SQL_LOGGER.isDebugEnabled();
return logSql;
}
@Override
public boolean isLogSummary() {
return TransactionManager.SUM_LOGGER.isDebugEnabled();
return logSummary;
}
@Override
public void logSql(String msg) {
TransactionManager.SQL_LOGGER.debug(Str.add(logPrefix, msg));
manager.log().sql().debug(Str.add(logPrefix, msg));
}
@Override
public void logSummary(String msg) {
TransactionManager.SUM_LOGGER.debug(Str.add(logPrefix, msg));
manager.log().sum().debug(Str.add(logPrefix, msg));
}
/**
@@ -21,6 +21,7 @@ class SavepointTransaction extends SpiTransactionProxy {
private final Savepoint savepoint;
private final Connection connection;
private final String logPrefix;
private final String spPrefix;
private boolean rollbackOnly;
private int state;
@@ -31,7 +32,8 @@ class SavepointTransaction extends SpiTransactionProxy {
this.connection = transaction.getInternalConnection();
this.savepoint = connection.setSavepoint();
int savepointId = savepoint.getSavepointId();
this.logPrefix = "txn[" + transaction.getId() + "-sp" + savepointId + "] ";
this.spPrefix = "sp[" + savepointId + "] ";
this.logPrefix = transaction.getLogPrefix() + spPrefix;
}
@Override
@@ -41,12 +43,12 @@ class SavepointTransaction extends SpiTransactionProxy {
@Override
public void logSql(String msg) {
TransactionManager.SQL_LOGGER.debug(Str.add(logPrefix, msg));
transaction.logSql(Str.add(spPrefix, msg));
}
@Override
public void logSummary(String msg) {
TransactionManager.SUM_LOGGER.debug(Str.add(logPrefix, msg));
transaction.logSummary(Str.add(spPrefix, msg));
}
@Override
@@ -2,7 +2,6 @@ package io.ebeaninternal.server.transaction;
import io.ebean.util.JdbcClose;
import io.ebeaninternal.api.SpiTransaction;
import org.slf4j.Logger;
import javax.persistence.PersistenceException;
import java.sql.Connection;
@@ -14,8 +13,6 @@ import java.util.concurrent.atomic.AtomicLong;
*/
abstract class TransactionFactory {
static final Logger TXN_LOGGER = TransactionManager.TXN_LOGGER;
final AtomicLong counter = new AtomicLong(1000);
final TransactionManager manager;
@@ -51,8 +48,8 @@ abstract class TransactionFactory {
}
}
if (explicit && TXN_LOGGER.isTraceEnabled()) {
TXN_LOGGER.trace(t.getLogPrefix() + "Begin");
if (explicit && manager.log().txn().isTrace()) {
manager.log().txn().trace(t.getLogPrefix() + "Begin");
}
return t;
@@ -15,6 +15,8 @@ import io.ebean.meta.MetricType;
import io.ebean.meta.MetricVisitor;
import io.ebeaninternal.api.ScopeTrans;
import io.ebeaninternal.api.ScopedTransaction;
import io.ebeaninternal.api.SpiLogManager;
import io.ebeaninternal.api.SpiLogger;
import io.ebeaninternal.api.SpiProfileHandler;
import io.ebeaninternal.api.SpiTransaction;
import io.ebeaninternal.api.SpiTransactionManager;
@@ -53,12 +55,6 @@ public class TransactionManager implements SpiTransactionManager {
public static final Logger clusterLogger = LoggerFactory.getLogger("io.ebean.Cluster");
public static final Logger SQL_LOGGER = LoggerFactory.getLogger("io.ebean.SQL");
public static final Logger SUM_LOGGER = LoggerFactory.getLogger("io.ebean.SUM");
public static final Logger TXN_LOGGER = LoggerFactory.getLogger("io.ebean.TXN");
protected final BeanDescriptorManager beanDescriptorManager;
/**
@@ -128,6 +124,9 @@ public class TransactionManager implements SpiTransactionManager {
private final TransactionFactory transactionFactory;
private final SpiLogManager logManager;
private final SpiLogger txnLogger;
private final DatabasePlatform databasePlatform;
private final SpiProfileHandler profileHandler;
@@ -142,6 +141,8 @@ public class TransactionManager implements SpiTransactionManager {
*/
public TransactionManager(TransactionManagerOptions options) {
this.logManager = options.logManager;
this.txnLogger = logManager.txn();
this.databasePlatform = options.config.getDatabasePlatform();
this.skipCacheAfterWrite = options.config.isSkipCacheAfterWrite();
this.notifyL2CacheInForeground = options.notifyL2CacheInForeground;
@@ -355,12 +356,12 @@ public class TransactionManager implements SpiTransactionManager {
public void notifyOfRollback(SpiTransaction transaction, Throwable cause) {
try {
if (TXN_LOGGER.isDebugEnabled()) {
if (txnLogger.isDebug()) {
String msg = transaction.getLogPrefix() + "Rollback";
if (cause != null) {
msg += " error: " + formatThrowable(cause);
}
TXN_LOGGER.debug(msg);
txnLogger.debug(msg);
}
} catch (Exception ex) {
@@ -374,8 +375,8 @@ public class TransactionManager implements SpiTransactionManager {
public void notifyOfQueryOnly(SpiTransaction transaction) {
// Nothing that interesting here
if (TXN_LOGGER.isTraceEnabled()) {
TXN_LOGGER.trace(transaction.getLogPrefix() + "Commit - query only");
if (txnLogger.isTrace()) {
txnLogger.trace(transaction.getLogPrefix() + "Commit - query only");
}
}
@@ -409,8 +410,8 @@ public class TransactionManager implements SpiTransactionManager {
public void notifyOfCommit(SpiTransaction transaction) {
try {
if (TXN_LOGGER.isDebugEnabled()) {
TXN_LOGGER.debug(transaction.getLogPrefix() + "Commit");
if (txnLogger.isDebug()) {
txnLogger.debug(transaction.getLogPrefix() + "Commit");
}
PostCommitProcessing postCommit = new PostCommitProcessing(clusterManager, this, transaction);
@@ -702,4 +703,16 @@ public class TransactionManager implements SpiTransactionManager {
throw new RuntimeException("Should never get here?");
}
}
public SpiLogManager log() {
return logManager;
}
public boolean isLogSql() {
return logManager.sql().isDebug();
}
public boolean isLogSummary() {
return logManager.sum().isDebug();
}
}

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